dig*_*ale 15 c++ strict-aliasing
就在几周前,我了解到C++标准有一个严格的别名规则.基本上,我曾经问过一个关于移位的问题 - 而不是一次一个地移动每个字节,以最大化性能我想加载我的处理器的本机寄存器(分别为32或64位)并执行4/8的移位所有字节都在一条指令中.
这是我想避免的代码:
unsigned char buffer[] = { 0xab, 0xcd, 0xef, 0x46 };
for (int i = 0; i < 3; ++i)
{
buffer[i] <<= 4;
buffer[i] |= (buffer[i + 1] >> 4);
}
buffer[3] <<= 4;
Run Code Online (Sandbox Code Playgroud)
相反,我想使用类似的东西:
unsigned char buffer[] = { 0xab, 0xcd, 0xef, 0x46 };
unsigned int *p = (unsigned int*)buffer; // unsigned int is 32 bit on my platform
*p <<= 4;
Run Code Online (Sandbox Code Playgroud)
有人在评论中提到我提出的解决方案违反了C++别名规则(因为p是类型int*
,缓冲区是类型的char*
,我正在取消引用p来执行移位.(请忽略对齐和字节顺序的可能问题 - 我处理那个片段之外的那些人)我很惊讶地了解他严格别名规则,因为我经常对缓冲区中的数据进行操作,将其从一种类型转换为另一种类型并且从未出现任何问题.进一步调查显示我使用的编译器(MSVC) )没有强制执行严格的别名规则,因为我只是在业余时间开发gcc/g ++作为业余爱好,我可能还没有遇到过这个问题.
那么我问了一个关于严格别名规则和C++的Placement new运算符的问题:
IsoCpp.org提供有关放置新的常见问题解答,它们提供以下代码示例:
#include <new> // Must #include this to use "placement new"
#include "Fred.h" // Declaration of class Fred
void someCode()
{
char memory[sizeof(Fred)]; // Line #1
void* place = memory; // Line #2
Fred* f = new(place) Fred(); // Line #3 (see "DANGER" below)
// The pointers f and place will be equal
// ...
}
Run Code Online (Sandbox Code Playgroud)
这个例子很简单,但我问自己,"如果有人调用方法怎么办?f
例如f->talk()
?那时我们会解除引用f
,指向与memory
(类型)相同的内存位置char*
.我读过很多将变量类型豁免的地方替换为char*
任何类型的别名,但我的印象是它不是"双向街道" - 意思是,char*
可以别名(读/写)任何类型T
,但类型T
可以只能用于别名char*
,如果T
本身是char*
作为我打字这一点,这没有任何意义,我等我倾向于相信,声称我最初的(比特移位为例)违反了严格别名规则为false.
有人可以解释一下是正确的吗?我一直在努力去理解什么是合法的,什么不合法(尽管已经阅读过很多关于这个主题的网站和SO帖子)
谢谢
别名规则意味着语言只承诺指针解除引用有效(即不触发未定义的行为),如果:
D* d
指向了有效的D,则访问返回的指针static_cast<B*>(d)
是正常的,但访问返回的指针reinterpret_cast<B*>(d)
则不是.后者可能无法解释D内部B子对象的布局.char
.由于char是字节大小和字节对齐的,因此无法从一段char*
时间内读取数据而无法读取数据D*
.也就是说,标准中的其他规则(特别是关于数组布局和POD类型的规则)可以被读取为确保您可以使用指针并在POD类型和数组之间进行双向reinterpret_cast<T*>
别名(如果确保具有char数组)适当的大小和对齐方式.char
换句话说,这是合法的:
int* ia = new int[3];
char* pc = reinterpret_cast<char*>(ia);
// Possibly in some other function
int* pi = reinterpret_cast<int*>(pc);
Run Code Online (Sandbox Code Playgroud)
虽然这可能会调用未定义的行为:
char* some_buffer; size_t offset; // Possibly passed in as an argument
int* pi = reinterpret_cast<int*>(some_buffer + offset);
pi[2] = -5;
Run Code Online (Sandbox Code Playgroud)
即使我们可以确保缓冲区足够大以包含三个int
s,对齐可能也不正确.与所有未定义行为的实例一样,编译器可能会做任何事情.三种常见的事件可能是:
因为你总是希望像魔鬼本身一样避开UB,所以你需要一个char
具有正确大小和对齐的数组.获得它的最简单方法就是从"右"类型的数组开始(在本例中为int),然后通过char指针填充它,因为int是POD类型,所以这是允许的.
附录:使用放置后new
,您将能够调用该对象上的任何功能.如果构造正确并且由于上述原因而未调用UB,那么您已在所需位置成功创建了一个对象,因此任何调用都是正常的,即使该对象是非POD(例如,因为它具有虚函数).毕竟,任何分配器类都可能使用放置new
来创建它们获得的存储中的对象.请注意,如果您使用展示位置,这只是必须如此new
; 类型双关语的其他用法(例如,带有fread/fwrite的幼稚序列化)可能导致对象不完整或不正确,因为对象中的某些值需要专门处理以维护类不变量.
归档时间: |
|
查看次数: |
790 次 |
最近记录: |