【问题标题】:Why are the addresses of argc and argv 12 bytes apart?为什么 argc 和 argv 的地址相隔 12 个字节?
【发布时间】:2020-05-24 11:13:29
【问题描述】:

我在我的计算机(运行 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

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

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

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

【问题讨论】:

  • 为什么不呢?它们可能相隔 174 个字节。答案将取决于您的操作系统和/或为main 设置的包装库。
  • @aschepler:它不应该依赖于为main 设置的任何包装器。在 C 语言中,main 可以作为常规函数调用,因此它需要像常规函数一样接收参数,并且必须遵守 ABI。
  • @aschelper:我注意到其他函数也有相同的行为。
  • 这是一个有趣的“思想实验”,但实际上,没有什么比“我想知道为什么”更重要的了。这些地址可以根据操作系统、编译器、编译器版本、处理器架构而改变,在“现实生活”中绝不应该依赖。
  • sizeof must be printed using %zu的结果

标签: c memory-alignment


【解决方案1】:

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

【讨论】:

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

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

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

此外,我很难想出一种方法来使此类信息在程序中有用。例如,即使您“知道”argv 的地址比argc 的地址早 12 个字节,仍然没有定义方法来计算这些指针中的一个。

【讨论】:

  • @R..GitHubSTOPHELPINGICE:从另一个计算一个是部分定义的,没有很好的定义。 C标准对如何转换为uintptr_t并不严格,当然也没有定义参数地址之间的关系或参数的传递位置。
  • @R..GitHubSTOPHELPINGICE:可以往返意味着 g(f(x)) = x,其中 x 是指针,f 是转换指针到 uintptr_t, g 是转换-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 只是搞砸了身份验证;它没有更新……
  • …地址位。或者转换为 uintptr_t 可能会给出高 16 位的基地址和低 16 位的偏移量,低位加 4 可能会进位到高位,但是缩放是错误的(因为表示的地址不是base•65536+offset 而是 base•64+offset,就像在某些系统中一样)。很简单,你从转换中得到的uintptr_t 不一定是一个简单的地址。
  • @R..GitHubSTOPHELPINGICE 从我对标准的阅读来看,只有很弱的保证 (void *)(uintptr_t)(void *)p 将等于 (void *)p。值得注意的是,委员会几乎就这个确切的问题发表了评论,得出的结论是“实现......也可能将基于不同来源的指针视为不同的即使它们按位相同。”
  • @R..GitHubSTOPHELPINGICE:抱歉,我错过了您添加的值计算为两个地址的 uintptr_t 转换的不同而不是指针的不同或以字节为单位的“已知”距离.当然,这是真的,但它有什么用呢?答案仍然是“仍然没有定义的方法来计算其中一个指针”作为答案状态,但该计算不是从 a 计算 b 而是从 b 计算 ab,因为必须在减法中使用b 来计算要添加的数量。从另一个计算一个没有定义。
猜你喜欢
  • 2022-06-20
  • 2014-08-04
  • 2011-02-08
  • 1970-01-01
  • 1970-01-01
  • 2020-04-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多