具有相同标识符的静态全局和全局变量的放置

The placement of static global and global variables with the same identifier

我正在学习有关链接的一些基础知识并遇到了以下代码。

文件:f1.c

#include <stdio.h>

static int foo;

int main() {
    int *bar();
    printf("%ld\n", bar() - &foo);
    return 0;
}

文件:f2.c

int foo = 0;
int *bar() {
    return &foo;
}

然后有个问题问我这个说法对不对:无论程序是怎么编译、链接还是运行,它的输出一定是一个常量(相对于多个运行 s) 并且它是非零的。

我认为这是正确的。虽然 foo 有两个定义,但其中一个是用 static 声明的,所以它隐藏了全局 foo,因此链接器不会只选择一个 foo。由于运行时变量的相对位置应该是固定的(虽然绝对地址可以变化),所以输出必须是一个常量。

我试验了代码,在 gcc 7.5.0gcc f1.c f2.c -o test && ./test 上它总是输出 1(但如果我删除 static,它会输出 0).但是答案说上面的说法是错误的。我想知道为什么。我的理解有没有错误?

objdump 的结果如下。 foo 都转到 .bss

上下文。 这是与计算机系统的链接章节相关的问题:Randal E. Bryant 和 David R. O'Hallaron 的程序员视角。但它不是来自这本书。

更新。 好的,我找到原因了。如果我们交换顺序并编译为gcc f2.c f1.c -o test && ./test,它将输出-1。好无聊的问题...

确实f1.c模块中的静态变量foo中的全局foo是不同的对象]f2.c 模块由 bar() 函数引用。因此输出应该是非零的。

但是请注意,减去 2 个不指向同一个数组或指向同一个数组末尾的指针是没有意义的,因此即使对于不同的对象,差异也可能是 0。即使 &foo == bar() 不是 0,这也可能发生,因为对象不同。这种行为在使用大型模型的 16 位分段系统中很常见,其中减去指针只影响指针的偏移部分,而比较它们是否相等比较段和偏移部分。现代系统有一个更规则的架构,所有的东西都在同一个地址 space。请注意,并非每个系统都是 linux PC。

此外,printf 转换格式 %ld 需要一个 long 类型的值,而您传递的 ptrdiff_t 类型的值可能是不同的类型(即例如,Windows 64 位目标上的 64 位 long long,这与那里的 32 位长不同)。使用正确的格式 %td 或将参数转换为 (long)(bar() - &foo).

最后,C 语言中没有任何内容可以保证全局对象地址之间的差异在同一程序的不同运行中保持不变。许多现代系统执行地址 space 随机化以降低成功攻击的风险,导致同一可执行文件的连续运行中堆栈对象 and/or 静态数据的不同地址。

从 wring printf 格式和指针算术问题中抽象出来 static 来自一个编译单元的全局变量将不同于 staticnon-static 在其他编译单元中具有相同名称的变量.

要正确查看 char 中的差异,您应该将两者都转换为 char 指针并使用 %td 格式,这将打印 ptrdiff_t 类型。如果你的平台不支持,将结果强制转换为long long int

int main() {
    int *bar();
    printf("%td\n", (char *)bar() - (char *)&foo);
    return 0;
}

printf("%lld\n", (long long)((char *)bar() - (char *)&foo));

如果要将此差异存储在变量中,请使用 ptrdiff_t 类型:

ptrdiff_t diff = (char *)bar() - (char *)&foo;