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

let*_*utx 40 c memory-alignment

我在我的计算机(运行 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;
}
Run Code Online (Sandbox Code Playgroud)

该程序的输出是

$ 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
Run Code Online (Sandbox Code Playgroud)

指针的大小&argv为 8 个字节。我期望的地址是argcaddress of (argv) + sizeof (argv) = 0x7ffed1a4c9f0 + 0x8 = 0x7ffed1a4c9f8但它们之间有一个 4 字节的填充。为什么会这样?

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

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

Eri*_*hil 61

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

  • 请注意,这可能会发生*即使它们是在堆栈上传递的*;编译器没有义务使用堆栈上的传入值槽作为值进入的本地对象的存储。这样做可能是有意义的,因为函数最终将进行尾调用,并且需要这些对象的当前值来生成尾调用的传出参数。 (7认同)

Joh*_*ger 11

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

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

此外,我在想出一种方法可以使此类信息在程序中有用时遇到了麻烦。例如,即使您“知道” 的地址argv是 的地址之前 12 个字节argc,仍然没有定义的方法来计算这些指针中的一个。

  • @R..GitHubSTOPHELPINGICE:从另一个计算一个是部分定义的,没有明确定义。C 标准对于如何执行到 uintptr_t 的转换并不严格,并且它当然没有定义参数地址或参数传递位置之间的关系。 (7认同)
  • @R..GitHubSTOPHELPINGICE:事实上,您可以往返意味着 g(f(x)) = x,其中 x 是指针,f 是转换指针到 uintptr_t,g 是转换 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 只是搞砸了身份验证;它不更新... (6认同)
  • …地址位。或者转换为 uintptr_t 可能会在高 16 位给出基地址,在低 16 位给出偏移量,低位加 4 可能会进位到高位,但缩放是错误的(因为表示的地址不是基•65536+偏移,而是基•64+偏移,就像在某些系统中一样)。很简单,从转换中获得的“uintptr_t”不一定是一个简单的地址。 (5认同)
  • @R..GitHubSTOPHELPINGICE:抱歉,我错过了您添加的值是根据地址的两个“uintptr_t”转换的不同计算得出的,而不是不同的指针或“已知”字节距离。当然,这是事实,但是它有什么用呢?正如答案所述,“仍然没有定义的方法来计算其中一个指针”,但该计算不会从“a”计算“b”,而是从“a”和“a”计算“b” “b”,因为在减法中必须使用“b”来计算要添加的金额。没有定义从另一个计算一个。 (5认同)
  • @R..GitHubSTOPHELPINGICE 根据我对标准的阅读,只有一个微弱的保证“(void *)(uintptr_t)(void *)p”将与“(void *)p”进行比较。值得注意的是,委员会已经对几乎这个确切的问题进行了评论,得出的结论是“实现......也可能将基于不同来源的指针视为不同的_即使它们按位相同_”。 (4认同)