根据this stackoverflow回答关于 C++11/14 严格的别名规则:
如果程序试图通过以下类型之一以外的glvalue访问对象的存储值,则行为未定义:对象的动态类型,对象的动态类型的cv限定版本,a类型(如4.4中所定义)类型为对象的动态类型,类型是对应的动态类型的有符号或无符号类型,类型是对应于cv限定版本的有符号或无符号类型对象的动态类型,聚合或联合类型,包括其元素或非静态数据成员中的上述类型之一(递归地,包括子聚合或包含联合的元素或非静态数据成员),一种类型,它是对象的动态类型(可能是cv限定的)基类类型,char或unsigned char类型 .
我们可以使用其他类型的存储
(1) char *
(2) char(&)[N]
(3) std::array<char, N> &
不依赖 undefined behavior ?
constexpr uint64_t lil_endian = 0x65'6e'64'69'61'6e;
// a.k.a. Clockwise-Rotated Endian which allocates like
// char[8] = { n,a,i,d,n,e,\0,\0 }
const auto& arr = // std::array<char,8> &
reinterpret_cast<const std::array<char,8> &> (lil_endian);
const auto& carr = // char(&)[8]>
reinterpret_cast<const char(&)[8]> (lil_endian);
const auto* p = // char *
reinterpret_cast<const char *>(std::addressof(lil_endian));
int main()
{
const auto str1 = std::string(arr.crbegin()+2, arr.crend() );
const auto str2 = std::string(std::crbegin(carr)+2, std::crend(carr) );
const auto sv3r = std::string_view(p, 8);
const auto str3 = std::string(sv3r.crbegin()+2, sv3r.crend() );
auto lam = [](const auto& str) {
std::cout << str << '\n'
<< str.size() << '\n' << '\n' << std::hex;
for (const auto ch : str) {
std::cout << ch << " : " << static_cast<uint32_t>(ch) << '\n';
}
std::cout << '\n' << '\n' << std::dec;
};
lam(str1);
lam(str2);
lam(str3);
}
所有lambda调用产生:
endian
6
e : 65
n : 6e
d : 64
i : 69
a : 61
n : 6e
godbolt.org/g/cdDTAM(启用-fstrict-aliasing -Wstrict-aliasing = 2)
2 回答
char(&)[N]
case和std::array<char, N>
case都会导致未定义的行为 . 原因已经被你引用了 . 请注意,char(&)[N]
和std::array<char, N>
与char
的类型不同 .我不确定
char
情况,因为当前标准没有明确说明一个对象可以被视为一个窄字符数组(有关进一步的讨论,请参阅here) .无论如何,如果要访问对象的基础字节,请使用
std::memcpy
,正如标准在[basic.types]/2中明确指出的那样:严格的别名规则实际上非常简单:如果一个不是另一个的子对象,那么具有重叠生命周期的两个对象不能具有重叠的存储区域 . (*)
然而,允许读取对象的内存表示 . 对象的内存表示是
unsigned char
[basic.types] / 4的序列:因此在你的例子中:
lam(str1)
是UB(未定义行为);lam(str2)
是UB(一个数组,它的第一个元素不是pointer interconvertible);lam(str3)
未在标准中声明为UB,如果您将char
替换为unsigned char
,则可能会认为您正在读取对象表示 . (它也没有定义,但它应该适用于所有编译器)因此,使用第三种情况并将
p
的声明更改为const unsigned char*
应始终产生预期结果 . 对于其他两种情况,它可以使用这个简单的示例,但如果代码更复杂或者在更新的编译器版本上可能会中断 .(*)此规则有两个例外:一个用于具有共同初始化序列的工会成员;和一个
unsigned char
或std::byte
数组,为其他对象提供存储 .