C++ 同名类之间的共享vtables:强制转换为基类型时,对虚拟方法的调用崩溃
检查下面的更新,我可以复制和需要帮助 我有一个奇怪的崩溃,有些方法在任何地方都可以正常工作,除了一个地方。代码如下:C++ 同名类之间的共享vtables:强制转换为基类型时,对虚拟方法的调用崩溃,c++,C++,检查下面的更新,我可以复制和需要帮助 我有一个奇怪的崩溃,有些方法在任何地方都可以正常工作,除了一个地方。代码如下: struct base { virtual wchar_t* get() = 0; // can be { return NULL; } doesn't matter }; struct derived: public base { virtual wchar_t* get() { return SomeData(); } }; struct containe
struct base
{
virtual wchar_t* get() = 0; // can be { return NULL; } doesn't matter
};
struct derived: public base
{
virtual wchar_t* get() { return SomeData(); }
};
struct container
{
derived data;
};
// this is approx. how it is used in real program
void output(const base& data)
{
data.get();
}
smart_ptr<container> item = GetItSomehow();
derived &v1 = item->data;
v1.get(); // works OK
//base &v2 = (base&)derived; // the old line, to understand old comments in the question
base &v2 = v1; // or base* v2 doesn't matter
v2.get(); // segmentation fault without going into method at all
在真实的程序中
更新:有没有可能因为gcc将vtable链接到一个同名但位于不同共享库中的错误类而发生这种情况?RealApp中的“派生”类实际上是在几个共享库中定义的,更糟糕的是,还有另一个类似的类具有相同的名称,但接口不同。奇怪的是,没有铸造到基类它的作品
我对这里的gcc/linking/vtables细节特别感兴趣
我似乎是这样复制的:
// --------- mod1.h
class base
{
public:
virtual void test(int i); // add method to make vtables different with mod2
virtual const char* data();
};
class test: public base
{
public:
virtual const char* data();
};
// --------- mod2.h
class base
{
public:
virtual const char* data();
};
class test: public base
{
public:
virtual const char* data();
};
// --------- mod2.cpp
#include "mod2.h"
const char* base::data() { return "base2"; }
const char* test::data() { return "test2"; }
// --------- modtest.cpp
#include <stdio.h>
// !!!!!!!!! notice that we include mod1
#include "mod1.h"
int main()
{
test t;
base& b = t;
printf("%s\n", t.data());
printf("%s\n", b.data());
return 0;
}
// --------- how to compile and run
g++ -c mod2.cpp && g++ mod2.o modtest.cpp && ./a.out
// --------- output from the program
queen3@pro-home:~$ ./a.out
test2
Segmentation fault
/------------mod1.h
阶级基础
{
公众:
虚空测试(inti);//添加方法使vtables与mod2不同
虚拟常量字符*数据();
};
类测试:公共基
{
公众:
虚拟常量字符*数据();
};
//-----------mod2.h
阶级基础
{
公众:
虚拟常量字符*数据();
};
类测试:公共基
{
公众:
虚拟常量字符*数据();
};
//-----------mod2.cpp
#包括“mod2.h”
const char*base::data(){返回“base2”;}
const char*test::data(){返回“test2”;}
//-----------modtest.cpp
#包括
// !!!!!!!!! 注意,我们包括mod1
#包括“mod1.h”
int main()
{
试验t;
基数&b=t;
printf(“%s\n”,t.data());
printf(“%s\n”,b.data());
返回0;
}
//------如何编译和运行
g++-c mod2.cpp&&g++mod2.o modtest.cpp&&a.out
//------程序的输出
queen3@pro-首页:~$/a.out
测试2
分段故障
在上面的modtest中,如果我们包含“mod2.h”而不是“mod1.h”,我们将得到正常的“test2\ntest2”输出,而没有segfault
问题是——这一过程的确切机制是什么?如何检测和预防?我知道gcc中的静态数据将链接到单个内存条目,但vtables…将派生类视为父类时,无需显式强制转换:
#include <iostream>
struct A {
virtual void get() { std::cout << "A" << std::endl; }
};
struct B : public A {
virtual void get() { std::cout << "B" << std::endl; }
};
int main(int argc, char **argv)
{
B b;
A & a = b;
a.get();
return 0;
}
#包括
结构A{
虚拟void get(){std::cout
应该读
base &v2 = v1;
编辑以响应更新:
在使用mod1
和mod2
标题的更新代码中,您违反了类的一个定义规则(即使出现在共享库中)。它基本上表明,在整个程序中,您必须只有一个类的定义(base
)虽然同一定义可以出现在多个源文件中。如果您有多个定义,则所有下注都将取消,您将获得未定义的行为。在这种情况下,未定义的行为恰好是崩溃。修复方法当然是在同一程序中不具有同一类的多个版本。这通常通过定义每个类都在一个标头(或非API/impl类的实现)中,并包括需要类定义的标头
原始答复:
如果它在除一个地方以外的任何地方都能工作,则听起来该对象在该地方无效(作为派生指针而不是基指针工作听起来很像您进入了未定义行为的领域)。可能是内存损坏、对象指针被删除或其他原因。您最好的选择是是否可以在其上运行valgrind。在第二个示例中,您违反了
引用维基百科:
在任何翻译单元中,模板、类型、函数或对象只能有一个定义。其中一些可以有任意数量的声明。定义提供一个实例
在整个程序中,一个对象或非内联函数不能有多个定义;如果使用一个对象或函数,它必须只有一个定义。您可以声明一个从未使用过的对象或函数,在这种情况下,您不必提供定义。在任何情况下都不能有多个定义
有些东西,如类型、模板和外部内联函数,可以在多个转换单元中定义。对于给定的实体,每个定义必须相同。不同转换单元中的非外部对象和函数是不同的实体,即使它们的名称和类型相同
您违反了规则的第2部分。base
和test
在mod1.hh
和mod2.hh
中多次声明并冲突,因此您的程序无效并调用未定义的行为。因此,您有时会遇到崩溃,有时也会遇到冲突。尽管如此,您的程序仍然无效。编译器不必警告您,因为这两个定义出现在不同的翻译单元中,在这种情况下,标准不要求它检查跨编译单元的一致性
防止此类问题非常容易。这就是名称空间的发明目的。尝试在特定名称空间中分离类,ODR将不再是问题
检测这类东西有点困难。你可以尝试的一件事是。乍一看,这看起来非常可怕,但实际上有助于解决这类东西的许多问题。作为一个副作用,unity build也会在开发过程中加快编译时间。上面的链接给出了在Visua中使用unity build的说明l Studio,但实际上添加到makefiles也很简单(包括自动生成必要的头文件)。您的答案在您的问题中:“相同名称的类之间共享vtables…”
你从两个CPP文件中编译出一个二进制,但是每个CPP文件包含不同的头文件,特别是不同的定义:<代码>结构基础B/COD>。在C++中,不能有两个同名的类。如果使用相同的名称,那么它们是同一个类,并且必须是一致的。(明显的例外是将它们放在两个不同的名称空间中。)
(这里的所有内容都是特定于编译器的。
base &v2 = (base&)derived; // or base* v2, doesn't matter
base &v2 = v1;
b.foo(3);
foo_(b,3);
void foo_(base * this, int i) {
...
}
struct vtable_for_base_t {
wchar_t* (*get_function_pointer)(base *); // initialized to get_base
};
vtable_for_base_t vtable_for_base;
vtable_for_base.get_function_pointer = &get_base;
vtable_for_????_t vtable_for_derived;
vtable_for_derived.get_function_pointer = &get_derived;
struct base {
vtable_for_base_t * vtable;
.... other members of base
};
struct derived {
vtable_for_????_t * vtable;
.... other members of derived
};
(b.vtable->get_function_pointer)(&b);
(b.vtable.SECOND_ENTRY)(&b);
// in one file
struct A {
int i;
char c[500];
}
// in another file
struct A {
char c[500];
int i;
}
test t;
// Using a pointer to make the cast a little more obvious
base *b = dynamic_cast<base *>(&t);
namespace INTERNATIONAL_BUSINESS_MACHINES {
void f();
}
namespace IBM = INTERNATIONAL_BUSINESS_MACHINES;