只要进程处于活动状态,const char* 文字字符串是否就持久存在?

Leo*_*eon 12 c++ constants lifetime null-terminated constexpr

我有如下功能:

const char* get_message() {
    return "This is a constant message, will NOT change forever!";
};

const char* get_message2() {
    return "message2";
};
Run Code Online (Sandbox Code Playgroud)

我计划在我的应用程序的任何地方使用它们,即使在不同的线程中。

const char*我想知道这些字符串的生命周期,即在函数之外使用这些字符串是否安全get_message

我猜想硬编码const char*字符串将被编译到应用程序的代码段而不是数据段中,所以也许像上面那样使用它们是安全的?

Jam*_*nze 16

从标准给出答案,"message"是一个字符串文字,并且字符串文字具有静态生命周期,这意味着该对象(char const[N]包含字符的对象)具有整个程序的生命周期。(对于具有重要构造函数或析构函数的对象来说,情况要复杂一些)。因此指向它的指针在程序的生命周期内都有效。


Chr*_*ich 9

是的,这样做是安全的。你的假设是正确的。

  • @DrazenGrasovec 是的,在 PE 文件中它通常是“.rdata”段。但这是一个技术细节,在问题的背景下并没有真正的帮助(在我看来)。 (6认同)
  • @DrazenGrasovec 该标准没有谈到二进制文件中的代码段,这是一个实现细节。然而,它确实说字符串文字具有“静态”存储持续时间,因此无论实际字符串文字最终在哪里,您的代码都是安全的。 (3认同)

Chu*_*non 7

\n

const char*我想知道这些字符串的生命周期,即在函数之外使用这些字符串是否安全get_message

\n
\n

那么快速浏览一下标准。

\n
\n

评估字符串文字会产生具有静态存储持续时间的字符串文字对象,该对象从上面指定的给定字符初始化。未指定所有字符串文字是否不同(即存储在不重叠的对象中)以及字符串文字的连续计算是否产生相同或不同的对象。[注意:尝试修改字符串文字的效果未定义。\xe2\x80\x94结束注]

\n\xe2\x80\x94-ISO/IEC JTC1 SC22 WG21 N4860(第 5.13.5 节 [字符串文字])

\n
\n

所以是的。在函数计算字符串文字并将 a 返回const char*到字符串文字后,标准确保该字符串文字将被赋予静态存储持续时间

\n


Dra*_* G. 6

简短的答案是字符串文字“message2”将与进程一样存在于内存中,但在. 罗达塔部分(假设我们谈论 ELF 文件)。

我们返回指向字符串常量的指针,但正如我们稍后将看到的,没有在任何地方定义单独的内存来存储该指针,并且没有必要,因为字符串的地址是在代码中计算的,并在每次函数调用时const char *使用寄存器 $ rax返回叫。

但是让我们看看代码中gdb发生了什么

在此输入图像描述

我们在函数中放置断点,返回指向常量字符串的指针,我们看到汇编代码和流程图:

在此输入图像描述

代码通过以下指令获取该字符串:

0x000055555555514a <+8>:    lea    0xeb3(%rip),%rax        # 0x555555556004
Run Code Online (Sandbox Code Playgroud)

该指令的作用是计算“message2”的地址。我们在这里看到 PIC(位置无关代码)的含义。

“message2”字符串的地址不是硬编码为绝对地址,而是计算为相对地址,作为下一条指令地址 (0x555555555151 + 0xeb3) 的硬编码偏移 0xeb3 并放入寄存器rax中。

相对寻址(当前地址+/-偏移量)的目的意味着进程将始终获得“message2”的正确地址,无论它加载到内存中的何处。

所以在这里我们看到const char *你问的实际上并不存在于内存中,因为地址是“动态”计算并使用 $rax 返回的:

我们在 $rax 中有地址:

(gdb) i r $rax
rax   0x555555556004      93824992239620
Run Code Online (Sandbox Code Playgroud)

它保存“message2”的地址:

(gdb) x/s 0x555555556004
0x555555556004: "message2"
Run Code Online (Sandbox Code Playgroud)

现在让我们看看进程地址映射中的地址0x555555556004在哪里:

0x555555556000     0x555555557000     0x1000     0x2000  r--p   /home/drazen/proba/main
Run Code Online (Sandbox Code Playgroud)

因此,该部分不可执行且不可写,只是可读且私有(r--p),这是有道理的,因为这不是共享库。

当我们检查 readelf 时,它显示它位于 ELF 文件的 .rodata 部分:

drazen@HP-ProBook-640G1:~/proba$ readelf  -x .rodata main

Hex dump of section '.rodata':
0x00002000 01000200 6d657373 61676532 00       ....message2.
Run Code Online (Sandbox Code Playgroud)

所以答案是,这个字符串不会被硬编码在 ELF 文件的代码段.text中,而是只读数据段.rodata,但是,当内存中存在长进程时,它会存在。

只是为了添加一些小细节,这个常量字符串当然会通过引用(地址)返回到 main() 函数,但不是在堆栈上,而是在寄存器rax中:

(gdb) i r
rax   0x555555556004      93824992239620
rbx   0x0 
Run Code Online (Sandbox Code Playgroud)

希望能帮助到你!

  • @DrazenGrasovec 它放在哪里并不重要。标准保证静态生存期,因此编译器必须采取一些措施来确保静态生存期。不同的链接器和不同的体系结构有不同的段概念。我使用过的编译器将字符串文字放置在与代码相同的段中,甚至将其放置在与其他数据相同的段中(并且允许覆盖它!)。 (8认同)
  • 你无法仅从程序集来判断某些代码是否合法。UB 可以在一个编译器上为您提供一个程序集,并在另一个编译器上破坏一些东西。 (6认同)
  • 从技术上讲你是对的。但在问题的背景下,这并不重要。此外,您的答案是非常特定于平台的,不一定适用于所有平台。 (5认同)
  • 请不要发布代码/数据的图片。 (5认同)
  • 好吧,问题是,这个字符串是否被硬编码到代码段中,从技术上讲,它不是,因为代码段是指令所在的位置,并且这个常量字符串没有被编码为汇编指令的一部分,只是纯数据,所以它的位置罗数据部分。我还没有在不同的平台上尝试过这个,但我确信它也会被放置在rodata中。 (2认同)