C++ 为什么将指针回退到原始类时会出现奇怪的行为?

C++ 为什么将指针回退到原始类时会出现奇怪的行为?,c++,reference,void-pointers,reinterpret-cast,strict-aliasing,C++,Reference,Void Pointers,Reinterpret Cast,Strict Aliasing,假设在我的代码中,我必须将一个void*存储为数据成员,并在需要时将其类型转换回原始类指针。为了测试它的可靠性,我编写了一个测试程序(linux-ubuntu 4.4.1g++-04-Wall),看到这种行为我感到震惊 struct A { int i; static int c; A () : i(c++) { cout<<"A() : i("<<i<<")\n"; } }; int A::c; int main () { void *p

假设在我的代码中,我必须将一个
void*
存储为数据成员,并在需要时将其类型转换回原始
指针。为了测试它的可靠性,我编写了一个测试程序(linux-ubuntu 4.4.1g++-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;
}
但是,如果您更改
void*p
A*p它给出了为什么?


另一个问题,我不能逃脱
(A*&)
,否则我不能使用
操作符+
;但它也发出警告,如。有什么方法可以克服警告吗?

正如编译器警告您的那样,您违反了严格的别名规则,这正式意味着结果是未定义的

您可以通过为增量使用函数模板来消除严格的别名冲突:

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;
}

正如其他人所评论的,您的代码看起来应该可以工作。只有一次(在用C++编写代码的17年多时间里),我遇到了一些事情,我直接看代码和行为,就像在你的例子中一样,根本没有意义。我最终通过调试器运行代码并打开了一个反汇编窗口。我在VS2003编译器中发现了一个只能解释为bug的东西,因为它只缺少一条指令。只需在函数顶部重新排列局部变量(从错误开始大约30行),编译器就会将正确的指令放回。所以,尝试使用反汇编的调试器并跟踪内存/寄存器,看看它实际在做什么

至于推进指针,您应该能够通过执行以下操作来推进它:

p = (char*)p + sizeof( A );

VS2003到VS2010从不对此进行投诉,不确定g++

您已经收到了正确的答案,而且确实是违反了严格的别名规则,导致了代码的不可预测行为。我只想指出,你问题的标题提到了“回溯指向原始类的指针”。事实上,您的代码与“返回”无关。代码将
void*
指针占用的原始内存内容重新解释为
a*
指针。这不是“回头看”。这是重新解释。一点也不一样

说明差异的一个好方法是使用and
int
float
示例。一个
浮点值
声明并初始化为

float f = 2.0;
cab可以强制转换(显式或隐式转换)为
int
类型

int i = (int) f;
达到了预期的效果

assert(i == 2);
这确实是一个演员阵容(转换)

或者,相同的
float
值也可以重新解释为
int

int i = (int &) f;
但是,在这种情况下,
i
的值将完全没有意义,并且通常不可预测。我希望从这些例子中很容易看出转换和内存重新解释之间的区别

重新解释正是您在代码中所做的。
(A*&)p
表达式只是将指针
void*p
占用的原始内存重新解释为
A*
类型的指针。该语言不能保证这两种指针类型具有相同的表示形式,甚至大小相同。因此,从代码中期望可预测的行为就像期望上面的
(int&)f
表达式的计算结果为
2

真正“回退”您的
void*
指针的正确方法是执行
(A*)p
,而不是
(A*)p
(A*)p
的结果实际上是原始指针值,可以通过指针算法安全地进行操作。获得作为左值的原始值的唯一正确方法是使用附加变量

A *pa = (A *) p;
...
pa++;
...

而且,没有合法的方法来“就地”创建左值,正如您试图通过
(A*&)p
铸造的那样。你的代码的行为是一个例证。

使用G+4.5.1,CLAN3.0.1275,Visual C++ 2010 SP1,程序输出{ 0, 1, 2 }。您正在使用哪个编译器生成意外结果?@trutheality:No。您不能使用
void*
执行算术。实际上
p=sizeof(a)+(char*)p按预期工作,没有警告。使用g++4.3、4.4或4.5,当在
-O2
或更高级别编译时,此代码输出
0 1
;当在
-O1
或更低级别编译时,它输出
0 1 2
。显然平台也很重要;在Win32上,带有
-O4
的g++4.5.1产生了预期的{0,1,2}@Adam。它不会增加简单
++
操作的机器指令吗(或者我们必须依赖于进行优化)?一个缓慢、正确的程序远远优于一个快速的程序,不正确的程序。@iammilind除非您自己测量和比较,否则无法知道。尽管我有讽刺性的评论,我还是希望编译器即使在低优化级别也能内联该程序,因此我不希望它有显著的性能成本。您是否检查了启用最大优化标志的MSVC?因为我保留了
-O4
。正如@Adam在评论中所建议的,即使是
g++
也能正确地输出较低的优化。这是一个很好的解释。我不确定“没有合法的方法在适当的地方创建左值”,尽管:
(char&)p
相当于
*reinterpret\u cast(&p)
,这是有效的,不违反严格的别名,并且有效地“在适当的地方创建左值”。@James:我认为这意味着没有合法的方法在适当的地方创建带有
A*
类型的左值,这正是提问者所尝试的,因此也是安德烈所说的。类型
char
unsigned char
,以及与引用对象的有效类型兼容的任何其他类型,当然。我想再挑剔一次,我认为创建左值是有效的
int i = (int &) f;
A *pa = (A *) p;
...
pa++;
...