字符串字面值: 它们去哪里?

我感兴趣的是字符串文字在哪里分配/存储。

我确实发现了一个有趣的答案,给你说:

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

但是,它与 C + + 有关,更不用说它说不用麻烦了。

我很烦人

所以我的问题是字符串字面值保存在哪里以及如何保存?为什么我不能试着改变它?实现是否因平台而异?有人愿意解释一下“聪明的把戏”吗

95221 次浏览

一种常见的技术是将字符串文字放在“ read-only-data”部分中,该部分以只读形式映射到进程空间(这就是为什么不能更改它)。

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

与其想方设法让字符串文字变得可变(它将高度依赖于您的平台,并且可能随着时间的推移而变化) ,不如使用数组:

char foo[] = "...";

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

这取决于你的 可执行文件格式。考虑这个问题的一种方法是,如果您是程序集编程,可以将字符串文字放在程序集程序的数据段中。您的 C 编译器可以做类似的事情,但这完全取决于您的二进制文件是为哪个系统编译的。

这个问题没有唯一的答案。C 和 c + + 标准只是说字符串文字具有静态存储时间,任何修改它们的尝试都会产生未定义行为,具有相同内容的多个字符串文字可能共享相同的存储,也可能不共享相同的存储。

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

确定细节也会因平台的不同而有所不同——大多数情况下可能包括能够告诉您将其放置在何处的工具。如果你想要的话,有些甚至可以让你控制像这样的细节(例如 gnu ld 允许你提供一个脚本来告诉它如何对数据、代码等进行分组)

Gcc 创建了一个 .rodata节,它在地址空间中映射到“某处”,并标记为只读,

VisualC + + (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

仅供参考,只是备份其他答案:

标准: ISO/IEC14882:2003表示:

2.13字符串字面值

  1. [ ... ]普通字符串文字的类型为“ array of n const char”和 静态存储持续时间(3.7)

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

字符串常常被分配给只读内存,这使得它们不可变。然而,在一些编译器中,修改是可能的,通过一个“聪明的技巧”。.聪明的做法是“使用指向内存的字符指针”。.记住一些编译器,可能不允许这样。.这是小样

char *tabHeader = "Sound";
*tabHeader = 'L';
printf("%s\n",tabHeader); // Displays "Lound"

为什么我不能试着改变它?

因为这是未定义行为:

声明

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

定义“普通”字符数组对象 st,它们的元素用字符串文字初始化。

此声明与

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

数组的内容是可修改的

char *p = "abc";

定义 p的类型为“指向 char 的指针”,并将其初始化为指向长度为4的类型为“ array of const char”的对象,该对象的元素用字符串文字初始化。如果尝试使用 p修改数组的内容,则该行为是未定义的。

他们去哪儿了?

GCC 4.8 x86-64 ELF Ubuntu 14.04:

  • 堆栈
  • 返回文章页面
    • 目标文件的 .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 中的 Segfault。

如果我们对 char[]也这样做:

 char s[] = "abc";

我们得到:

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

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

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

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

其中 -s强制 objdump显示所有部分的完整内容,main.o是目标文件,-B 1强制 grep在匹配之前也打印一行(这样你就可以看到部分名称) ,而 str是你正在搜索的字符串文本。

使用 Windows 计算机上的 gcc,以及在 main中声明的一个变量,如

char *c = "whatever";

跑步

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

报税表

Contents of section .rdata:
0000 77686174 65766572 00000000           whatever....