You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用右值引用构造string仍会引发内存拷贝吗?

关于std::move构造std::string的内存疑问

测试代码

#include <iostream>
#include <string>
#include <string_view>
using namespace std;

int main() {
    string a = "abcdef";
    cout << (const int*)a.c_str() << endl;//0x64fe20
    string_view av{ a };
    cout << av << endl;//abcdef

    string b = std::move(a);
    cout << (const int*)b.c_str() << endl;//0x64fdf0
    cout << av << endl;// bcdef
}

可能的输出

0x64fe20
abcdef
0x64fdf0
 bcdef

问题描述

使用std::move(a)构造string b的核心目的是避免内存拷贝,但测试发现:a与b的内部字符串缓冲区地址不同,且原本指向a的string_view输出异常(变为" bcdef")。由此疑惑是否发生了至少部分内存拷贝?C未保证缓冲区地址相同,但如何实现无拷贝的移动构造?测试环境为VS2022及GCC(版本遗忘),采用C20标准。


解答

  1. 缓冲区地址不同的原因:小字符串优化(SSO)
    主流C++标准库的std::string都实现了小字符串优化——对于长度较短的字符串(不同编译器阈值不同,比如VS默认是15字节+终止符),会直接把字符存在std::string对象内部的缓冲区里,不会在堆上分配内存。这种情况下,移动构造std::string时,只能将原对象内部缓冲区的内容复制到新对象的内部缓冲区,因此两者的缓冲区地址必然不同。但这种拷贝是对象内部栈内存的拷贝,开销远小于堆内存拷贝,不属于我们要避免的“内存拷贝”范畴。

  2. string_view输出异常的原因
    string_view本质是保存原字符串的字符指针和长度,不会持有内存所有权。当原std::string是SSO状态时,移动构造会复制内部缓冲区的内容到新对象,同时原对象的内部缓冲区可能会被修改(比如VS的实现会把原字符串的第一个字符置空),导致string_view指向的原缓冲区内容被破坏,从而输出异常。如果是堆上的长字符串,移动后原对象的堆指针会被置空,此时string_view会指向悬空内存,行为是未定义的,风险更高。

  3. 实现无拷贝移动构造的场景
    当字符串长度超过SSO阈值时,std::string会在堆上分配内存存储字符。此时移动构造仅会将原对象的堆指针、长度、容量等成员变量转移给新对象,原对象的指针会被置空,完全不会拷贝堆上的字符数据,这就是真正的无拷贝移动。你可以用更长的字符串测试,比如:

    string a = string(20, 'x'); // 长度超过SSO阈值
    string b = std::move(a);
    cout << (const int*)a.c_str() << endl; // 原a的指针已置空,输出空地址或随机值
    cout << (const int*)b.c_str() << endl; // 地址与原a的初始地址相同
    

内容的提问来源于stack exchange,提问作者Zhang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 17:45:17