เหตุใดที่อยู่ของ argc และ argv 12 ไบต์แยกกัน


40

ฉันรันโปรแกรมต่อไปนี้บนคอมพิวเตอร์ของฉัน (Intel 64 บิตที่ใช้ 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;
}

ผลลัพธ์ของโปรแกรมคือ

$ 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

ขนาดของตัวชี้&argvคือ 8 ไบต์ ฉันคาดว่าที่อยู่argcจะเป็นaddress of (argv) + sizeof (argv) = 0x7ffed1a4c9f0 + 0x8 = 0x7ffed1a4c9f8แต่มีช่องว่างระหว่าง 4 ไบต์ เหตุใดจึงเป็นเช่นนี้

ฉันเดาว่าอาจเป็นเพราะการจัดตำแหน่งหน่วยความจำ แต่ฉันไม่แน่ใจ

ฉันสังเกตเห็นพฤติกรรมเดียวกันกับฟังก์ชั่นที่ฉันเรียกเช่นกัน


15
ทำไมจะไม่ล่ะ? พวกมันสามารถแยกได้ 174 ไบต์ คำตอบจะขึ้นอยู่กับระบบปฏิบัติการของคุณและ / mainหรือห้องสมุดเสื้อคลุมที่ไม่ติดตั้งสำหรับ
aschepler

2
@aschepler: มันไม่ควรขึ้นอยู่กับกระดาษห่อใด ๆ mainที่ไม่ติดตั้งสำหรับ ใน C mainสามารถเรียกได้ว่าเป็นฟังก์ชั่นปกติดังนั้นจึงจำเป็นต้องได้รับการโต้แย้งเช่นฟังก์ชั่นปกติและจะต้องเชื่อฟัง ABI
Eric Postpischil

@aschelper: ฉันสังเกตเห็นพฤติกรรมเดียวกันสำหรับฟังก์ชั่นอื่นเช่นกัน
letmutx

4
มันเป็น 'การทดลองทางความคิด' ที่น่าสนใจ แต่จริงๆแล้วไม่มีอะไรที่ควรจะเป็นมากกว่า 'ฉันสงสัยว่าทำไม' ที่อยู่เหล่านี้สามารถเปลี่ยนแปลงได้ขึ้นอยู่กับระบบปฏิบัติการคอมไพเลอร์เวอร์ชั่นคอมไพเลอร์สถาปัตยกรรมโปรเซสเซอร์และไม่ควรขึ้นอยู่กับใน 'ชีวิตจริง'
Neil

2
ผลลัพธ์ของขนาดของต้องพิมพ์โดยใช้%zu
phuclv

คำตอบ:


61

ในระบบของคุณอาร์กิวเมนต์จำนวนเต็มหรือตัวชี้จำนวนน้อยแรกจะถูกส่งผ่านในการลงทะเบียนและไม่มีที่อยู่ เมื่อคุณใช้ที่อยู่ด้วย&argcหรือ&argvคอมไพเลอร์จะต้องสร้างที่อยู่โดยการเขียนเนื้อหาการลงทะเบียนไปยังที่ตั้งสแต็คและให้ที่อยู่ของสแต็กที่ตั้งเหล่านั้น ในการทำเช่นนั้นคอมไพเลอร์จะเลือกสถานที่ใดก็ตามที่มีสแต็กเกิดขึ้นเพื่อความสะดวก


6
โปรดทราบว่านี้อาจเกิดขึ้นถึงแม้ว่าพวกเขาจะถูกส่งผ่านสแต็ค ; คอมไพเลอร์ไม่มีข้อผูกมัดที่จะใช้สล็อตขาเข้า - ค่าบนสแต็กเป็นที่เก็บสำหรับวัตถุในท้องถิ่นที่ค่าเข้าไป มันอาจจะเหมาะสมที่จะทำสิ่งนี้ในที่สุดฟังก์ชันก็จะทำการเรียกแบบ tail-tail และต้องการค่าปัจจุบันของวัตถุเหล่านี้เพื่อสร้างอาร์กิวเมนต์ที่ส่งออกสำหรับ tail-call
. GitHub หยุดช่วยน้ำแข็ง

10

เหตุใดที่อยู่ของ argc และ argv 12 ไบต์แยกกัน

จากมุมมองของมาตรฐานภาษาคำตอบคือ "ไม่มีเหตุผลเฉพาะ" C ไม่ได้ระบุหรือบอกเป็นนัยถึงความสัมพันธ์ระหว่างที่อยู่ของพารามิเตอร์ฟังก์ชัน @EricPostpischil อธิบายถึงสิ่งที่อาจเกิดขึ้นในการใช้งานเฉพาะของคุณ แต่รายละเอียดเหล่านั้นจะแตกต่างกันไปสำหรับการใช้งานที่อาร์กิวเมนต์ทั้งหมดถูกส่งผ่านบนสแต็กและไม่ใช่ทางเลือกเดียว

