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

Posted

技术标签:

【中文标题】字符串文字:它们去哪儿了?【英文标题】:String literals: Where do they go? 【发布时间】:2011-02-05 02:32:28 【问题描述】:

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

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

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

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

我很烦。 =D

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

【问题讨论】:

【参考方案1】:

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

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

与其想办法让字符串字面量可变(它将高度依赖于您的平台并且可能随时间而变化),不如使用数组:

char foo[] = "...";

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

【讨论】:

是的,当我想要可变字符串时,我会使用数组。我只是好奇而已。谢谢。 在将数组用于可变字符串时,您必须小心缓冲区溢出 - 简单地写入比数组长度更长的字符串(例如,在这种情况下为 foo = "hello")可能会导致意外的副作用...(假设您没有使用 new 或其他东西重新分配内存) 当使用数组字符串时是否会进入堆栈或其他地方? 我们不能像@ChrisCooper 所说的那样使用char *p = "abc"; 来制作可变字符串【参考方案2】:

我为什么不尝试修改它?

因为它是未定义的行为。引用C99 N1256 draft6.7.8/32“初始化”

示例 8:声明

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

定义“普通”字符数组对象st,其元素使用字符串字面量进行初始化。

此声明与

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

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

char *p = "abc";

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

【讨论】:

【参考方案3】:

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

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

确定细节也会因平台而异 - 很可能包括可以告诉您将其放置在何处的工具。如果你愿意,有些甚至可以让你控制这样的细节(例如,gnu ld 允许你提供一个脚本来告诉它如何对数据、代码等进行分组)

【讨论】:

我发现字符串数据不太可能直接存储在 .text 段中。对于非常短的文字,我可以看到编译器为字符串"AB" 生成诸如movb $65, 8(%esp); movb $66, 9(%esp); movb $0, 10(%esp) 之类的代码,但绝大多数情况下,它将位于诸如.data.rodata 之类的非代码段中或类似的(取决于目标是否支持只读段)。 如果字符串文字在程序的整个过程中都有效,即使在静态对象的销毁期间,那么返回对字符串文字的 const 引用是否有效?为什么这个程序显示运行时错误见ideone.com/FTs1Ig @AdamRosenfield:如果你觉得无聊,你可能想看看(例如)遗留的 UNIX a.out 格式(例如,freebsd.org/cgi/…)。您应该很快注意到的一件事是它只支持一个始终可写的数据段。因此,如果您想要只读字符串文字,基本上它们可以去的唯一地方就是文本段(是的,当时链接器经常这样做)。【参考方案4】:

仅供参考,仅支持其他答案:

标准:ISO/IEC 14882:2003 说:

2.13。字符串字面量

    [...]一个普通的字符串字面量类型为“n const char的数组”并且 静态存储时长 (3.7)

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

【讨论】:

有用的信息,但注意链接适用于 C++,而问题与c 相关联 在 2.13 中确认 #2。使用 -Os 选项(优化大小),gcc 与 .rodata 中的字符串文字重叠。【参考方案5】:

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

Visual C++ (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

【讨论】:

我看不到如何使用 objdump 反汇编 rdata 部分。 @user2284570,这是因为该部分不包含程序集。它包含数据。 只是为了获得更具可读性的输出。我的意思是我想用反汇编而不是这些部分的地址来内联字符串。 (你知道的下摆printf("some null terminated static string");instead of printf(*address);in C)【参考方案6】:

这取决于您的executable 的format。一种思考方式是,如果您进行汇编编程,您可能会将字符串文字放在汇编程序的数据段中。您的 C 编译器会执行类似的操作,但这完全取决于您正在为哪个系统编译二进制文件。

【讨论】:

【参考方案7】:

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

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

【讨论】:

【参考方案8】:

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

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

【讨论】:

以上是关于字符串文字:它们去哪儿了?的主要内容,如果未能解决你的问题,请参考以下文章

[去哪儿网]首个重复字符

codeigniter:实体去哪儿了?

Java永久代去哪儿了

Tesseract:简单的Java光学字符识别

关于 Android 测量文字宽度的方法

Python网络爬虫之Selenium使用代理登陆:爬取去哪儿网站