【发布时间】:2016-09-12 14:19:04
【问题描述】:
就在几周前,我了解到 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;
相反,我想使用类似的东西:
unsigned char buffer[] = { 0xab, 0xcd, 0xef, 0x46 };
unsigned int *p = (unsigned int*)buffer; // unsigned int is 32 bit on my platform
*p <<= 4;
有人在评论中指出我提出的解决方案违反了 C++ 别名规则(因为 p 的类型为 int* 而缓冲区的类型为 char* 并且我取消引用 p 以执行移位。(请忽略可能的问题对齐和字节顺序——我处理这个 sn-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
// ...
}
这个例子很简单,但我在问自己,“如果有人在f 上调用一个方法——例如f->talk() 怎么办?那时我们将取消对f 的引用,它指向同一个内存位置为memory(char* 类型。我读过很多地方,char* 类型的变量可以豁免为任何类型的别名,但我的印象是它不是“两个-way street" -- 意思是,char* 可以别名(读/写)任何类型T,但类型T 只能用于别名char*,如果T 本身属于char*。在我输入此内容时,这对我没有任何意义,因此我倾向于相信我的初始(位移示例)违反严格别名规则的说法是错误的。
有人可以解释一下什么是正确的吗?我一直在努力理解什么是合法的,什么是不合法的(尽管阅读了许多关于该主题的网站和 SO 帖子)
谢谢
【问题讨论】:
-
如果调用
f的成员函数是未定义的行为,那会使放置新类型变得毫无用处,不是吗? -
"它指向与内存相同的内存位置(类型为 char*)" -
memory的类型不是char *。在第 2 行,数组衰减为一个指针,但这并不意味着memory是一个指针。而且,即使memory是一个指针,它指向的内存位置的类型也是char,而不是char *。 -
看来你的问题是
f->talk()是否可以;我认为删除所有序言(“那么”之前的东西)会改善这个问题
标签: c++ strict-aliasing