2010-04-07 84 views
130

我对字符串文字在哪里分配/存储感兴趣。字符串文字:他们去哪里?

我没有找到一个有趣的答案here,他说:

定义字符串内联实际上在程序本身嵌入的数据,并不能改变(一些编译器允许这个由智能把戏,不要打扰)。

但是,它必须与C++,更不用说它说不打扰。

我很烦。 = D

所以我的问题是我的字符串文字在哪里以及如何保存?为什么我不应该尝试改变它?实施是否因平台而异?有没有人关心“巧妙的把戏”?

+1

http://stackoverflow.com/questions/ 164194 | http://stackoverflow.com/questions/1704407 – 2015-06-05 10:50:12

回答

101

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

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

而是然后试图找出一个技巧,使字符串文字多变的(这将在很大程度上取决于你的平台上,并可能会随时间而改变),只需使用数组:

char foo[] = "..."; 

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

+4

是的,我想要有可变字符串时使用数组。我只是好奇而已。谢谢。 – 2010-04-07 06:07:05

+2

虽然使用数组作为可变字符串时,必须小心缓冲区溢出,但只要写入比字符串长度更长的字符串(例如'foo =“hello”'在这种情况下)就会导致意想不到的副作用...... (假设你没有用'new'或其他东西重新分配内存) – johnny 2011-09-26 17:52:09

+1

使用数组字符串进入堆栈还是其他地方? – 2016-12-26 11:32:47

4

这取决于您的executableformat。想一想的一种方法是,如果您是汇编程序设计,那么您可能会将字符串文字放入汇编程序的数据段中。你的C编译器就是这样做的,但这一切都取决于你编译的二进制系统。

43

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

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

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

+0

我发现字符串数据不太可能直接存储在.text段中。对于非常短的文字,我可以看到编译器生成代码,例如'movb $ 65,8(%esp); movb $ 66,9(%esp); movb $ 0,10(%esp)'为字符串'“AB”',但绝大多数时候,它将处于非代码段,如'.data'或'.rodata'等取决于目标是否支持只读段)。 – 2012-10-02 20:00:10

+0

如果字符串文字在程序的整个持续时间内都是有效的,即使在破坏静态对象的过程中,那么它是否有效将const引用返回给字符串?为什么这个程序显示运行时错误请参阅http://ideone.com/FTs1Ig – Destructor 2015-11-21 14:48:31

13

gcc使.rodata节被映射到地址空间的某个地方并被标记为只读,

Visual C++(cl.exe)为了同样的目的而制作了一个.rdata部分。

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

E.g.

>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-11-30 10:26:34

+0

@ user2284570,这是因为该部分不包含程序集。它包含数据。 – 2015-11-30 11:21:15

+1

只是为了获得更多可读的输出。我的意思是我想用反汇编内联字符串而不是这些部分的地址。 *(你知道'printf(“一些null终止静态字符串”);'而不是'printf(*地址);'在C)* – user2284570 2015-11-30 11:23:53

21

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

标准:ISO/IEC 14882:2003说:

2.13。字符串文字

  1. [...]一个普通的字符串文字的类型是“的n const char阵列”和 静态存储持续时间(3.7)

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

+1

有用的信息,但通知链接是用于C++,而问题被拼成[标签:C] – 2013-07-10 19:30:15

+1

在2.13中证实了#2。使用-Os选项(针对大小进行优化),gcc与.rodata中的字符串文字重叠。 – 2015-02-20 07:15:03

2

字符串字面经常分配给只读存储器,使他们不变。然而,在一些编译器中,通过“智能技巧”进行修改是可能的。聪明的技巧是通过“使用字符指针指向内存”。记住一些编译器,可能不允许这样..这里是演示

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

它为什么工作? – 2016-12-26 11:51:50

28

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

因为它是未定义的行为。从C99 N1256 draft6.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,其元素具有字符串文字被初始化。如果尝试使用p来修改数组的内容,则行为是未定义的。

他们去哪里?

GCC 4.8 x86-64 ELF Ubuntu 14。04:

  • char s[]:堆
  • char *s:对象文件
  • 其中对象文件的.text部被转储的相同段,这已经阅读和exec权限
    • .rodata部,但不写

计划:

#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),当然我们可以修改它。

0

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

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....