นอกจากนี้ฉันมีปัญหาในการหาวิธีที่ข้อมูลดังกล่าวจะเป็นประโยชน์ในโปรแกรม ตัวอย่างเช่นแม้ว่าคุณจะ "รู้" ว่าที่อยู่ของargvคือ 12 ไบต์ก่อนที่อยู่ของargcยังคงไม่มีวิธีที่กำหนดไว้ในการคำนวณหนึ่งในตัวชี้เหล่านั้นจากที่อื่น


7
@ R..GitHubSTOPHELPINGICE: การคำนวณอย่างใดอย่างหนึ่งจากที่อื่นมีการกำหนดบางส่วนไม่ได้กำหนดไว้อย่างดี มาตรฐาน C ไม่เข้มงวดกับวิธีการแปลงที่uintptr_tจะดำเนินการและแน่นอนไม่ได้กำหนดความสัมพันธ์ระหว่างที่อยู่ของพารามิเตอร์หรือที่ข้อโต้แย้งจะถูกส่งผ่าน
Eric Postpischil

6
@ R..GitHubSTOPHELPINGICE: ความจริงที่ว่าคุณสามารถไปกลับหมายความว่า g (f (x)) = x โดยที่ x คือตัวชี้ f คือการแปลงตัวชี้ไปยัง uintptr_t-g และ g คือ convert-uintptr_t-to -pointer ในเชิงคณิตศาสตร์และเชิงตรรกะมันไม่ได้หมายความว่า g (f (x) +4) = x + 4 ตัวอย่างเช่นถ้า f (x) เป็นx²และ g (y) เป็น sqrt (y) ดังนั้น g (f (x)) = x (สำหรับจริงที่ไม่ใช่เชิงลบ x) แต่ g (f (x) +4) ≠ x + 4 โดยทั่วไป ในกรณีของพอยน์เตอร์การแปลงเป็นuintptr_tอาจให้ที่อยู่ใน 24 บิตสูงและบิตการรับรองความถูกต้องบางอย่างใน 8 บิตต่ำ จากนั้นเพิ่ม 4 สกรูเพียงรับรองความถูกต้อง; ไม่อัปเดต ...
Eric Postpischil

5
…บิตที่อยู่ หรือการแปลงเป็น uintptr_t อาจให้ที่อยู่พื้นฐานใน 16 บิตสูงและออฟเซ็ตใน 16 บิตต่ำและการเพิ่ม 4 ถึงบิตต่ำอาจดำเนินการเป็นบิตสูง แต่การปรับเป็นผิด (เพราะที่อยู่ที่แสดงไม่ใช่ ฐาน• 65536 + ออฟเซ็ต แต่ค่อนข้างเป็นฐาน• 64 + ออฟเซ็ตเหมือนในบางระบบ) ค่อนข้างง่ายที่uintptr_tคุณได้รับจากการแปลงไม่จำเป็นต้องเป็นที่อยู่ธรรมดา
Eric Postpischil

4
@ R..GitHubSTOPHELPINGICE จากการอ่านของฉันของมาตรฐานมีเพียงการรับประกันอ่อนแอที่จะเปรียบเทียบเท่ากับ(void *)(uintptr_t)(void *)p (void *)pและมันก็คุ้มค่าที่จะทราบว่าคณะกรรมการมีความเห็นในเกือบปัญหาตรงนี้สรุปว่า "การใช้งาน ... อาจชี้การรักษาขึ้นอยู่กับต้นกำเนิดที่แตกต่างกันที่แตกต่างกันถึงแม้ว่าพวกเขาจะเหมือนกันค่าที่เหมาะสม ."
Ryan Avella

5
@ R..GitHubSTOPHELPINGICE: ขออภัยฉันพลาดที่คุณเพิ่มค่าที่คำนวณจากความแตกต่างของuintptr_tการแปลงที่อยู่สองแห่งแทนที่จะเป็นพอยน์เตอร์หรือระยะทาง "รู้จัก" เป็นไบต์ แน่นอนว่าเป็นเรื่องจริง แต่มันมีประโยชน์อย่างไร? มันยังคงเป็นความจริงที่“ ยังไม่มีวิธีที่กำหนดไว้ในการคำนวณหนึ่งในตัวชี้เหล่านั้นจากที่อื่น” เป็นคำตอบระบุ แต่การคำนวณนั้นไม่ได้คำนวณbจากaแต่ค่อนข้างจะคำนวณbจากทั้งสองaและbเนื่องจากbต้องใช้ในการลบเพื่อคำนวณจำนวน เพื่อเพิ่ม. การคำนวณอย่างใดอย่างหนึ่งจากที่อื่นไม่ได้กำหนดไว้
Eric Postpischil
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.