C++ class 实例化可以在运行时改变它的大小吗
Can a C++ class instantiation change its size during runtime
我这里有一个非常特殊的情况...我继承了一些旧的 C++ 代码(C++11 之前),它又长又复杂,其中一半是用 C 编写的,另一半是用 C 编写的- with-classes 心态(即:classes 有更多的数据成员,没有太多的 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
创建一个基本 class 对象数组。到目前为止一切顺利,这就是大型复杂应用程序所做的(classes 更有层次,构造函数做的更多),但全局静态变量 (c
) 也存在于大型应用程序中复杂的应用程序,它使用它作为唯一的对象标识符。然后大型复杂的应用程序开始工作,等等......
然后在某个时间点我们遍历对象数组,做一些工作。迭代看起来与我在这里写的完全一样,在 for
循环中找到,具有详尽的指针算法。我这里的示例只是在大型复杂应用程序中打印对象 ID (m
) 更多的事情已经完成。
但是在大型复杂应用程序的某处发生了一些神奇的事情......在列表的一半之后的某个时间,对象(通过 p
指针获得)不再有效,它们的 base_id
s 显示的数据对我来说非常像指针和其他数据成员的值。但是,如果我检查特定索引处的数组成员,则该值有效(即:正确增加对象 ID)。
我还检查了以下内容:
- 似乎没有内存损坏问题。 Valgrind 和 Clang 的内存清理器没有显示任何令人讨厌的东西。
- 所有对象都正确创建和销毁,没有内存泄漏。
- 唯一发生这种疯狂的地方是上面的这个循环
所以...我得出一个结论:
- 有人在某处修改了一些数组值以实际指向不同类型的对象(所有这些都派生自
Bar
(例如...一些不同名称的基 class)所以我可能'我需要在代码中挖掘更多才能找到它。但这是一个巨大的代码库,该数组实际上有数千个引用,在此之前我想问:
(问题来了:)
我不知道 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
is undefined behavior。因此,编译器有可能得出 b
确实指向 Bar
个实例,并省略或搞砸对 Foo*
的强制转换——如今编译器会做这种事情.
总而言之,Bar* b = new Foo[200];
是行不通的。使用
Foo* fp = new Foo[200];
相反。如果出于某种原因,您需要一个 Bar*
,您可以在后面加上
Bar* b = fp;
但不清楚你为什么需要这个;您可以在需要 Bar*
时使用 fp
。
我这里有一个非常特殊的情况...我继承了一些旧的 C++ 代码(C++11 之前),它又长又复杂,其中一半是用 C 编写的,另一半是用 C 编写的- with-classes 心态(即:classes 有更多的数据成员,没有太多的 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
创建一个基本 class 对象数组。到目前为止一切顺利,这就是大型复杂应用程序所做的(classes 更有层次,构造函数做的更多),但全局静态变量 (c
) 也存在于大型应用程序中复杂的应用程序,它使用它作为唯一的对象标识符。然后大型复杂的应用程序开始工作,等等......
然后在某个时间点我们遍历对象数组,做一些工作。迭代看起来与我在这里写的完全一样,在 for
循环中找到,具有详尽的指针算法。我这里的示例只是在大型复杂应用程序中打印对象 ID (m
) 更多的事情已经完成。
但是在大型复杂应用程序的某处发生了一些神奇的事情......在列表的一半之后的某个时间,对象(通过 p
指针获得)不再有效,它们的 base_id
s 显示的数据对我来说非常像指针和其他数据成员的值。但是,如果我检查特定索引处的数组成员,则该值有效(即:正确增加对象 ID)。
我还检查了以下内容:
- 似乎没有内存损坏问题。 Valgrind 和 Clang 的内存清理器没有显示任何令人讨厌的东西。
- 所有对象都正确创建和销毁,没有内存泄漏。
- 唯一发生这种疯狂的地方是上面的这个循环
所以...我得出一个结论:
- 有人在某处修改了一些数组值以实际指向不同类型的对象(所有这些都派生自
Bar
(例如...一些不同名称的基 class)所以我可能'我需要在代码中挖掘更多才能找到它。但这是一个巨大的代码库,该数组实际上有数千个引用,在此之前我想问:
(问题来了:)
我不知道 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
is undefined behavior。因此,编译器有可能得出 b
确实指向 Bar
个实例,并省略或搞砸对 Foo*
的强制转换——如今编译器会做这种事情.
总而言之,Bar* b = new Foo[200];
是行不通的。使用
Foo* fp = new Foo[200];
相反。如果出于某种原因,您需要一个 Bar*
,您可以在后面加上
Bar* b = fp;
但不清楚你为什么需要这个;您可以在需要 Bar*
时使用 fp
。