About
RSS

Bit Focus


头文件禁区: C++ 中的 ABI

Posted at 2011-08-31 06:52:02 | Updated at 2018-08-15 15:43:09

    在上一篇文章中写到了 C 的 ABI (application binary interface) 有关的内容. 而 C++ 这货也采用 header / cpp 分离的方式来组织代码, 并在编译时将头文件直接在 cpp 中插入, 这种暴力方法已经不能简单说是为了兼容 C 的各种编译期行为, 完全就是彻头彻尾地重蹈覆辙, 甚至有过之而无不及.

    首先, C++ 也会遇到 "如果直接用了动态库中修改过签名的函数怎么办" 这个全静态语言的千古难题. 而且比起 C 语言更要命的是, C++ 的类是不能进行前置声明来回避对象内存布局耦合: 前置声明就没有引用成员函数了, 那还怎么面向坑爹的对象呢? 不仅仅对象成员布局有此问题, 连同 C++ 藉由实现多态的虚函数亦面临着同样的窘境. 试想动态库中虚函数表都改得面目全非时, 使用该对象的代码却还用原来的方式访问, 那还不是各种鸡飞狗跳.
    对于如此恶劣环境下仍然想保持 ABI 兼容性的 C++ 程序员来说, 倒也不是完全无路可走. 具体的方案就不在这篇文章中赘述了, 请转到陈硕老师的 C++ 工程实践: 二进制兼容性C++ 工程实践: 避免使用虚函数作为库的接口. 两篇文章都写得非常详细.

    撇开这些跟 C 语言有关的孽缘不谈, C++ 本身也提供了一些机制来帮助程序员养成编码恶习, 比如一些函数实现可以直接写进头文件中, 而无须担忧任何链接错误. 首当这个批评的就是成员函数, 包括静态或非静态, 虚或非虚. inline 函数或者 template 函数实现要写头文件这点可以理解可以忍, 但成员函数来凑什么热闹? 成员函数的不往头文件里面写, 可以避免一些 ABI 耦合, 比如
class Date {
    int month;
    int day;
public:
    int getDay() const;
    int getMonth() const;
};
    这种情况下, 在使用
Date* date /* initialize */;
date->getDay();
不必担心内存布局耦合, 因为作为非虚的成员函数, Date::getDay() const 这样的函数签名正如 getday(Date const*) 一样, 这就回到了上一篇文章最后提到的解决方法了. 但是如果用得不恰当, 比如头脑发热认为 inline 一下能提高效率什么的, 而活生生地写成
class Date {
    int month;
    int day;
public:
    inline int getDay() const
    {
        return day;
    }

    inline int getMonth() const
    {
        return month;
    }
};
    使用动态库的代码看到是 inline 函数, 就会理所当然地把代码直接展开嵌入, getDay() 调用就相当于直接访问 Date 类偏移为 sizeof int 的地址. 这样一来, 如果动态库先升级, 在 month 前面再加个 year 程序就必死无疑.

    inline 函数也不是完全不能使用, 只是这种直接内存访问相关的应该明令禁止. 形如上篇文章中例子里的 void add_person_0(name_t name, age_t age);void add_person_0(name_t name, gender_t gender, age_t age); 两个接口如果想同时使用, 那么这样写是没问题的
void add_person(name_t name, gender_t gender, age_t age);

inline void add_person(name_t name, age_t age)
{
    add_person(name, gender_t::hideyoshi, age);
}
    也就是说使用一个 inline 函数来代替笨拙的默认参数. 当然前提是这个默认参数本身不会造成 ABI 的困扰. (这只是作为独立的例子, 并不表示上篇文章中的问题可以用这个方法解决!)

    不管怎么说, 将函数体写进头文件这种事情, 对于成员函数或 inline 函数都是可以避免的, 而模板函数则完全无法规避这个大坑. 而这一点, 并不是程序员说 "咱不写模板就好了" 这么简单的事情. 要知道, 这世界上很多函数的实现都是直接往头文件里面堆的, 换句话说, 任何泛型容器, 算法, 都不可以作为有 ABI 需求的接口参数或返回值类型来使用!

Post tags:   C++  Template  ABI  STL  inline

Leave a comment:




Creative Commons License Your comment will be licensed under
CC-NC-ND 3.0


. Back to Bit Focus
NijiPress - Copyright (C) Neuron Teckid @ Bit Focus
About this site