Tại sao các địa chỉ của argc và argv cách nhau 12 byte?


40

Tôi đã chạy chương trình sau trên máy tính của mình (Intel 64-bit chạy Linux).

#include <stdio.h>

void test(int argc, char **argv) {
    printf("[test] Argc Pointer: %p\n", &argc);
    printf("[test] Argv Pointer: %p\n", &argv);
}

int main(int argc, char **argv) {
    printf("Argc Pointer: %p\n", &argc);
    printf("Argv Pointer: %p\n", &argv);
    printf("Size of &argc: %lu\n", sizeof (&argc));
    printf("Size of &argv: %lu\n", sizeof (&argv));
    test(argc, argv);
    return 0;
}

Đầu ra của chương trình là

$ gcc size.c -o size
$ ./size
Argc Pointer: 0x7fffd7000e4c
Argv Pointer: 0x7fffd7000e40
Size of &argc: 8
Size of &argv: 8
[test] Argc Pointer: 0x7fffd7000e2c
[test] Argv Pointer: 0x7fffd7000e20

Kích thước của con trỏ &argvlà 8 byte. Tôi dự kiến ​​địa chỉ argcaddress of (argv) + sizeof (argv) = 0x7ffed1a4c9f0 + 0x8 = 0x7ffed1a4c9f8nhưng có một vùng đệm 4 byte ở giữa chúng. Tại sao điều này là trường hợp?

Tôi đoán là nó có thể là do căn chỉnh bộ nhớ, nhưng tôi không chắc chắn.

Tôi nhận thấy hành vi tương tự với các chức năng tôi gọi là tốt.


15
Tại sao không? Chúng có thể cách nhau 174 byte. Câu trả lời sẽ phụ thuộc vào hệ điều hành của bạn và / hoặc thư viện trình bao bọc thiết lập cho main.
aschepler

2
@aschepler: Không nên phụ thuộc vào bất kỳ trình bao bọc nào thiết lập cho main. Trong C, maincó thể được gọi là một hàm thông thường, vì vậy nó cần nhận các đối số như một hàm thông thường và phải tuân theo ABI.
Eric Postpischil

@aschelper: Tôi cũng nhận thấy hành vi tương tự đối với các chức năng khác.
letmutx

4
Đó là một 'thử nghiệm suy nghĩ' thú vị, nhưng thực sự, không có gì nên hơn là một 'Tôi tự hỏi tại sao'. Các địa chỉ này có thể thay đổi tùy thuộc vào hệ điều hành, trình biên dịch, phiên bản trình biên dịch, kiến ​​trúc bộ xử lý và không nên phụ thuộc vào 'đời thực'.
Neil

Câu trả lời:


61

Trên hệ thống của bạn, một vài đối số số nguyên hoặc con trỏ đầu tiên được truyền vào các thanh ghi và không có địa chỉ. Khi bạn lấy địa chỉ của chúng bằng &argchoặc &argv, trình biên dịch phải chế tạo địa chỉ bằng cách viết nội dung đăng ký vào vị trí ngăn xếp và cung cấp cho bạn địa chỉ của các vị trí ngăn xếp đó. Khi làm như vậy, trình biên dịch chọn, theo một nghĩa nào đó, bất kỳ vị trí ngăn xếp nào xảy ra đều thuận tiện cho nó.


6
Lưu ý rằng điều này có thể xảy ra ngay cả khi chúng được truyền vào ngăn xếp ; trình biên dịch không có nghĩa vụ sử dụng khe giá trị đến trên ngăn xếp làm bộ lưu trữ cho các đối tượng cục bộ mà các giá trị đi vào. Có thể có ý nghĩa để làm điều này là chức năng cuối cùng sẽ gọi đuôi và cần các giá trị hiện tại của các đối tượng này để tạo ra các đối số đi cho cuộc gọi đuôi.
R .. GitHub DỪNG GIÚP ICE

10

Tại sao các địa chỉ của argc và argv cách nhau 12 byte?

