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

Can a C++ class instantiation change its size during runtime

本文关键字:运行时 实例化 是否 C++      更新时间:2023-10-16

我这里有一个非常特殊的情况......我继承了一些旧的C++代码(pre-C++11),它又长又复杂,其中一半是用C编写的,另一半是用C-with-class的心态编写的(即:具有更多数据成员的类,没有太多的类方法,直接操作数据成员......当然,这个项目需要一些重构,这就是我现在正在做的事情)。但是代码暴露了一些我觉得令人费解的问题。

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

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

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

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

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

我还检查了以下内容:

  • 似乎没有内存损坏问题。瓦尔格林德和克朗的记忆消毒剂没有显示任何可疑的东西。
  • 所有对象都已正确创建和销毁,没有内存泄漏。
  • 这种疯狂发生的唯一地方是在上面的这个循环中

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

  • 有人在某处修改了一些数组值以实际指向不同类型的对象(所有这些都派生自Bar(例如......一些不同名称的基类),因此可能需要在代码中挖掘更多才能找到它。但这是一个巨大的代码库,该数组实际上有数千个引用,在此之前我想问一下:

(问题来了:)

我不知道C++对象可以在运行时更改其大小(如果可能,请与我们分享),因此除了我确定的可能问题之外,社区中的任何人都可能知道为什么指针算术行为如此奇怪?

(是的,该项目将使用标准容器等转换为适当的 C++11 ......但现在我只对这个特定问题感兴趣)

你的问题在这里:

Bar* b = new Foo[200];

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

您发布的代码小心翼翼地始终在执行指针算术之前将b转换为Foo*。(即使太小心了:((Foo*)p)->x可能只是p->x。但是,如果在任何时候,任何地方,有人忘记这样做(例如尝试b[i],或b+i,或(Foo*)(b+i)......),这将导致你描述的行为。或者,也许,随着所有这些沮丧的进行,有人将Bar*降为一个 Baz*,其中Baz也继承自Bar,但大小与Foo不同。这也将以不稳定的方式覆盖字段。

此外,delete[] b是未定义的行为。因此,编译器得出的结论并不超出范围,即b确实指向Bar实例,并省略或搞砸了Foo*的强制转换 - 编译器现在做这种事情。

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

Foo* fp = new Foo[200];

相反。如果出于某种原因,您需要一个 Bar* ,您可以按照它进行操作

Bar* b = fp;

但目前还不清楚为什么需要这个;你可以在需要Bar*的时候使用fp