GCC将字符串常量存储在只读数据段中,并将其分配给一个地址,指针指向该地址。

9
当我在我的Linux x86_64机器上使用GCC编译和运行以下C程序时:
#include <stdio.h>

int main(void)
{
    char *p1 = "hello";               // Pointers to strings
    char *p2 = "hello";               // Pointers to strings
    if (p1 == p2) {                   // They are equal
    printf("equal %p %p\n", p1, p2);  // equal 0x40064c 0x40064c
                                      // This is always the output on my machine
    }
    else {
    printf("NotEqual %p %p\n", p1, p2);
    }
}

我总是得到以下输出:

equal 0x40064c 0x40064c

我知道字符串存储在常量表中,但与动态分配的内存相比,地址太低了。与以下程序进行比较:
#include <stdio.h>

int main(void)
{
    char p1[] = "hello";                // char arrar
    char p2[] = "hello";                // char array
    if (p1 == p2) {
    printf("equal %p %p\n", p1, p2);
    }
    else {                              // Never equal
    printf("NotEqual %p %p\n", p1, p2); // NotEqual 0x7fff4b25f720 0x7fff4b25f710
                                        // Different pointers every time
                                        // Pointer values too large
    }
}

两个指针不相等,因为它们是两个可以独立操作的数组。
我想知道GCC如何生成这两个程序的代码,并在执行期间将它们映射到内存。由于这已经被记录了很多次,因此欢迎提供文档链接。

1
你总可以查看反汇编代码。这是一个很好的技能需要在未来再次使用。 - Ed S.
你的示例代码中只有两个字符串:equal %p %p\nNotEqual %p %p\np1p2 只是被初始化为某个值的字符数组变量,然后作为字符串使用。特别地,例如你仍然可以执行 p1[0] = 'H'; p2[0] = 'J'; 而没有任何问题。如果你想让 p1p2 成为字符串常量,请使用 static const char p1[] = "Hello";。至少 GCC-4.6.3 将局部 const 数组视为变量,而不是真正的只读常量,因此需要使用 static。它也不会合并字符串,所以这两个字符串有不同的指针。你正在使用哪个编译器? - Nominal Animal
1个回答

13

在这两种情况下,编译器只会在程序的.rodata部分(其中rodata代表只读数据)中发布字符串"hello"的实际字节一次。

它们实际上直接从可执行文件映射到内存中,有点类似于代码段。这就是为什么它们与动态分配的部分相距甚远的原因。

然后:

char *p = "hello";

p简单地初始化为此(只读)数据的地址。显然:

char *q = "hello";

获取相同的地址。这被称为字符串池,是编译器的可选常见优化。

但当您编写以下代码时:

char p[] = "hello";

它可能会生成这样的内容:

char p[6];
memcpy(p, "hello", 6);

实际上,"hello" 是只读的池化字符串的地址。

调用 memcpy 仅用于举例说明。它可能会内联复制,而不是使用函数调用。

如果稍后执行:

char q[] = "hello";

这将定义另一个数组和另一个memcpy()。所以数据相同,但地址不同。

但是这些数组变量将驻留在哪里呢?嗯,那取决于情况。

  • 如果它们是局部的、非静态的变量:则在堆栈中。
  • 如果它们是全局变量:那么它们将在可执行文件的.data部分中,并且它们将与正确的字符一起保存在那里,因此运行时不需要memcpy。这很好,因为那个memcpy必须在main之前执行。
  • 如果它们是本地静态变量:与全局变量完全相同。它们两者一起被称为类似于variables of static duration的东西。

关于文档链接,抱歉,我不知道有哪些。

但是,如果您可以自己进行实验,谁需要文档呢?对于这个最好的工具是objdump,它可以反汇编程序,转储数据部分等等!

希望这回答了你的问题...


比起 objdump,有更简单的方法,只需使用 -S 而不是 -c 来生成汇编代码。 - Jens Gustedt
@JensGustedt:承认。我只是习惯于“objdump”的输出。 - rodrigo
它可能也不需要使用memcpy,而是直接将"hello"存储为堆栈单词。 - obataku

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接