String literals: Where do they go?
我对字符串文本的分配/存储位置感兴趣。
我在这里找到了一个有趣的答案,说:
Defining a string inline actually embeds the data in the program itself and cannot be changed (some compilers allow this by a smart trick, don't bother).
但是,它与C++有关,更不用说它不麻烦了。
我很烦。= D
所以我的问题是,我的字符串在哪里,如何保存?为什么我不尝试改变它?实施是否因平台而异?有人愿意详细阐述"聪明的把戏"吗?
一种常见的技术是将字符串文本放入"只读数据"部分,该部分以只读方式映射到进程空间(这就是您不能更改它的原因)。
它确实因平台而异。例如,较简单的芯片架构可能不支持只读内存段,因此数据段是可写的。
相反,尝试找出一个使字符串文本可更改的技巧(它将高度依赖于您的平台,并可能随着时间而更改),只需使用数组:
1 | char foo[] ="..."; |
编译器将安排从文本初始化数组,您可以修改数组。
对此没有任何答案。C和C++标准只是说字符串文本具有静态存储持续时间,任何修改它们的尝试都会给出未定义的行为,并且具有相同内容的多个字符串文字可能共享或可能不共享相同的存储。
根据您正在为其编写的系统以及它使用的可执行文件格式的功能,它们可能与程序代码一起存储在文本段中,或者它们可能具有用于初始化数据的单独段。
根据平台的不同,确定细节也会有所不同——最有可能的是包括可以告诉您将其放置在何处的工具。如果您需要的话,有些甚至可以让您控制这样的细节(例如,gnu ld允许您提供一个脚本来告诉它如何对数据、代码等进行分组)。
为什么我不尝试改变它?
因为它是未定义的行为。引用自C99 N1256草案6.7.8/32"初始化":
EXAMPLE 8: The declaration
1 char s[] ="abc", t[3] ="abc";defines"plain" char array objects
s andt whose elements are initialized with character string literals.This declaration is identical to
1
2 char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };The contents of the arrays are modifiable. On the other hand, the declaration
1 char *p ="abc";defines
p with type"pointer to char" and initializes it to point to an object with type"array of char" with length 4 whose elements are initialized with a character string literal. If an attempt is made to usep to modify the contents of the array, the behavior is undefined.
他们去哪里?
GCC 4.8 x86-64 ELF Ubuntu 14.04:
char s[] :叠加char *s :- 对象文件的
.rodata 节 - 对象文件的
.text 节被转储的同一段,它具有读取和执行权限,但不具有写入权限。
- 对象文件的
程序:
1 2 3 4 5 6 7 8 |
编译和反编译:
1 2 | gcc -ggdb -std=c99 -c main.c objdump -Sr main.o |
输出包含:
1 2 3 4 | char *s ="abc"; 8: 48 c7 45 f8 00 00 00 movq $0x0,-0x8(%rbp) f: 00 c: R_X86_64_32S .rodata |
所以字符串存储在
然后:
1 | readelf -l a.out |
包含(简化):
1 2 3 4 5 6 7 8 9 10 | 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 |
这意味着默认链接器脚本将
如果我们对
1 | char s[] ="abc"; |
我们得到:
1 | 17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp) |
所以它被存储在栈中(相对于
仅供参考,只需备份其他答案:
标准:ISO/IEC 14882:2003规定:
2.13. String literals
[...]An ordinary string literal has type"array of
n const char " and
static storage duration (3.7)Whether all string literals are distinct (that is, are stored in
nonoverlapping objects) is
implementation- defined. The effect of
attempting to modify a string literal
is undefined.
gcc生成一个
VisualC++(EDCOX1,1)是为了同样的目的而制作的EDCOX1×2的部分。
您可以查看
例如。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | >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 |
它取决于可执行文件的格式。考虑到这一点的一种方法是,如果您正在进行汇编编程,那么您可以将字符串文本放入汇编程序的数据段中。您的C编译器执行类似的操作,但这完全取决于您的二进制文件是为哪个系统编译的。
字符串文本经常分配给只读内存,使其不可变。但是,在某些编译器中,修改是可以通过"智能技巧"实现的,而智能技巧是通过"使用指向内存的字符指针"实现的。记住,有些编译器可能不允许这样做。下面是演示
1 2 3 4 |
由于这可能不同于编译器,因此最好的方法是筛选搜索字符串文字的对象转储:
1 | objdump -s main.o | grep -B 1 str |
当
在windows机器上使用gcc,在
1 | char *c ="whatever"; |
运行
1 | objdump -s main.o | grep -B 1 whatever |
收益率
1 2 | Contents of section .rdata: 0000 77686174 65766572 00000000 whatever.... |