san*_*orn 5 c++ strict-aliasing language-lawyer c++11
根据有关C++11/14严格别名规则的stackoverflow答案:
如果程序尝试通过以下类型之一以外的泛左值访问对象的存储值,则行为未定义:
对象的动态类型,
对象动态类型的 cv 限定版本,
- 与对象的动态类型类似的类型(如 4.4 中定义),
- 与对象的动态类型相对应的有符号或无符号类型,
- 与对象动态类型的 cv 限定版本相对应的有符号或无符号类型,
- 聚合或联合类型,其元素或非静态数据成员中包括上述类型之一(递归地包括子聚合或包含的联合的元素或非静态数据成员),
- 是对象动态类型的(可能是 cv 限定的)基类类型的类型,
- a
char或unsigned char类型。
我们可以使用以下方式访问其他类型的存储吗
(1)char *
(2)char(&)[N]
(3)std::array<char, N> &
不依赖于未定义的行为?
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);
}
Run Code Online (Sandbox Code Playgroud)
所有 lambda 调用都会产生:
endian
6
e : 65
n : 6e
d : 64
i : 69
a : 61
n : 6e
Run Code Online (Sandbox Code Playgroud)
godbolt.org/g/cdDTAM(启用 -fstrict-aliasing -Wstrict-aliasing=2 )
严格的别名规则实际上非常简单:如果一个对象不是另一个对象的子对象,则具有重叠生命周期的两个对象不能具有重叠的存储区域。(*)
然而,允许读取对象的内存表示。对象的内存表示是[basic.types]/4的序列unsigned char:
T 类型对象的对象表示
unsigned char是T 类型对象所占用的N 个对象的序列,其中 N 等于sizeof(T)。对象的值表示是保存类型 T 的值的一组位。
因此在你的例子中:
lam(str1)是UB(未定义行为);lam(str2)是 UB (数组及其第一个元素不能进行指针互换);lam(str3)标准中没有表述为 UB ,如果您替换char为unsigned charUB ,可能会认为您正在阅读对象表示。(它也没有定义,但它应该适用于所有编译器)p因此,使用第三种情况并更改to的声明const unsigned char*应该始终会产生预期的结果。对于其他两种情况,它可以使用这个简单的示例,但如果代码更复杂或使用较新的编译器版本,则可能会中断。
(*) 此规则有两个例外:一个是具有共同初始化序列的联合体成员;一个用于数组unsigned char或为其他对象std::byte提供存储。