Reinterpret_cast, char*和未定义行为
reinterpret_cast, char*, and undefined behavior
在什么情况下,reinterpret_cast
和char*
(或char[N]
)是未定义的行为,何时是已定义的行为?我应该用什么经验法则来回答这个问题?
从这个问题中我们得知,以下是未定义的行为:
alignas(int) char data[sizeof(int)];
int *myInt = new (data) int; // OK
*myInt = 34; // OK
int i = *reinterpret_cast<int*>(data); // <== UB! have to use std::launder
但是在什么时候我们可以在char
数组上做reinterpret_cast
并使其不是未定义的行为?下面是一些简单的例子:
没有
new
,只有reinterpret_cast
:alignas(int) char data[sizeof(int)]; *reinterpret_cast<int*>(data) = 42; // is the first cast write UB? int i = *reinterpret_cast<int*>(data); // how about a read? *reinterpret_cast<int*>(data) = 4; // how about the second write? int j = *reinterpret_cast<int*>(data); // or the second read?
int
的生存期何时开始?是否附有data
的申报单?如果是,data
的生存期什么时候结束?如果
data
是一个指针呢?char* data_ptr = new char[sizeof(int)]; *reinterpret_cast<int*>(data_ptr) = 4; // is this UB? int i = *reinterpret_cast<int*>(data_ptr); // how about the read?
如果我只是在网上接收结构体,并希望根据第一个字节有条件地转换它们,该怎么办?
// bunch of handle functions that do stuff with the members of these types void handle(MsgType1 const& ); void handle(MsgTypeF const& ); char buffer[100]; ::recv(some_socket, buffer, 100) switch (buffer[0]) { case '1': handle(*reinterpret_cast<MsgType1*>(buffer)); // is this UB? break; case 'F': handle(*reinterpret_cast<MsgTypeF*>(buffer)); break; // ... }
这些情况中是否有UB?都是吗?从c++ 11到c++ 1z,这个问题的答案会改变吗?
这里有两个规则:
-
[基本。Lval]/8,也就是严格混叠规则:简单地说,你不能通过指向错误类型的指针/引用来访问对象。
-
[基地。Life]/8:简单地说,如果你为不同类型的对象重用存储空间,你不能在不清洗指针的情况下使用指向旧对象的指针。
这些规则是区分"内存位置"的重要部分。或"存储区域";和"一个对象"
你所有的代码示例都陷入了同样的问题:它们不是你强制转换的对象:
alignas(int) char data[sizeof(int)];
创建一个类型为char[sizeof(int)]
的对象。该对象是而不是和int
。因此,您可能无法像访问它一样访问它。不管它是读还是写;你还是激怒了UB。
char* data_ptr = new char[sizeof(int)];
还创建了一个类型为char[sizeof(int)]
的对象。
char buffer[100];
创建一个类型为char[100]
的对象。该对象既不是MsgType1
也不是MsgTypeF
。所以你不能像访问它一样访问它。
请注意,这里的UB是当您作为Msg*
类型之一访问缓冲区时,而不是当您检查第一个字节时。如果所有的Msg*
类型都是可复制的,那么读取第一个字节,然后将缓冲区复制到适当类型的对象中是完全可以接受的。
switch (buffer[0]) {
case '1':
{
MsgType1 msg;
memcpy(&msg, buffer, sizeof(MsgType1));
handle(msg);
}
break;
case 'F':
{
MsgTypeF msg;
memcpy(&msg, buffer, sizeof(MsgTypeF));
handle(msg);
}
break;
// ...
}
请注意,我们讨论的是语言状态下的未定义行为。很有可能编译器对这些都很好。
这个问题的答案在c++ 11和c++ 1z之间有变化吗?
自c++ 11以来有一些重要的规则澄清(特别是[basic.life])。但是这些规则背后的意图并没有改变。
- 编译C++时未定义的引用
- 为什么从 char 转换为 std::byte 可能是未定义的行为?
- 从 std::string 到 std::array<char,size> 的 memcopy 额外数据是否是一种未定义的行为?
- 将结构 std::memcpy 转换为具有足够容量的 std::vector 是未定义的行为<char>吗?
- 包括"lvtocon.h",未定义对'operator<<(std::ostream&, char const*)的引用
- 未定义模板"std::__1::basic_istringstream<char, std::__1::char_traits<char>, std::__1::allocator&
- C++ 如何检查 char 变量是否未定义(未初始化)
- gtest - 未定义对"testing::InitGoogleTest(int*, char**)"的引用
- Qt5 对'QApplication::QApplication(int&, char**, int)'的未定义引用
- 使用 char16_t 类型作为 char[] 数组,并通过 reinterpret_cast<> 重新转换它。我的代码是否有未定义的行为?
- 未定义对“PerformChat(char*, char*, char*, char*, char*, char*)”的
- Arduino:未定义的对"I2CRW::readByte(unsigned char, unsigned char)"的引用
- 链接器错误(未定义的引用)与“静态 constexpr const char*”和完美转发
- 对'icu_56::UnicodeString::UnicodeString(signed char, unsigned short const*, int)' 的未定义引用
- gpg::MD5(char const*, int): 错误: 未定义对'MD5_Init'的引用
- libopencv_calib3d:未定义的对'std::__throw_out_of_range_fmt(char const*, ...)的引用@GLIBCXX_3.4.20'
- 对静态constexpr char[]的未定义引用
- Reinterpret_cast, char*和未定义行为
- C++未定义的引用...还有警告:已弃用从字符串常量到 'char*' 的转换
- 未定义对"boost::log_mt_posix::basic_attribute_set<char>::~basic_attribute_set()"的引用