一个const char*字面字符串在进程存活期间是否持久存在?

10
我有以下类似的功能:
const char* get_message() {
    return "This is a constant message, will NOT change forever!";
};

const char* get_message2() {
    return "message2";
};

我打算在我的应用程序中随处使用它们,即使在不同的线程中也是如此。
我想知道这些字符串的生命周期,也就是说,是否可以安全地在函数`get_message`之外使用这些`const char*`字符串。
我猜一个硬编码的`const char*`字符串将会被编译到应用程序的代码段而不是数据段中,所以也许可以安全地像上面那样使用它们?

12
字面字符串像这样的在整个程序的生命周期内存在。但请注意,const char * 也可以指向非字面字符串,而这些字符串的生命周期取决于它们所指向的内容。 - undefined
6
强制挑剔一下,字符串字面值的类型是const char [N],而不是const char * - undefined
3
我建议使用auto&而不是const char*,以避免不必要地丢失类型信息。这样做还可以使需要已知大小数组的函数更加满意,并且如果需要的话,您可以在返回的C字符串上使用基于范围的for循环。示例 - undefined
3
C++中的字符串简单解决方案:对于所有的字符串,使用std::string。这样就可以避免生命周期的问题。当然,也有例外情况(就像任何严格的规则一样),在某些情况下使用std::string_view可能是更好的选择,但如果只使用std::string,就不会出错。 - undefined
4
这就是“C”风格编码的不好之处,也是为什么C++引入了容器(如std::vectorstd::string)和智能指针(如std::unique_ptrstd::shared_ptr)。所有这些类型都有助于描述对象的生命周期。你的常量对象最好使用std::string_view来建模,并且可以使用std::string来更改消息。 - undefined
显示剩余6条评论
4个回答

14
从标准的角度来回答,"message"是一个字符串字面量,字符串字面量具有静态生命周期,这意味着对象(包含字符的char const[N])在整个程序的生命周期内都是有效的。(对于具有非平凡构造函数或析构函数的对象来说,情况会稍微复杂一些)。因此,指向它的指针在程序的整个生命周期内都是有效的。

我已经将char const[]编辑为char const[N],因为前者也是一种有效的类型(尽管在原始代码中没有使用),所以将其用作后者的简写可能会有点混淆。 - undefined

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

这并不完全正确,因为如果你将代码段视为文本,那么这个字符串常量将会被编译到ELF文件的.rodata段中。 - undefined
6
@DrazenGrasovec 是的,在PE文件中,通常会有一个名为.rdata的段。但在问题的背景下,这只是一个技术细节,对于问题的解答并没有太多帮助(依我之见)。 - undefined
2
@DrazenGrasovec 标准没有涉及二进制文件中的代码段,这只是一种实现细节。然而,它确实指出字符串字面量具有“静态”存储期,因此您的代码是安全的,无论实际的字符串字面量在哪里结束。 - undefined

6
我想知道这些字符串的生命周期,也就是说,在函数“get_message”之外使用这些“const char*”字符串是否安全。
快速查看一下标准。
评估字符串字面值将产生一个具有静态存储期的字符串字面值对象,根据上述规定从给定的字符初始化。所有字符串字面值是否不同(即存储在不重叠的对象中),以及对字符串字面值进行连续评估是否产生相同或不同的对象是未指定的。[注意:尝试修改字符串字面值的效果是未定义的。-注释]
所以是的。在函数评估字符串字面值并返回一个“const char*”给字符串字面值之后,标准确保该字符串字面值将具有静态存储期。

5
短答案是字符串字面量"message2"会在进程中存在于内存中,但是在.rodata段中(假设我们谈论的是ELF文件)。
我们返回指向字符串常量的指针,但是正如我们后面将看到的,没有单独定义存储这个const char *指针的内存,并且也没有必要,因为字符串的地址是在代码中计算并且每次函数调用时都使用寄存器$rax返回。
但是让我们看看在代码中gdb会发生什么。

enter image description here

我们在返回指向常量字符串的函数中设置了断点,然后我们查看了汇编代码和进程映射。

enter image description here

代码在以下指令中获取到这个字符串:
0x000055555555514a <+8>:    lea    0xeb3(%rip),%rax        # 0x555555556004

这个指令的作用是计算"message2"的地址。 我们在这里看到了PIC(位置无关代码)的意思。
"message2"字符串的地址不是硬编码为绝对地址, 而是计算为相对地址,即下一条指令地址(0x555555555151 + 0xeb3)的硬编码偏移量0xeb3,并放入寄存器rax中。
相对寻址(当前地址 +/- 偏移量)的目的是确保进程始终能够获取"message2"的正确地址,无论它在内存中的位置如何。
所以在这里我们看到,你所询问的"const char *"实际上并不存在于内存中,因为地址是即时计算并使用$rax返回的:
我们在$rax中有地址:
(gdb) i r $rax
rax   0x555555556004      93824992239620

而且它保存了"message2"的地址:
(gdb) x/s 0x555555556004
0x555555556004: "message2"

现在让我们看看进程地址映射中地址0x555555556004的位置在哪里:
0x555555556000     0x555555557000     0x1000     0x2000  r--p   /home/drazen/proba/main

所以这个部分是不可执行和不可写的,只能读取和私有(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.

所以答案是,这个字符串不会硬编码在ELF文件的代码段.text中,而是在只读数据段.rodata中,但是只要进程存在于内存中,它就会存在。
还有一个小细节要补充,这个常量字符串当然会通过引用(地址)返回给main()函数,但不是在堆栈上,而是在寄存器rax中。
(gdb) i r
rax   0x555555556004      93824992239620
rbx   0x0 

希望能有所帮助!

5
从技术角度来说,你是对的。但在问题的背景下,这并不重要。而且你的回答非常依赖于特定平台,并不一定适用于所有平台。 - undefined
5
请不要发布代码/数据的图片。 - undefined
6
仅凭汇编代码本身无法判断其合法性。未定义行为可能在一个编译器上生成正确的汇编代码,而在另一个编译器上出现错误。 - undefined
2
问题是,这个字符串是硬编码到代码段中的吗?从技术上讲,它并不是,因为代码段是指令所在的地方,而这个常量字符串并不是作为汇编指令的一部分编码的,它只是纯粹的数据,所以它被放置在rodata部分。我还没有在不同的平台上尝试过这个,但我相信它也会被放置在rodata中。 - undefined
8
@DrazenGrasovec 不管放在哪里都没关系。标准保证了静态生命周期,所以编译器必须采取措施确保静态生命周期。不同的链接器和不同的架构对段的概念有不同的理解。我曾经使用过将字符串字面量放在与代码相同的段中的编译器,甚至有些编译器将其放在与其他数据相同的段中(并允许覆盖它!)。 - undefined
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接