将虚函数添加到类声明的末尾可以避免二进制不兼容吗?

发布于 2024-09-02 04:32:49 字数 906 浏览 3 评论 0原文

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

如果我有:

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

我将不再看到 coredump。有人能告诉我这是为什么吗?这个技巧总是有效吗?

附言。编译器是gcc,这可以与其他编译器一起使用吗?

Could someone explain to me why adding a virtual function to the end of a class declaration avoids binary incompatibility?

If I have:

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

And later modify this class declaration to:

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

I get a coredump from another .so compiled against the previous declaration. But if I put someFuncC() at the end of the class declaration (after "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;
};

I don't see coredump anymore. Could someone tell me why this is? And does this trick always work?

PS. compiler is gcc, does this work with other compilers?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

行至春深 2024-09-09 04:32:49

来自另一个答案

这是否会导致内存泄漏、擦除你的硬盘、让你怀孕、让讨厌的鼻恶魔在你的公寓里追赶你,或者让一切正常工作而没有明显的问题,都是不确定的。对于一个编译器来说可能是这样,然后用另一个编译器进行更改,用新的编译器版本进行更改,每个新的编译进行更改,月相,您的心情,或者取决于最后一个阳光明媚时通过处理器的中微子数量下午。或者也可能不会。

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

远离它。

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

你有什么理由想让它发挥作用吗?

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

From another answer:

Whether this leads to a memory leak, wipes your hard disk, gets you pregnant, makes nasty Nasal Demons chasing you around your apartment, or lets everything work fine with no apparent problems, is undefined. It might be this way with one compiler, and change with another, change with a new compiler version, with each new compilation, with the moon phases, your mood, or depending on the number or neutrinos that passed through the processor on the last sunny afternoon. Or it might not.

All that, and an infinite amount of other possibilities are put into one term: Undefined behavior:

Just stay away from it.

If you know how a particular compiler version implements its features, you might get this to work. Or you might not. Or you might think it works, but it breaks, but only if whoever sits in front of the computer just ate a yogurt.

Is there any reason why you want this to work anyway?

I suppose it works/doesn't work the way it does/doesn't because your compiler creates virtual table entries in the order the virtual functions are declared. If you mess up the order by putting a virtual function in between the others, then when someone calls other1(), instead someFuncC() is called, possibly with wrong arguments, but definitely at the wrong moment. (Be glad it crashes immediately.)
This is, however, just a guess, and even if it is a right one, unless your gcc version comes with a document somewhere describing this, there's no guarantee it will work this way tomorrow even with the same compiler version.

许久 2024-09-09 04:32:49

我有点惊讶这种特殊的重新安排居然有帮助。它当然不能保证有效。

上面给出的类通常会按以下顺序转换为某些内容:

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:私有:)。

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

I'm a bit surprised that this particular rearrangement helps at all. It's certainly not guaranteed to work.

The class you give above will normally be translated to something on this order:

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

When/if you add someFuncC, you should normally get another entry added to the class' virtual functions table. If the compiler arranges that before any of the other functions, you'll run into a problem where attempting to invoke one function actually invokes another. As long as its address is at the end of the virtual function table, things should still work. C++ doesn't guarantee anything about how vtables are arranged though (or even that there is a vtable).

With respect to normal data, (non-static) members are required to be arranged in ascending order as long as there isn't an intervening access specificer (public:, protected: or private:).

If the compiler followed the same rules when mapping virtual function declarations to vtable positions, your first attempt should work, but your second could break. Obviously enough, there's no guarantee of that though -- as long as it works consistently, the compiler can arrange the vtable about any way it wants to.

苦行僧 2024-09-09 04:32:49

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

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

Maybe G++ puts its "private" virtual table in a different place than its public one.

At any rate, if you can somehow trick the compiler into thinking an object looks differently than it really does and then use that object you are going to invoke nasal demons. You can't depend on anything they do. This is basically what you are doing.

满地尘埃落定 2024-09-09 04:32:49

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

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

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

Binary compatibility is a bad thing. You should use a plaintext-based system, perhaps XML.

Edit: Misplaced the meaning of your question a little. Windows provides many native ways to share data, for example GetProcAddress, __declspec(dllexport) and __declspec(dllimport). GCC must offer something similar. Binary serialization is a bad thing.

Edit again:
Actually, he didn't mention executables in his post. At all. Nor what he was trying to use his binary compatibility for.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文