假设我有一个接口(interface)类
class foo_if_t {
};
,第一个库 libfoo_orig.so
,其类 foo_t
继承自 foo_if_t
并具有以下代码:
#include "foo_if.h"
class foo_t : public foo_if_t{
private:
int res;
public:
foo_t(){}
virtual ~foo_t(){}
};
和第二个库libfoo_mod.so
,其类foo_t
重新定义如下:
#include "foo_if.h"
class foo_t : public foo_if_t{
private:
int res[100];
public:
foo_t() {
for (int i=0; i<100; i++)
res[i] = i;
}
virtual ~foo_t(){}
};
我创建一个符号链接(symbolic link) libfoo.so --> libfoo_orig.so
并编译以下应用程序
#include "foo_orig.h"
int main(){
foo_if_t *foo_if = new foo_t();
delete foo_if;
}
与g++ -ggdb -O0 test.cpp -o test -L。 -lfoo
(因此链接到 libfoo.so)。
此时我将符号链接(symbolic link)更改为目标libfoo_mod.so
并重新运行代码。这将导致以下错误:
*** Error in `./test': free(): invalid next size (fast): 0x0000000001ec9010 ***
Aborted (core dumped)
我认为可能发生的情况是,库 foo_orig.so
中的 foo_t
的构造函数分配占用的堆 block 比来自 foo_mod 的堆 block 要小.so
,因此当调用 foo_mod.so
foo_t
构造函数时,它会弄脏超出分配边界的堆内存(从而损坏堆 next
block 引用)。这告诉我,堆预留是在链接时以某种方式预先计算的,而我认为它将在运行时根据调用的实际构造函数代码动态解析。我是否弄错了,如果没有,为什么生成的输出代码会这样?
作为反证明测试,我将 new foo_t()
调用包装在为每个库编写的 static foo_it_t * foo_if_t::new_instance()
实现中;从主代码调用 new_instance
可以正常工作。
最佳答案
这里的问题是你的库代码和主程序代码对foo_t
的布局结构有不同的想法。尽管它们都认识到该类型的存在,但它们都是使用定义该类型的不同版本的 header 进行编译的。这总是会带来大麻烦。
在您的情况下,问题是由以下事实引起的:由 new
执行的实际内存分配是从主程序编译的,因此知道创建的对象的大小是多少是。同时,构造函数是用库代码编译的,并且对底层对象大小有完全不同的想法。因此,主程序在堆上创建了一个 sizeof(foo_t)
对象 - 从它的角度来看,它是 sizeof(int)
。构造函数不知道这一点,很乐意将内存删除最多 100 个整数,从而损坏了堆。
基本上你不能用这种方式“作弊”。如果您更改头文件,您应该始终重新编译依赖于该头文件的库,否则会面临像这样不可预测的麻烦(显然在这种情况下您是故意这样做的,但这也很容易意外完成)。
关于C++ 类库动态运行时分配,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37114376/