177

我对分配/存储字符串文字的位置感兴趣。

我确实在这里找到了一个有趣的答案,说:

定义一个字符串内联实际上将数据嵌入程序本身并且不能更改(一些编译器通过一个聪明的技巧允许这样做,不要打扰)。

但是,它与 C++ 有关,更不用说它说不要打扰。

我很烦。=D

所以我的问题是我的字符串文字在哪里以及如何保存?为什么我不应该尝试改变它?实现是否因平台而异?有人愿意详细说明“聪明的把戏”吗?

4

8 回答 8

137

一种常见的技术是将字符串文字放在“只读数据”部分中,该部分以只读方式映射到进程空间(这就是您无法更改它的原因)。

它确实因平台而异。例如,较简单的芯片架构可能不支持只读存储器段,因此数据段将是可写的。

而不是试图找出一个技巧来使字符串文字可变(它将高度依赖于您的平台并且可能随着时间而改变),只需使用数组:

char foo[] = "...";

编译器将安排数组从文字初始化,您可以修改数组。

于 2010-04-07T04:16:22.070 回答
64

为什么我不应该尝试改变它?

因为它是未定义的行为。引自C99 N1256 草案 6.7.8 /32“初始化”

例 8:声明

char s[] = "abc", t[3] = "abc";

定义“普通”字符数组对象st其元素用字符串字面量初始化。

此声明与

char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };

数组的内容是可修改的。另一方面,声明

char *p = "abc";

定义p类型为“pointer to char”并将其初始化为指向长度为 4 的类型为“const char 数组”的对象,其元素使用字符串字面量进行初始化。如果尝试使用p来修改数组的内容,则行为未定义。

他们去哪里?

GCC 4.8 x86-64 ELF Ubuntu 14.04:

  • char s[]: 堆
  • char *s
    • .rodata目标文件的部分
    • 目标文件部分被转储的同一段.text,具有读取和执行权限,但没有写入权限

程序:

#include <stdio.h>

int main() {
    char *s = "abc";
    printf("%s\n", s);
    return 0;
}

编译和反编译:

gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o

输出包含:

 char *s = "abc";
8:  48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
f:  00 
        c: R_X86_64_32S .rodata

所以字符串存储在.rodata节中。

然后:

readelf -l a.out

包含(简化):

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x0000000000000704 0x0000000000000704  R E    200000

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata

这意味着默认链接描述文件会同时转储.text和转储.rodata到可以执行但不能修改的段 ( Flags = R E)。尝试修改这样的段会导致 Linux 中的段错误。

如果我们对char[]:

 char s[] = "abc";

我们获得:

17:   c7 45 f0 61 62 63 00    movl   $0x636261,-0x10(%rbp)

所以它被存储在堆栈中(相对于%rbp),我们当然可以修改它。

于 2015-06-05T09:07:22.573 回答
56

对此没有一个答案。C 和 C++ 标准只是说字符串文字具有静态存储持续时间,任何修改它们的尝试都会产生未定义的行为,并且具有相同内容的多个字符串文字可能会或可能不会共享相同的存储空间。

根据您正在编写的系统以及它使用的可执行文件格式的功能,它们可能与程序代码一起存储在文本段中,或者它们可能具有用于初始化数据的单独段。

确定细节也会因平台而异——很可能包括可以告诉你它放在哪里的工具。如果你想要的话,有些甚至可以让你控制这样的细节(例如,gnu ld 允许你提供一个脚本来告诉它如何对数据、代码等进行分组)

于 2010-04-07T04:20:46.417 回答
23

仅供参考,只是支持其他答案:

标准:ISO/IEC 14882:2003说:

2.13. 字符串文字

  1. [...]一个普通的字符串字面量具有“array of n const char”类型和静态存储持续时间 (3.7)

  2. 是否所有字符串文字都是不同的(即,存储在不重叠的对象中)是实现定义的。尝试修改字符串文字的效果是未定义的。

于 2011-05-26T18:01:38.760 回答
15

gcc.rodata在地址空间中创建一个被映射“某处”并标记为只读的部分,

Visual C++ ( cl.exe).rdata为相同的目的创建了一个节。

您可以查看dumpbinobjdump(在 Linux 上)的输出以查看可执行文件的部分。

例如

>dumpbin vec1.exe
Microsoft (R) COFF/PE Dumper Version 8.00.50727.762
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file vec1.exe

File Type: EXECUTABLE IMAGE

  Summary

        4000 .data
        5000 .rdata  <-- here are strings and other read-only stuff.
       14000 .text
于 2010-04-07T04:25:27.537 回答
4

这取决于您的可执行文件的格式。一种思考方式是,如果您进行汇编编程,您可能会将字符串文字放在汇编程序的数据段中。您的 C 编译器会执行类似的操作,但这完全取决于您正在为哪个系统编译二进制文件。

于 2010-04-07T04:19:27.427 回答
3

字符串文字经常被分配到只读内存,使它们不可变。但是,在某些编译器中,可以通过“智能技巧”进行修改。智能技巧是“使用指向内存的字符指针”。记住一些编译器,可能不允许这样做。这是演示

char *tabHeader = "Sound";
*tabHeader = 'L';
printf("%s\n",tabHeader); // Displays "Lound"
于 2013-10-13T12:49:36.247 回答
0

由于这可能因编译器而异,因此最好的方法是过滤搜索字符串文字的对象转储:

objdump -s main.o | grep -B 1 str

其中-s强制objdump显示所有部分的全部内容,main.o是目标文件,-B 1强制grep在匹配之前打印一行(以便您可以看到部分名称),并且str是您正在搜索的字符串文字。

main在 Windows 机器上使用 gcc,并在like中声明一个变量

char *c = "whatever";

跑步

objdump -s main.o | grep -B 1 whatever

返回

Contents of section .rdata:
 0000 77686174 65766572 00000000           whatever....
于 2015-11-10T09:47:43.747 回答