使用reinterpret_cast的C++未定义行为及memmove隐式创建对象的疑问
原代码的未定义行为
原代码中,char buf[sizeof(Data)]仅分配了足够容纳Data对象的内存,但**Data的生命周期并未启动**。后续通过reinterpret_cast将buff转为Data*并使用std::launder的操作属于未定义行为——因为此时内存中并没有存活的Data对象。
原代码如下:
#include <iostream> #include <cstring> struct Data{ }; void process(char * buff){ decltype(auto) d = std::launder(reinterpret_cast<Data *>(buff)); } int main() { char buf[sizeof(Data)]; process(buf); }
cppreference关于std::memmove的说明
std::memmove may be used to implicitly create objects in the destination buffer.
(翻译:std::memmove可用于在目标缓冲区中隐式创建对象)
修改后的代码
修改后的代码通过调用std::memmove(buff, buff, sizeof(Data))尝试触发对象的隐式创建,代码如下:
#include <iostream> #include <cstring> struct Data{ }; void process(char * const buff){ decltype(auto) d = std::launder(reinterpret_cast<Data *>(std::memmove(buff,buff,sizeof(Data)))); } int main() { char buf[sizeof(Data)]; process(buf); }
问题解答
1. memmove能否隐式启动Data的生命周期,消除未定义行为?
可以。按照C++标准的规定,std::memmove的这种自复制调用(把buff指向的内存复制到自身,且缓冲区大小刚好能容纳Data对象)会触发隐式对象创建。因为Data是平凡类型,满足隐式创建的所有条件:内存大小足够、对齐要求符合,而且原内存里只有char数组——char类型的对象生命周期可以被新创建的Data对象覆盖。这时候用reinterpret_cast配合std::launder获取Data*就是合法操作,原代码的未定义行为也就不存在了。
2. 这个Data对象的生命周期何时结束?
这个隐式创建的Data对象,生命周期会持续到它所在的内存区域被释放,或者被其他对象覆盖。在这个例子里,buf是main函数里的栈上数组,所以当main执行完、栈帧被销毁时,buf的内存被回收,Data对象的生命周期也就跟着结束了。如果后续有代码修改buf的内容(比如在main里process调用之后写buf),那也会提前结束这个Data对象的生命周期。
内容的提问来源于stack exchange,提问作者getsoubl

