将字符串字面值分配给std::string

14

我知道下面的代码会创建一个字符数组并一直存在内存中直到程序结束:

char* str = "this is a string";

关于这个声明,它创建了一个字符的本地数组,并将在str超出作用域时被释放:

char str[] = "this is a string";

我感到好奇的是,如果我这样写会发生什么:

std::string str = "this is a string";

str应该将字符串复制到它自己的内存(本地),但是字符串本身会怎样?它的生命周期是整个程序还是在str超出作用域时被释放?


5
字符串字面值具有static存储期,因此它将始终存在于程序的整个生命周期中。 - The Paramagnetic Croissant
std::basic_string 不拥有字面值,因此它的生命周期不由 str 确定。 - edmz
4个回答

15

当您编写此代码时

std::string str = "this is a string";

C++应该找到一个接受const char* 参数的 std::string 构造函数,用它来创建一个临时对象,然后调用复制构造函数将该临时对象复制到str中,最后销毁临时对象。

但是,有一种优化可以让C++编译器跳过临时对象的构造和销毁,因此结果与以下代码相同:

std::string str("this is a string");

但是字符串字面量本身呢?它的生存期是整个程序还是在str超出作用域时被释放?

当以这种方式使用时,字符串字面量本身对于您的程序来说是不可访问的。通常,C++将其放置在与其他字符串字面量相同的段中,将其用于传递给std::string的构造函数,然后忘记它。优化器允许消除所有字符串字面量中的重复项,包括仅用于初始化其他对象的字符串。


7
据我所记,此处不允许调用operator=,因为这需要两边都有构造的对象。相反,使用=进行初始化将调用复制构造函数。 - Oberon
@Oberon 你是对的,这是一个复制构造函数。我已经修正了答案,非常感谢! - Sergey Kalinichenko

7

大多数操作系统会将程序内存分成几个部分:

  • 栈(Stack)
  • 堆(Heap)
  • 数据段(Data segment)
  • BSS 段(BSS segment)
  • 代码段(Code segment)

你已经了解了栈和堆,但其他部分呢?

代码段 保存所有以二进制形式表示的操作。

现在变得有趣起来了: 让我们看下面的代码:

int x;
class Y{  static int y;  };
int main(){
  printf("hello world");
  return 0;
}

程序在哪里分配了变量xy呢?它们既不是本地变量也不是动态分配的,那么分配在哪里呢? 数据段(Data segment)存储所有静态和全局变量,当程序被加载时,该段保留足够的字节来容纳所有静态和全局变量。如果变量是对象,则随着程序的运行会为所有变量(包括对象)分配足够的字节。在main函数执行之前,程序将调用每个全局对象的构造函数,在main完成后,它将按逆序调用构造函数时的顺序调用每个对象的析构函数。 BSS段(BSS segment)是数据段(Data segment)的子集,其中包含全局和静态指针,这些指针都被初始化为null。
因此,假设字符串字面值没有被优化掉,程序将其存储在数据段(Data segment)中。只要程序还在运行,它就一直存在。此外,如果它是一个字符串字面值,很可能你甚至可以在exe文件中看到它!将exe文件作为文本文件打开,沿途某个地方,你会清楚地看到这个字符串。
那么std::string str = "hello world";呢?
这是一个有趣的情况。str本身存储在堆栈上。实际的内部缓冲区存储在堆(heap)中,但用于赋值字符串的字符串字面值存储在数据段(Data segment)中,而使str的值变为hello world的代码存储在代码段(code segment)中。不用说,如果我们使用汇编语言编写程序,就需要亲自构建这个生态系统。

2
我会提出一个反问:你为什么在意呢?
C++标准规定了语言的行为,而在实现方面的第一个核心原则基本上被称为“as-if”规则:
§ 1.9程序执行
1/ 本国际标准中的语义描述定义了一个参数化的不确定性抽象机。本国际标准对符合要求的实现的结构没有任何要求。特别是,它们不需要复制或模拟抽象机的结构。相反,符合要求的实现需要模拟(只有)下面解释的抽象机的可观察行为。
在您的情况下:
std::string str = "this is a string";

有各种有效的情况:

  • 之后你不使用str?那么这整个代码部分可能会被完全省略
  • 之后你立即将'T'赋值给str[0]?那么两者可能会合并为std::string str = "This is a string";
  • ...

而且不能保证你的编译器会做什么。它可能取决于你使用的编译器,你使用的标准库实现,你正在编译的架构/操作系统甚至是传递给编译器的参数...

因此,如果您想知道在您的情况下,您将需要检查生成的机器代码。对于以下代码,请询问coliru

#include <string>

int main(int argc, char** argv) {
    std::string str = "this is a string";
}

Clang在IR中产生以下内容:

@.str = private unnamed_addr constant [17 x i8] c"this is a string\00", align 1

这将生成以下汇编代码:

.L.str:
    .asciz    "this is a string"
    .size .L.str, 17

所以,针对这些特定条件,"this is a string"将会保留原样存储在二进制文件中,并加载到只读存储器中。它将一直停留在进程的地址空间中,直到结束,操作系统可能会根据内存压力将其页面化或者不进行页面化。


0

你的一些初始陈述并不完全正确:

对于char *char []的例子,在这两种情况下,如果它在全局命名空间中声明,变量本身str将保持在作用域内并且可访问,直到程序结束。

如果它在函数或方法的作用域中声明,则在作用域仍然活动时可以访问。两者都是如此。

至于用于存储实际文字字符串的内存的实际情况,那是未指定的。特定的C++实现可以自由地管理运行时内存,以任何更方便的方式,只要结果符合C++标准。就C++而言,您没有访问str对象使用的内存,您只引用了str对象本身。

当然,您可以自由地获取一个本地的char *指针,指向str中的一个字符。但是,指针是否有效直接取决于底层对象的作用域。当相应的str对象超出作用域时,指针将不再有效,并且访问指针的内容将变成未定义的行为。

请注意,在str在全局命名空间的情况下,str的范围是程序的生命周期,因此这一点是无意义的。但是当str处于本地范围中并且它超出范围,使用指针将变为未定义的行为。底层内存发生了什么是不相关的。C++标准实际上并没有很好地定义底层实现中应该或不应该发生的内存操作,但它定义了什么是否是未定义的行为。
基于此,您可以自己推断出std::string情况的答案。原理相同,您正在访问std::string对象而不是底层内存。
但是请注意,除了作用域问题之外,一些(但不是全部)std::string对象的方法还被指定为使所有现有的直接指针和迭代器对其内容无效,因此这也影响直接char *std::string中某个字符是否保持有效。

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