C ++类实例化可以在运行时更改其大小

时间:2016-04-22 11:11:09

标签: c++ c++03 pointer-arithmetic c++98

我有一个非常特殊的情况......我继承了一些旧的C ++代码(前C ++ 11),这些代码很长,很复杂,一半用C语言编写,另一半用C语言编写 - 类心态(即:具有更多数据成员的类,没有太多的类方法,直接操纵数据成员......这个项目肯定需要一些重构,这就是我现在正在进行的工作)。但是这些代码暴露了我发现令人费解的一些问题。

首先让我们采取以下非常简单的方法(无错误):

#include <iostream>

static int c = 0;

struct Bar
{
    Bar() : base_id(++c) { std::cout << "Bar "<< base_id << std::endl;}
    int base_id;
};

struct Foo : public Bar
{
  Foo() : x(c) { std::cout << "Foo "<< x << std::endl;}
  int x;
};

int main()
{
  Bar* b = new Foo[200];
  Foo *p;

  for(p = (Foo*)b; p - (Foo*)b < 200; p ++ )
  {
      std::cout << p->base_id << " " << ((Foo*)p)->x 
         << " p-b=" << (unsigned)(p-(Foo*)b) << std::endl;
  }


  delete[] b;
}

或text version:我们通过new Derived创建一个基类对象数组。到目前为止一切都很好,这就是大型复杂应用程序所做的(那里的类更加层次化,构造函数做得更多),但是全局静态变量(c)也存在于大型复杂应用程序中,它使用它作为唯一对象标识符。然后大型复杂的应用程序开始工作,等等....

然后在某个时间点,我们遍历对象数组,做一些工作。迭代看起来与我在这里写的一个完全相同,在for循环中找到,具有详尽的指针算法。我的示例只是在大型复杂应用程序中打印对象id(m),完成了更多的工作。

但是在大​​型复杂应用程序的某个地方发生了一些魔术......在列表的一半之后的某个时间,对象(通过p指针获得)不再有效,它们的base_id s显示数据对我来说几乎看起来像指针和其他数据成员的值。但是,如果我检查特定索引处的数组成员,那么该值是有效的(即:正确增加对象ID)。

我还检查了以下内容:

  • 似乎没有内存损坏问题。 Valgrind和Clang的记忆消毒剂没有显示任何令人讨厌的东西。
  • 所有对象都已正确创建和销毁,没有内存泄漏。
  • 这种疯狂发生的唯一地方就是上面的这个循环

所以......我得出了一个结论:

  • 有人在某处修改了一些数组值以实际指向不同类型的对象(所有这些都来自Bar(e ...一些不同命名的基类)所以我可能需要更多地挖掘找到这个的代码。但是这是一个庞大的代码库,该数组有数千个引用,在此之前我想问:

(这就是问题所在:)

我不知道C ++对象可以在运行时改变它的大小(如果有可能,请与我们分享),所以除了我已经确定的可能问题之外,社区中的任何人都可能有任何想法为什么指针算术表现得如此奇怪?

(是的,项目将转换为带有标准容器等的正确的c ++ 11 ......但是现在我只对这个特定问题感兴趣)

1 个答案:

答案 0 :(得分:2)

你的问题在这里:

Bar* b = new Foo[200];

b指向Bar数组的第一个元素中的Foo子对象,但不能用作访问数组的方法;大小错了。不断地将它强制转换回Foo指针似乎有效,但很容易失败。 (当Bar子对象甚至与它所属的Foo对象的地址相同时,面对多重继承会变得更糟。)

在发布指针算法之前,您发布的代码小心谨慎地将b强制转换为Foo*。 (即使过于谨慎:((Foo*)p)->x可能只是p->x。)但是,如果在任何地方,任何地方都有人忘记这样做(例如尝试b[i]b+i,或者(Foo*)(b+i) ...),它将导致您描述的行为。 或者,随着所有这些向下倾斜的进行,有人将Bar*向下倾斜 Baz*,其中Baz也继承自Bar,但其大小与Foo不同。 这也会以不稳定的方式覆盖字段。

此外,delete[] b is undefined behavior。因此,编译器得出结论b确实指向Bar个实例,并且将强制转换或者强制转换为Foo* - 编译器没有可能的范围 - 编译器会这样做这些天的事情。

总而言之,Bar* b = new Foo[200];是行不通的。使用

Foo* fp = new Foo[200];

代替。如果由于某种原因,您需要Bar*,则可以使用

进行操作
Bar* b = fp;

但不清楚你为什么需要这个;只要需要fp,您就可以使用Bar*