字符串文字:它们去哪里了?


161

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

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

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

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

我很烦 = D

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

Answers:


125

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

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

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

char foo[] = "...";

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


5
是的,当我想使用可变字符串时,可以使用数组。我只是好奇而已。谢谢。
克里斯·库珀

2
但是,在将数组用于可变字符串时,您确实要小心缓冲区溢出-仅写比数组长度长的字符串(例如,foo = "hello"在这种情况下)会导致意外的副作用...(假设您不重新用new或其他方式分配内存)
约翰尼

2
使用数组字符串时是否会进入堆栈或其他位置?
Suraj Jain

我们不能char *p = "abc";像@ChrisCooper所说的那样使用可变字符串吗?
KPMG

52

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

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

根据平台的不同,确定细节的方式也有所不同-最有可能包括可以告诉您放置位置的工具。如果需要的话,有些甚至可以让您控制类似的细节(例如,gnu ld允许您提供一个脚本来告诉所有有关如何对数据,代码等进行分组的信息)。


1
我发现字符串数据不太可能直接存储在.text段中。对于非常短的文字,我可以看到编译器生成的代码,例如movb $65, 8(%esp); movb $66, 9(%esp); movb $0, 10(%esp)对于字符串"AB",但绝大多数的时候,这将是在非代码段如.data.rodata或类似(取决于是否target支持只读段)。
亚当·罗森菲尔德

如果字符串文字在程序的整个过程中都是有效的,即使在销毁静态对象期间也是如此,那么将const引用返回给字符串文字是否有效?为什么该程序显示运行时错误,请参见ideone.com/FTs1Ig
Destructor

@AdamRosenfield:如果您有时感到无聊,则可能需要查看(例如)传统的UNIX a.out格式(例如freebsd.org/cgi/…)。您应该很快注意到的一件事是,它仅支持一个始终可写的数据段。因此,如果您要使用只读字符串文字,那么实际上它们只能进入文本段(是的,当时链接程序经常这样做)。
杰里·科芬

48

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

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

例8:声明

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

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

此声明与

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

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

char *p = "abc";

定义p类型为“ char的指针”,并将其初始化为指向长度为4的“ 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),我们当然可以对其进行修改。


22

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

标准:ISO / IEC 14882:2003指出:

2.13。字符串文字

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

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


2
有用的信息,但注意链接适用于C ++,而问题与 c
纠缠不清

1
在2.13中确认为#2。使用-Os选项(针对大小进行优化),gcc将.rodata中的字符串文字重叠。
张鹏

14

gcc建立了一个.rodata节,该节被映射到地址空间中的“某处”,并标记为只读,

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

您可以查看dumpbin或的输出objdump(在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

1
我看不到如何用objdump分解rdata节。
user2284570 2015年

@ user2284570,这是因为该部分不包含程序集。它包含数据。
Alex Budovski

1
只是为了获得更具可读性的输出而已。我的意思是我想使字符串内联反汇编而不是这些部分的地址。(您知道的下摆printf("some null terminated static string");而不是printf(*address);C)
user2284570

4

这取决于格式您的可执行文件。一种考虑的方法是,如果您是汇编程序设计人员,则可以将字符串文字放入汇编程序的数据段中。您的C编译器会执行类似的操作,但是这完全取决于您要针对哪个系统编译二进制文件。


2

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

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

0

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

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

其中-s部队objdump,以显示所有部分的全部内容,main.o是目标文件,-B 1军队grep的比赛(这样你可以看到部分名称)之前还印一行,并str为字符串文字你的搜索。

在Windows计算机上使用gcc,并在mainlike中声明一个变量

char *c = "whatever";

跑步

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

退货

Contents of section .rdata:
 0000 77686174 65766572 00000000           whatever....
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.