Từ quan điểm của tiêu chuẩn ngôn ngữ, câu trả lời là "không có lý do cụ thể". C không chỉ định hoặc ngụ ý bất kỳ mối quan hệ giữa các địa chỉ của các tham số chức năng. @EricPostpischil mô tả những gì có thể xảy ra trong triển khai cụ thể của bạn, nhưng những chi tiết đó sẽ khác nhau đối với việc triển khai trong đó tất cả các đối số được truyền trên ngăn xếp và đó không phải là cách thay thế duy nhất.

Hơn nữa, tôi gặp khó khăn khi nghĩ ra cách mà thông tin đó có thể hữu ích trong một chương trình. Ví dụ, ngay cả khi bạn "biết" rằng địa chỉ của argvlà 12 byte trước địa chỉ của argc, vẫn không có cách nào được xác định để tính toán một trong những con trỏ đó với nhau.


7
@ R..GitHubSTOPHELPINGICE: Tính toán cái này từ cái kia được xác định một phần, không được xác định rõ. Tiêu chuẩn C không nghiêm ngặt về cách uintptr_tthực hiện chuyển đổi và chắc chắn nó không xác định mối quan hệ giữa các địa chỉ của tham số hoặc nơi các đối số được truyền.
Eric Postpischil

6
@ R..GitHubSTOPHELPINGICE: Thực tế là bạn có thể đi khứ hồi có nghĩa là g (f (x)) = x, trong đó x là một con trỏ, f là convert-trỏ-to-uintptr_t và g là convert-uintptr_t-to -pulum. Về mặt toán học và logic, nó không ngụ ý rằng g (f (x) +4) = x + 4. Ví dụ: nếu f (x) là x² và g (y) là sqrt (y), thì g (f (x)) = x (đối với x không âm thực), nhưng g (f (x) +4) ≠ x + 4, nói chung. Trong trường hợp con trỏ, việc chuyển đổi thành uintptr_tcó thể cung cấp địa chỉ trong 24 bit cao và một số bit xác thực trong 8 bit thấp. Sau đó thêm 4 chỉ cần xác thực; nó không cập nhật chương trình
Eric Postpischil

5
Các bit địa chỉ bit. Hoặc việc chuyển đổi sang uintptr_t có thể cung cấp địa chỉ cơ sở ở 16 bit cao và bù vào 16 bit thấp và thêm 4 vào các bit thấp có thể mang theo các bit cao, nhưng tỷ lệ bị sai (vì địa chỉ được thể hiện không phải là cơ sở • bù đắp 65536 + nhưng đúng hơn là cơ sở • bù 64 +, như trong một số hệ thống). Rất đơn giản, những uintptr_tgì bạn nhận được từ một chuyển đổi không nhất thiết là một địa chỉ đơn giản.
Eric Postpischil

4
@ R..GitHubSTOPHELPINGICE từ việc tôi đọc tiêu chuẩn, chỉ có một đảm bảo yếu (void *)(uintptr_t)(void *)psẽ so sánh bằng (void *)p. Và đáng lưu ý rằng ủy ban đã bình luận về gần như vấn đề chính xác này, kết luận rằng "việc triển khai ... cũng có thể coi con trỏ dựa trên nguồn gốc khác nhau là khác nhau mặc dù chúng giống hệt nhau một chút ."
Ryan Avella

5
@ R..GitHubSTOPHELPINGICE: Xin lỗi, tôi đã bỏ lỡ rằng bạn đã thêm một giá trị được tính là khác nhau của hai uintptr_tchuyển đổi địa chỉ thay vì khác nhau của con trỏ hoặc khoảng cách được biết đến của byte theo byte. Chắc chắn, đó là sự thật, nhưng nó hữu ích như thế nào? Nó vẫn còn đúng là “vẫn còn cách nào định nghĩa để tính một trong những gợi ý từ người khác” như các quốc gia trả lời, nhưng tính toán mà không tính toán btừ amà là tính toán bcủa cả hai ab, vì bphải được sử dụng trong các phép trừ để tính toán số tiền thêm. Tính toán cái này từ cái kia không được xác định.
Eric Postpischil
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.