【问题标题】:The placement of static global and global variables with the same identifier具有相同标识符的静态全局变量和全局变量的放置
【发布时间】:2021-12-28 22:41:33
【问题描述】:

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

文件: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;
}

然后一个问题问我这句话是否正确:无论程序如何编译,链接或运行,它的输出都必须是一个常数(相对于多次运行)并且它是非零的。

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

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

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

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

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

【问题讨论】:

  • 除非您首先将指针转换为合适的整数类型,否则从技术上讲,进行减法应该是未定义的行为,但实际上(或在 uintptr_t 中进行减法时),只要文件最终编译成同一个二进制文件(如果它们最终在不同的共享库中,则相对位置可能会在多次运行之间发生变化)。
  • 即使有适当的演员表,请参阅port70.net/~nsz/c/c11/n1570.html#6.5.6p9。这两个地址不满足约束,所以行为没有很好的定义。
  • @PSkocik 在这种情况下,由于您提到的原因,没关系。只要您不期望减法产生有意义的结果,就可以(总是)减去指针。使用!=== 而不是- 可能更可口。或者,只需使用%p 打印
  • Re “它会影响全局 foo”:C 没有任何全局名称空间(int 等关键字除外)或范围。它最大的作用域是文件作用域,两个foo在不同的文件中(翻译单元)。使名称引用同一个对象是通过链接完成的,而static int foo;具有内部链接,因此链接器在解析外部链接时看不到。
  • 是否“无论程序如何编译、链接或运行,它的输出必须是一个常数(相对于多次运行)并且它是非零的。”意味着如果程序链接不同,输出可能会有所不同?例如,给定我的特定系统,cc -o f f1.c f2.c &amp;&amp; ./f 打印“1”,而 cc -o f f2.c f1.c &amp;&amp; ./f 打印“-1”。

标签: c gcc linker


【解决方案1】:

确实,f1.c 模块中的静态变量foof2.c 模块中的全局foo 是不同的对象。 bar() 函数。因此输出应该不为零。

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

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

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

【讨论】:

    【解决方案2】:

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

    要正确查看 chars 的差异,您应该将两者都转换为 char 指针并使用将打印 ptrdiff_t 类型的 %td 格式。如果您的平台不支持,请将结果转换为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;
    
    

    【讨论】:

    • 关于“正确”:为了正确实现作者的意图,不应该减去指针,因为减去指向不在同一聚合内的对象的指针时的行为不是由 C 标准定义的。可以先转换为uintptr_t,然后减去,尽管结果仍然是实现定义的,并且可能在不同的运行中为“相同”地址产生不同的整数,并且我们不能保证(通过 C 标准)相同的整数即使对于彼此偏移相同的地址也存在差异。
    猜你喜欢
    • 2022-01-28
    • 2015-06-12
    • 1970-01-01
    • 1970-01-01
    • 2015-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-22
    相关资源
    最近更新 更多