4

有人可以向我解释为什么在类声明的末尾添加一个虚函数可以避免二进制不兼容吗?

如果我有:

class A
{ 
  public:
    virtual ~A();
    virtual void someFuncA() = 0;
    virtual void someFuncB() = 0;
    virtual void other1() = 0;
  private:
    int someVal;
};

稍后将这个类声明修改为:

class A
{ 
  public:
    virtual ~A();
    virtual void someFuncA() = 0;
    virtual void someFuncB() = 0;
    virtual void someFuncC() = 0;
    virtual void other1() = 0;
  private:
    int someVal;
};

我从另一个根据先前声明编译的 .so 得到一个核心转储。但是,如果我将 someFuncC() 放在类声明的末尾(在“int someVal”之后):

class A
{ 
  public:
    virtual ~A();
    virtual void someFuncA() = 0;
    virtual void someFuncB() = 0;
    virtual void other1() = 0;
  private:
    int someVal;
  public:
    virtual void someFuncC() = 0;
};

我再也看不到核心转储了。有人能告诉我这是为什么吗?这个技巧总是有效吗?

PS。编译器是 gcc,这适用于其他编译器吗?

4

5 回答 5

6

另一个答案

这是否会导致内存泄漏、擦除您的硬盘、让您怀孕、让讨厌的鼻恶魔在您的公寓周围追逐您,或者让一切正常工作而没有明显的问题,都是不确定的。一个编译器可能是这样的,另一个编译器会改变,新的编译器版本会改变,每次新的编译,月相,你的心情,或者取决于在最后一个晴天通过处理器的中微子的数量下午。或者它可能不会。

所有这些,以及无限量的其他可能性都放在一个术语中:未定义的行为

远离它。

如果您知道特定编译器版本如何实现其功能,您可能会使其工作。或者你可能不会。或者你可能认为它有效,但它坏了,但前提是坐在电脑前的人只是吃了酸奶。

你有什么理由想让它工作吗?

我想它的工作方式/不工作方式/不工作方式是因为您的编译器按照声明虚拟函数的顺序创建虚拟表条目。如果你通过在其他人之间放置一个虚函数来搞乱顺序,那么当有人调用时other1()someFuncC()就会调用 , 可能带有错误的参数,但肯定是在错误的时刻。(很高兴它立即崩溃。)
然而,这只是一个猜测,即使它是正确的,除非你的 gcc 版本带有描述这个的文档,否则不能保证它明天会以这种方式工作,即使使用相同的编译器版本

于 2010-05-18T18:19:01.187 回答
2

这种特殊的重新排列完全有帮助,我有点惊讶。它当然不能保证工作。

您在上面给出的课程通常会按此顺序翻译成某些东西:

typedef void (*vfunc)(void);

struct __A__impl { 
     vfunc __vtable_ptr;
     int someVal;
};

__A__impl__init(__A__impl *object) { 
    static vfunc virtual_functions[] = { __A__dtor, __A__someFuncA, __A__someFuncB};
    object->__vtable__ptr = virtual_functions;    
}

当/如果您添加someFuncC,您通常应该将另一个条目添加到类的虚拟函数表中。如果编译器将其安排在任何其他函数之前,您将遇到一个问题,即尝试调用一个函数实际上会调用另一个函数。只要它的地址在虚函数表的末尾,事情应该仍然有效。但是,C++ 不保证 vtable 的排列方式(甚至不保证存在vtable)。

对于普通数据,(非静态)成员需要按升序排列,只要没有中间的访问说明符(public:protected:private:

如果编译器在将虚函数声明映射到 vtable 位置时遵循相同的规则,那么您的第一次尝试应该可以工作,但您的第二次尝试可能会中断。显然,尽管不能保证这一点——只要它始终如一地工作,编译器就可以按照它想要的任何方式排列 vtable。

于 2010-05-18T18:21:39.867 回答
1

也许 G++ 将其“私有”虚拟表放在与公共表不同的位置。

无论如何,如果你能以某种方式欺骗编译器,使其认为一个对象看起来与实际不同,然后使用该对象,你将调用鼻恶魔。你不能依赖他们所做的任何事情。这基本上就是你正在做的事情。

于 2010-05-18T18:10:24.003 回答
0

我建议避免与类的二进制兼容性或序列化相关的任何事情。这会打开太多的蠕虫罐头,并且没有足够的鱼来吃掉它们。

传输数据时,更喜欢 XML 或基于 ASCII 的带有字段定义的协议,而不是二进制协议。一个灵活的协议将帮助人们(包括你)调试和维护协议。ASCII 和 XML 协议提供比二进制更高的可读性。

类、对象、结构等,绝不应该通过二值图像作为一个整体进行比较。首选方法是让每个类实现自己的比较运算符或函数。毕竟,班级是如何比较其数据成员的专家。一些数据成员,比如指针和容器,真的会搞砸二进制比较。

由于处理器速度很快,内存很便宜,因此将您的原则从节省空间更改为正确性和稳健性。在程序没有错误后优化速度或空间。

有太多关于二进制协议和对象的二进制比较(和分配)的恐怖故事发布到 Stack Overflow 和新闻组。让我们通过学习现有的恐怖故事来减少新恐怖故事的数量。

于 2010-05-18T18:22:15.687 回答
-2

二进制兼容性是一件坏事。您应该使用基于纯文本的系统,也许是 XML。

编辑:有点错位了你的问题的意思。Windows 提供了许多本地方式来共享数据,例如 GetProcAddress、__declspec(dllexport) 和 __declspec(dllimport)。GCC 必须提供类似的东西。二进制序列化是一件坏事。

再次编辑:实际上,他在帖子中没有提到可执行文件。完全没有。也不是他试图使用他的二进制兼容性来做什么。

于 2010-05-18T18:26:11.013 回答