6

假设在我的代码中,我必须将一个void*作为数据成员存储并在需要时将其类型转换回原始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;
}

这只是一个测试程序;实际上,对于我的情况,必须将任何指针存储为void*,然后将其转换回实际指针(在 的帮助下template)。所以我们不要担心那部分。上述代码的输出是,

p->i = 0
p->i = 0 // ?? why not 1
p->i = 1

但是,如果您将其更改void* p;A* p;它会给出预期的行为为什么 ?

另一个问题,我无法逃避,(A*&)否则我无法使用operator ++;但它也会发出警告,因为取消引用类型双关指针会破坏严格的别名规则。有什么体面的方法来克服警告吗?

4

3 回答 3

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

使用这个函数模板,下面的定义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;
}
于 2011-06-11T04:43:15.740 回答
6

您已经收到了正确的答案,并且确实是违反了严格的别名规则导致了代码的不可预测的行为。我只是注意到你的问题的标题提到了“将指针转换回原始类”。实际上,您的代码与“向后”转换任何内容没有任何关系。您的代码将指针占用的原始内存内容重新解释void *A *指针。这不是“回击”。这是重新诠释。甚至远非同一件事。

说明差异的一个好方法是使用intfloat示例。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为 type 的指针A *。该语言不保证这两种指针类型具有相同的表示形式,甚至是相同的大小。因此,期望代码中的可预测行为就像期望上面的(int &) f表达式计算为2.

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

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

并且没有合法的方法可以“就地”创建左值,就像您尝试通过(A *&) p演员一样。您的代码的行为就是一个例证。

于 2011-06-11T05:38:31.353 回答
2

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

至于推进指针,你应该能够通过这样做来推进它:

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

VS2003 到 VS2010 从来没有给你抱怨过,不确定 g++

于 2011-06-11T04:50:46.183 回答