说我有这个简单的功能
void foo(string a, string b) {
std::string a_string = a + a;
// reassign a new string to a_string
a_string = b + b + b;
// more stuff
}
为分配新字符串而保留的内存是否a+a会立即释放?a_string
C++ 内存管理的新手。我还在纠结它。
据我所知,字符串只是复制到为 a_string 分配的内存中,因为它比释放内存和分配新内存更有效。如果为 (a+a) 分配的内存小于为 a_string 分配的大小,则会调整其大小。
String 为您管理内存。当您向字符串添加或删除数据时,您不必分配缓冲区空间。如果您添加的内容超出了当前分配的缓冲区的容量,则 string 将在幕后为您重新分配它。
std::string是一个资源管理器类。它拥有底层证券char*。delete一旦std::string调用对象的析构函数,即当对象本身超出范围时,分配的内存就会得到d。
重新分配时也会发生同样的事情std::string。如果旧内存对于新字符串来说太小并且可能被新的堆内存替换,那么旧内存将被释放。资源管理类,例如std::string有一个强有力的保证永远不会泄漏内存(假设符合标准的实现)。
因为您分配的是临时的,所以不需要进行复制(因此不需要重新分配)。相反,临时的内容b + b + b将我移动到字符串中。这意味着将临时的底层指针复制到现有的字符串对象中,并剥离该指针的所有权的临时。这意味着,临时不再拥有内存,因此delete在分配后直接调用其析构函数时将不再拥有它。这具有巨大的优势,只需要复制一个指针而不是memcpy完整的字符串。
具有该保证的其他资源管理类包括智能指针(std::unique_pointer, std::shared_pointer)和集合(例如std::vector, std::list, std::map...)。
在现代 C++ 中,很少需要手动进行这种内存管理。提到的资源管理类涵盖了大多数情况,并且几乎在所有时间都应该优先于手动内存管理,除非它确实有必要并且您确切地知道自己在做什么。
这取决于它是短字符串还是长字符串:
std::string a_string = a + a;
// reassign a new string to a_string
a_string = b + b + b;
首先,由于保证复制 elison (c++17) ,a+a 直接构造为 a_string 。这里没有释放。
然后,如果a_string足够短,则将其分配在堆栈上,而无需额外的堆分配。这种短字符串优化 (SSO) 由大多数编译器执行,但不是标准强制要求的。
如果 SSO 发生了,那么这不会释放 a_string 中的任何空间,而只是重用它。记忆无处可去:
a_string = b + b + b;
但是,如果 a_string 对于 SSO 来说太长,则此行会释放为该字符串分配的堆空间。
当检查的声明std::string时,可以清楚地看到内存的去向:
template< class CharT, class Traits = std::char_traits<CharT>, class Allocator = std::allocator<CharT> > class basic_string;
非 SSO 字符串的内存使用std::allocator. new该分配器使用和操作符分配和释放内存delete。通常他们在幕后使用 malloc/free。这就是 malloc 和 free 的工作方式。
通过运行很容易找出 SSO 字符串的大小
std::cout << std::string().capacity() << '\n';
对于 64 位英特尔 SSO 上的 clang 8.0.0 最多可用于 22 个字符的字符串,而对于 gcc 8.3 它只有 15 个字符。
不,字符串的工作方式有点像 C++ 中的向量,因为一旦在内存中保留了空间,它就不会被释放,除非明确告知这样做,或者它超过了它的最大容量。这是为了尽可能避免调整大小,因为这样做意味着分配一个新的字符数组,复制必要的值,并删除旧数组。通过维护保留的内存,删除一个字符不需要创建一个全新的数组,并且只需要在字符串不够大而无法包含您要放入其中的内容时才需要重新分配。希望有帮助!
请注意,您有很多分配,从通过值而不是通过 const 引用传递的参数开始。
那么a+a你无法真正避免。
然后是有趣的部分:
(b + b) + b为 2 个临时对象创建 2 个分配。
然后您使用a_string由临时替换的移动分配:无分配。