memmove覆盖对象及其生命周期相关技术问题
memmove允许源地址与目标地址存在重叠区域。若将其用于trivially copyable类型,memmove复制的对象会被隐式创建,其生命周期随之启动(不存在未定义行为)。但针对memmove覆盖对象的场景,存在以下疑问,先看示例代码:
#include <array> #include <type_traits> #include <iostream> struct obj_t { int a, b, c, d; }; static_assert(std::is_trivially_copyable_v<obj_t> == true, "objects must be trivially copyable to be implicitly created by memmove"); int main() { std::array<obj_t, 8> objs{}; std::cout << "before:\n"; int i = 0; for (auto& obj : objs) { obj.a = i++; std::cout << obj.a << "\n"; } //move 4 objects from position 2 to position 1 in the array: obj_t* src = &objs[2]; obj_t* dest = &objs[1]; memmove(dest, src, 4 * sizeof(obj_t)); std::cout << "after:\n"; for (auto& obj : objs) std::cout << obj.a << "\n"; }
程序输出如下:
before: 0 1 2 3 4 5 6 7 after: 0 2 3 4 5 5 6 7
数组中位置1至4的结构体被覆盖(包含重叠区域与非重叠区域的部分结构体)。
问题
- 被memmove覆盖的对象,其生命周期会如何变化?在新对象被隐式创建前,它们的生命周期是否会正常结束(此处是否存在未定义行为)?
- 对于源地址处遗留的重复副本(如位置5的结构体),旧副本是否仍属于活跃对象?
解答
问题1:被覆盖对象的生命周期与未定义行为
当用memmove覆盖trivially copyable类型的对象时,旧对象的生命周期会在新对象被隐式创建的那一刻终止,不存在“新对象创建前旧对象还活跃”的阶段,因此这里没有未定义行为。
C++标准规定,对于可平凡复制类型,当你通过字节复制(比如memmove)将数据写入某个存储位置时,若该位置原本有同类型对象,字节复制的行为会直接隐式创建新对象并覆盖旧对象——旧对象的生命周期会被新对象的创建自动终结,不需要显式销毁(因为可平凡复制类型的销毁不需要执行特殊逻辑)。
在示例中,objs[1]到objs[4]这些旧对象,在memmove执行过程中被字节覆盖时,对应的新对象会被隐式创建,旧对象的生命周期同步结束,全程符合标准要求,没有未定义行为。
问题2:源地址遗留副本的活跃性
源地址中被复制但未被覆盖的对象(比如示例中的objs[5]),其生命周期依然保持活跃。
memmove只是读取源地址的字节数据进行复制,这个操作不会影响源对象的生命周期——只要源对象所在的存储位置没有被覆盖,也没有被显式销毁,它就还是活跃的对象。示例中objs[5]的字节数据被复制到了objs[4],但objs[5]自身的存储位置没有被修改,所以它仍然是原来的那个活跃对象,后续对它的访问依然合法。
内容的提问来源于stack exchange,提问作者greenlagoon

