iam*_*ind 6 c++ reference strict-aliasing void-pointers reinterpret-cast
假设在我的代码中,我必须存储一个void*
as数据成员,并class
在需要时将其强制转换回原始指针.为了测试它的可靠性,我写了一个测试程序(linux ubuntu 4.4.1 g ++ -04-Wall),我很震惊地看到了这个行为.
struct A
{
int i;
static int c;
A () : i(c++) { cout<<"A() : i("<<i<<")\n"; }
};
int A::c;
int main ()
{
void *p = new A[3]; // good behavior for A* p = new A[3];
cout<<"p->i = "<<((A*)p)->i<<endl;
((A*&)p)++;
cout<<"p->i = "<<((A*)p)->i<<endl;
((A*&)p)++;
cout<<"p->i = "<<((A*)p)->i<<endl;
}
Run Code Online (Sandbox Code Playgroud)
这只是一个测试程序; 实际上,对于我的情况,必须将任何指针存储为void*
,然后将其转换回实际指针(借助于template
).所以我们不要担心这一部分.上面代码的输出是,
p->i = 0
p->i = 0 // ?? why not 1
p->i = 1
Run Code Online (Sandbox Code Playgroud)
但是,如果您更改它void* p;
,A* p;
它会给出预期的行为.为什么?
另一个问题,我无法逃脱,(A*&)
否则我无法使用operator ++
; 但它也会发出警告,因为取消引用类型惩罚指针会破坏严格别名规则.有没有可行的方法来克服警告?
Jam*_*lis 11
好吧,正如编译器警告你的那样,你违反了严格的别名规则,这正式意味着结果是未定义的.
您可以通过使用增量的函数模板来消除严格的别名冲突:
template<typename T>
void advance_pointer_as(void*& p, int n = 1) {
T* p_a(static_cast<T*>(p));
p_a += n;
p = p_a;
}
Run Code Online (Sandbox Code Playgroud)
使用此函数模板,以下定义可main()
生成Ideone编译器上的预期结果(并且不发出警告):
int main()
{
void* p = new A[3];
std::cout << "p->i = " << static_cast<A*>(p)->i << std::endl;
advance_pointer_as<A>(p);
std::cout << "p->i = " << static_cast<A*>(p)->i << std::endl;
advance_pointer_as<A>(p);
std::cout << "p->i = " << static_cast<A*>(p)->i << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
您已经收到了正确的答案,这确实违反了严格的别名规则,导致代码无法预测的行为.我只是注意到你的问题的标题引用了"回送指向原始类的指针".实际上,您的代码与"返回"任何东西都没有任何关系.您的代码会将void *
指针占用的原始内存内容重新解释为A *
指针.这不是"退回".这是重新解释.甚至远不是同一件事.
说明差异的一个好方法是使用int
和float
示例.一个float
声明和初始化值
float f = 2.0;
Run Code Online (Sandbox Code Playgroud)
cab被转换(显式或隐式转换)到int
类型
int i = (int) f;
Run Code Online (Sandbox Code Playgroud)
与预期的结果
assert(i == 2);
Run Code Online (Sandbox Code Playgroud)
这确实是演员(转换).
或者,float
也可以将相同的值重新解释为int
值
int i = (int &) f;
Run Code Online (Sandbox Code Playgroud)
然而,在这种情况下,价值i
将是完全无意义的并且通常是不可预测的.我希望通过这些示例很容易看出转换和内存重新解释之间的区别.
重新解释正是您在代码中所做的.的(A *&) p
表达不是别的,只是通过指针所占据原始存储器的重新解释void *p
为类型的指针A *
.该语言不保证这两种指针类型具有相同的表示形式,甚至具有相同的大小.因此,期望代码中的可预测行为就像期望上面的(int &) f
表达式来评估2
.
真正"反击" void *
指针的正确方法是做(A *) p
,而不是(A *&) p
.结果(A *) p
确实是原始指针值,可以通过指针算法安全地操作.获取原始值作为左值的唯一正确方法是使用附加变量
A *pa = (A *) p;
...
pa++;
...
Run Code Online (Sandbox Code Playgroud)
并且没有合法的方法来创建一个"就地"的左值,正如你的(A *&) p
演员所尝试的那样.代码的行为就是一个例子.