代码之家  ›  专栏  ›  技术社区  ›  Chris Cooper

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

  •  142
  • Chris Cooper  · 技术社区  · 15 年前

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

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

    定义一个字符串inline实际上将数据嵌入到程序本身中,并且不能被更改(一些编译器通过一个巧妙的技巧来实现这一点,不用担心)。

    但是,它与C++有关,更不用说它不麻烦了。

    我很烦。= D

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

    8 回复  |  直到 7 年前
        1
  •  116
  •   R Samuel Klatchko    15 年前

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

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

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

    char foo[] = "...";
    

    编译器将安排从文本初始化数组,您可以修改数组。

        2
  •  47
  •   Jerry Coffin    15 年前

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

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

    根据平台的不同,确定细节也会有所不同——最有可能的是包括可以告诉您将其放置在何处的工具。如果您需要的话,有些甚至可以让您控制这样的细节(例如,gnu ld允许您提供一个脚本来告诉它如何对数据、代码等进行分组)。

        3
  •  38
  •   Ciro Santilli OurBigBook.com    7 年前

    为什么我不尝试改变它?

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

    例8:声明

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

    定义“普通”char数组对象 s t 其元素是用字符串文本初始化的。

    此声明与

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

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

    char *p = "abc";
    

    定义 p 使用类型“pointer to char”并初始化它以指向长度为4的“array of char”类型的对象,该对象的元素是用字符串文字初始化的。如果试图使用 若要修改数组的内容,行为未定义。

    他们去哪里?

    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
    

    所以字符串存储在 .罗达塔 第节。

    然后:

    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
    

    这意味着默认链接器脚本会同时转储 文本 .罗达塔 可执行但不可修改的段( Flags = R E )尝试修改这样的段会导致Linux中出现segfault。

    如果我们为 char[] :

     char s[] = "abc";
    

    我们获得:

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

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

        4
  •  21
  •   Grijesh Chauhan Anand Krishnan    11 年前

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

    标准: ISO/IEC 14882:2003 说:

    2.13。字符串常量

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

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

        5
  •  13
  •   Alex Budovski    15 年前

    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
    
        6
  •  4
  •   Parappa    15 年前

    这取决于 format 您的 executable . 考虑到这一点的一种方法是,如果您正在进行汇编编程,那么您可以将字符串文本放入汇编程序的数据段中。您的C编译器执行类似的操作,但这完全取决于您的二进制文件是为哪个系统编译的。

        7
  •  2
  •   Yu Hao    11 年前

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

    char *tabHeader = "Sound";
    *tabHeader = 'L';
    printf("%s\n",tabHeader); // Displays "Lound"
    
        8
  •  0
  •   mihai    9 年前

    由于这可能不同于编译器,因此最好的方法是筛选搜索字符串文字的对象转储:

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