English 中文(简体)
同一名称的类别之间共有的表:在投向基类时,要求虚拟方法坠毁
原标题:Shared vtables between classes of same name: call to virtual method crashes when casting to base type
  • 时间:2012-01-13 14:18:39
  •  标签:
  • c++

下面是人民民主党,我可以复制,需要帮助。

我有奇怪的坠毁,除了1处地方外,有些方法在任何地方都进行罚款。 该法典:

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

现在,正如我说过的那样,我称之为“项目”和“项目”;数据(植被)在许多地方对不同目标都有影响,它始终发挥作用。 除1个地方外。 但是,如果投到基类(output)的话,它就没有做任何工作。

现在的问题是:HOWWHY,这种情况可能发生? 我怀疑纯粹的虚拟电话,但我没有在建筑商使用虚拟方法。 我看不出这些呼吁是如何不同的。 我怀疑基数方法是抽象的,但如果我增加一个机构,也是一样的。

我不能举一个小的例子来测试,因为正如我所说的那样,它总是发挥作用,只有1个地方除外。 如果我知道为什么不在那里工作,那么我就不需要测试样本,因为这已经是答案。

P.S. The environment is Ubuntu 11.10 x64 but the program is compiled for 32 bit using gcc 4.5.2 custom build.

P.P.S.

warning: can t find linker symbol for virtual table for `derived::get  value
warning:   found `SomeOtherDerivedFromBaseClass::SomeOtherCrazyFunction  instead

in the real program

UPDATE:任何机会都可能发生,因为有相同的名称,但在不同共享的图书馆内,可能会与错误类别挂钩? 几个共享图书馆实际界定的“衍生”类别,更糟糕的是,有另一个名称相同但不同接口的同类。 奇怪的是,它不投身于基类。

我特别有兴趣了解这方面的细节。

这里我似乎照搬了:

