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

Chr*_*per 155 c memory string-literals

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

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

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

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

我很烦.= d

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

R S*_*hko 122

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

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

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

char foo[] = "...";
Run Code Online (Sandbox Code Playgroud)

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

  • 是的,当我想拥有可变字符串时,我使用数组.我只是好奇而已.谢谢. (5认同)
  • 但是,在为可变字符串使用数组时,你必须要小心缓冲区溢出 - 只需编写一个比数组长度更长的字符串(例如`foo ="hello"`)就会导致意想不到的副作用......(假设你没有用"new"或其他东西重新分配内存 (2认同)
  • 当使用数组字符串进入堆栈或其他地方时? (2认同)

Jer*_*fin 50

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

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

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

  • @Carlitos_30:作为基于堆栈的局部变量,它们仍然需要从某些内容进行初始化以保存正确的内容。因此,如果您想要一个具有正确内容的局部变量,您可以使用 `char foo[] = "whatever";`,并且您将获得一个 `char` 的局部数组——它通常是从某个地方的实际字符串文字初始化的。 (2认同)

Cir*_*四事件 43

我为什么不试着改变呢?

因为它是未定义的行为.C99 N1256草案6.7.8 / 32"初始化":

例8:声明

char s[] = "abc", t[3] = "abc";
Run Code Online (Sandbox Code Playgroud)

定义"plain"char数组对象s,t其元素使用字符串文字初始化.

该声明与之相同

char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };
Run Code Online (Sandbox Code Playgroud)

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

char *p = "abc";
Run Code Online (Sandbox Code Playgroud)

p使用"指向char的指针"类型定义并将其初始化为指向类型为"array of char"且长度为4的对象,其元素使用字符串文字初始化.如果尝试使用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;
}
Run Code Online (Sandbox Code Playgroud)

编译和反编译:

gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o
Run Code Online (Sandbox Code Playgroud)

输出包含:

 char *s = "abc";
8:  48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
f:  00 
        c: R_X86_64_32S .rodata
Run Code Online (Sandbox Code Playgroud)

所以字符串存储在该.rodata部分中.

然后:

readelf -l a.out
Run Code Online (Sandbox Code Playgroud)

包含(简化):

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
Run Code Online (Sandbox Code Playgroud)

这意味着缺省链接脚本转储既.text.rodata成可以执行,但是不能修改(一个段Flags = R E).尝试修改此类段会导致Linux中出现段错误.

如果我们这样做char[]:

 char s[] = "abc";
Run Code Online (Sandbox Code Playgroud)

我们获得:

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

所以它存储在堆栈中(相对于%rbp),我们当然可以修改它.


Jus*_*cle 22

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

标准:ISO/IEC 14882:2003说:

2.13.字符串文字

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

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

  • 有用的信息,但注意链接适用于C ++,而问题与[tag:C]纠缠不清 (2认同)
  • 确认 2.13 中的#2。使用 -Os 选项(优化大小),gcc 会重叠 .rodata 中的字符串文字。 (2认同)

Ale*_*ski 14

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
Run Code Online (Sandbox Code Playgroud)


Par*_*ppa 5

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