为什么argc和argv的地址相隔12个字节?


40

我在计算机(运行Linux的64位Intel)上运行了以下程序。

#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个字节。我期望的地址是argcaddress of (argv) + sizeof (argv) = 0x7ffed1a4c9f0 + 0x8 = 0x7ffed1a4c9f8但是它们之间有4字节的填充。为什么会这样呢?

我的猜测是,这可能是由于内存对齐所致,但我不确定。

我注意到与调用的函数相同的行为。


15
为什么不?它们可能相隔174个字节。答案将取决于您的操作系统和/或为其设置的包装器库main
aschepler

2
@aschepler:不应依赖为设置的任何包装器main。在C语言中,main可以将其称为常规函数,因此它需要像常规函数一样接收参数,并且必须遵守ABI。
埃里克·Postpischil

@aschelper:我注意到其他功能也有相同的行为。
letmutx

4
这是一个有趣的“思想实验”,但实际上,没有什么比“我想知道为什么”更重要了。这些地址可以根据操作系统,编译器,编译器版本,处理器体系结构进行更改,并且在任何情况下都不应依赖于它们。
尼尔

2
sizeof的结果必须使用%zu
phuclv

Answers:


61

在您的系统上,前几个整数或指针参数在寄存器中传递且没有地址。当您使用&argc或作为地址时&argv,编译器必须通过将寄存器内容写入堆栈位置并为您提供这些堆栈位置的地址来构造地址。这样做,从某种意义上说,编译器会选择对它有利的任何堆栈位置。


6
请注意,即使将它们传递到堆栈中,也可能发生这种情况;编译器没有义务使用堆栈上的传入值插槽作为值所进入的本地对象的存储。这样做可能是有道理的,因为该函数最终将进行尾调用,并且需要这些对象的当前值来生成用于尾调用的传出参数。
R .. GitHub停止帮助ICE

10

为什么argc和argv的地址相隔12个字节?

从语言标准的角度来看,答案是“没有特殊原因”。C没有指定或暗示功能参数的地址之间的任何关系。@EricPostpischil描述了您的特定实现中可能发生的情况,但是对于其中所有参数都在堆栈上传递的实现,这些细节将有所不同,这不是唯一的选择。

而且,我在想​​出一种在程序中有用这些信息的方式时遇到了麻烦。例如,即使您“知道”的地址比的地址argv早12个字节argc,仍然没有确定的方法可以从另一个指针中计算出其中一个指针。


7
@ R..GitHubSTOPHELPINGICE:彼此之间的计算是部分定义的,没有很好地定义。C标准对转换uintptr_t的执行方式并不严格,它当然也没有定义参数地址之间或参数传递位置之间的关系。
埃里克·Postpischil

6
@ R..GitHubSTOPHELPINGICE:可以往返的事实意味着g(f(x))= x,其中x是指针,f是convert-pointer-to-uintptr_t,g是convert-uintptr_t-to -指针。从数学和逻辑上讲,这并不意味着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可能会带来高位,但是缩放是错误的(因为所表示的地址不是base•65536 + offset,而是base•64 + offset,就像在某些系统中一样)。很简单,uintptr_t您从转换中获得的不一定是一个简单的地址。
埃里克·Postpischil

4
从我对标准的阅读中了解到@ R..GitHubSTOPHELPINGICE,只有一个弱保证(void *)(uintptr_t)(void *)p可以比较(void *)p。值得一提的是,委员会几乎在这个确切的问题上都发表了评论,认为“实现...也可能将基于不同起源的指针视为不同的,即使它们在位上是相同的。”
Ryan Avella

5
@ R..GitHubSTOPHELPINGICE:抱歉,我想念您添加的值是两次uintptr_t地址转换的不同而不是指针的不同或字节的“已知”距离。当然,这是正确的,但是它有什么用呢?说:“还是有计算从其他的指针中的一个没有定义的方式”作为答案的状态仍然是一个事实,但计算不计算ba而是计算b从两个ab,因为b必须在减法来计算的金额添加。没有定义彼此计算。
埃里克·波斯特皮希尔
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.