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

时间:2010-04-07 04:11:10

标签: c memory string-literals

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

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

  

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

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

我很烦。 = d

所以我的问题是我的字符串文字保存在哪里以及如何保存?我为什么不试着改变呢?实施是否因平台而异?有没有人愿意详细阐述“聪明的伎俩?”

8 个答案:

答案 0 :(得分:117)

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

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

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

char foo[] = "...";

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

答案 1 :(得分:47)

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

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

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

答案 2 :(得分:38)

我为什么不试图改变它?

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

  

示例8:声明

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

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

     

此声明与

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

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

char *p = "abc";
     

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

他们去哪了?

GCC 4.8 x86-64 ELF Ubuntu 14.04:

  • char s[]:stack
  • 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),我们当然可以修改它。

答案 3 :(得分:21)

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

标准:ISO/IEC 14882:2003说:

  

<强> 2.13。字符串文字

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

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

  4.   

答案 4 :(得分: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

答案 5 :(得分:4)

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

答案 6 :(得分:2)

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

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

答案 7 :(得分: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....