// --------- 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
", t.data());
   printf("%s
", 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

In the modtest above, if we include "mod2.h" instead of "mod1.h", we get normal "test2 test2" output without segfault.

The question is - what is the exact mechanism for this? How to detect and prevent? I knew that static data in gcc will be linked to single memory entry, but vtables...

最佳回答

Edit in response to update: In your updated code where you use mod1 and mod2 header you re violating the One Definition Rule for classes (even by appearing in shared libraries). It basically states that in your entire program you must have only one definition of a class (base in this case) although the same definition can appear in multiple source files. If you have more than one definition then all bets are off and you get undefined behavior. In this case, the undefined behavior happens to be a crash. The fix is of course to not have multiple versions of the same class in the same program. This is usually accomplished by defining each class in a single header (or implementation for non-API/impl classes) and including that header where the class definition is needed.

Original answer: If it works everywhere except one place it sounds like the object isn t valid in that one place (working as derived pointer but not as base sounds a lot like you entered the realm of undefined behavior). Either it s corrupted memory, a deleted object pointer, or something else. Your best bet is if you can run valgrind on it.

问题回答

您的回答是: 同一名称类别之间共有的席位......>。

您从两份申请卷宗中汇编了一名双亲,但每份申请文件都包含不同的主人档案,尤其是关于“条形状”的另一种定义。 在C 0中,你可以有两门同名的班子。 如果使用同一名称,他们就属于同一类别,你必须一致。 (明显例外是将其放在两个不同的名称空间。)

(此处每一处都是专门汇编者。) 但这可能是大多数汇编者的一种典型做法。

首先,请了解非虚拟方法。 在物体上执行这种方法的:

b.foo(3);

法典基本上改写如下:

foo_(b,3);

所用方法如下:

void foo_(base * this, int i) {
    ...
}

i.e.this pointer is Secretly adopted as the first para amount to the function.

But things aren t so simple with virtual methods. There will be two different free functions that implement get. We ll call one of these get_base and the other get_derived. (Never mind that you actually have a pure virtual method (=0), it doesn t really change the story.)

问题是,如何在操作时间选择正确的<代码>get? 即便是,对于每个拥有至少一种虚拟方法的类别,汇编者也形成了一个表象。 特定阶层的表列了该类所有虚拟方法。 例如

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;

职能点的类型是一个包含一个参数的职能(base*,该参数将成为this/code>,并将wchar_t*

两个类别:<编码>底线和<代码>derived 事实上,这是指在世系下的这些table。

struct base {
    vtable_for_base_t * vtable;
    .... other members of base
};

struct derived {
    vtable_for_????_t * vtable;
    .... other members of derived
};

每当建造一个<条码>基准<>/代码>物体时,就开始输入<条码>vtable点,以标明<条码>的表。 每当derived 标的是构造的,而是标明了<代码>derived的表。 如今,只要汇编者看到<条码>b.get(,就会将其改为:

(b.vtable->get_function_pointer)(&b);

它研究了标语中标注的,以找到正确版本的<代码>get的功能点。 然后将<条码>b转至该功能,以确保其有正确的<条码>。

简言之,每个物体都有一个(隐藏的)成员,他们知道<条码>的正确版本。 在这种情况下,汇编者假定,在基数表中的first的条目,以及从基数得出的任何类型表格,将采用get方法。

<><>>>> 在为衍生课程设置表时,首先列出的条目将符合基类的方法。 他们将按他们处于基地的顺序行事。 以后将列出衍生产品类别的任何新虚拟方法。

如果您有两种虚拟方法,即foobar,则这些方法将成为base的头两个条目,相应的衍生版本也将在derived的表中处理头两个位置。

现在,要了解一下,为什么你重新陷入了ault。 在<代码>mod2.h中,设定了基数表,其中<代码>数据是第一个(仅)条目。 因此,任何包括<代码>mod2.h并试图执行<编码>b.data(<>/code>的代码都将在表上填入first。 但是,这一条在编集时并不相关,因为它包括<代码>mod1.h。

modtest.cpp includes mod1.h. As a result, it sees a base class that has two methods, where data is the second method listed in the vtable. Therefore, any attempt to execute b.data will actually become:

(b.vtable.SECOND_ENTRY)(&b);

因为它假设第二个条目是data(>.

It will attempt to get the second entry from the vtable, but the real vtable (created in mod2.h) has only one entry! Therefore it s trying to access invalid memory and everything fails.

In short, consider defining these two structs in two different header files in C:

 // in one file
 struct A {
     int i;
     char c[500];
 }

 // in another file
 struct A {
     char c[500];
     int i;
 }

没有人会期望这样做。 该方案往往得到错误的记忆。 因此,你用table子 t。

在将衍生产品作为母类时,无需明确表述:

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

本案中更为明确的说法可能掩盖 b。 你告诉汇编者,你知道你正在做什么,也不会阻止,或者在许多情况下甚至警告你,你正在做一些失败的事情。

如果它没有编辑,就意味着在法典中存在错误(在大多数情况下,汇编者给你错误信息的原因)。

在你的第二个例子中,你违反了

To Quote from Wikipedia:

  1. In any translation unit, a template, type, function, or object can have no more than one definition. Some of these can have any number of declarations. A definition provides an instance.
  2. In the entire program, an object or non-inline function cannot have more than one definition; if an object or function is used, it must have exactly one definition. You can declare an object or function that is never used, in which case you don t have to provide a definition. In no event can there be more than one definition.
  3. Some things, like types, templates, and extern inline functions, can be defined in more than one translation unit. For a given entity, each definition must be the same. Non-extern objects and functions in different translation units are different entities, even if their names and types are the same.

你违反了规则第2部分。 。 因此,你有时会失事,有时甚至会这样做。 然而,你的方案无效。 汇编者不必警告你,因为这两种定义都出现在不同的翻译单位,而标准并不要求它检查整个汇编单位在这种情况下的一致性。

预防这类问题非常容易。 这正是创造名称空间的目的。 试图将你的班级与特别名称空间分开,再也不成问题。

Detecting this kind of thing is a bit harder. One thing you can try is a unity-build. This looks really scary at first sight, but actually helps in solving a lot of problems with this kind of thing. As a sideeffect a unity-build will also speed up compilation time while you are developing. The link above gives instructions for using a unity-build in Visual Studio, but it is actually quite simple to add to makefiles as well (including the automatic generation of the necessary header).

base &v2 = (base&)derived; // or base* v2, doesn t matter

改为

base &v2 = v1; 

问题是——这是什么确切的机制?

See the other response about ODR.

如何发现和预防?

为贵图书馆制作大量译本,并纳入其中的每一种依赖性。

确保你利用适当的范围和可见度。 如果形象是私人的,那就是一个案例(一个或保留图像空间)。 否则,它应当是公开的,通过包容而向客户可见。 将所有内容都列入一个TU,并利用明确界定的公约进行范围和可见度,将可查出错误的many

在某些情况下,联系人也可以踢。 事实上,出口你的虚拟数字是出于许多原因的伟大想法——联系人会发现这一问题。

我知道,电梯的静态数据将与单一记忆条目挂钩,但表......

may be duplicated if virtuals definitions are visible. That is, your entire rtti-info and vtable may be exported per TU which can cause serious bloat and add quite a bit to compile and link times.





相关问题
Undefined reference

I m getting this linker error. I know a way around it, but it s bugging me because another part of the project s linking fine and it s designed almost identically. First, I have namespace LCD. Then I ...

C++ Equivalent of Tidy

Is there an equivalent to tidy for HTML code for C++? I have searched on the internet, but I find nothing but C++ wrappers for tidy, etc... I think the keyword tidy is what has me hung up. I am ...

Template Classes in C++ ... a required skill set?

I m new to C++ and am wondering how much time I should invest in learning how to implement template classes. Are they widely used in industry, or is this something I should move through quickly?

Print possible strings created from a Number

Given a 10 digit Telephone Number, we have to print all possible strings created from that. The mapping of the numbers is the one as exactly on a phone s keypad. i.e. for 1,0-> No Letter for 2->...

typedef ing STL wstring

Why is it when i do the following i get errors when relating to with wchar_t? namespace Foo { typedef std::wstring String; } Now i declare all my strings as Foo::String through out the program, ...

C# Marshal / Pinvoke CBitmap?

I cannot figure out how to marshal a C++ CBitmap to a C# Bitmap or Image class. My import looks like this: [DllImport(@"test.dll", CharSet = CharSet.Unicode)] public static extern IntPtr ...

Window iconification status via Xlib

Is it possible to check with the means of pure X11/Xlib only whether the given window is iconified/minimized, and, if it is, how?

热门标签