sizeof(struct)如何帮助提供ABI兼容性?

时间:2014-09-20 10:54:56

标签: c api compatibility abi

假设C库必须与应用程序代码共享结构的细节,并且必须保持API和ABI向后兼容性。它试图通过检查传递给它的结构的大小来做到这一点。

说,需要更新以下结构。在库版本1中,

typedef struct {
    int size;
    char* x;
    int y;
} foo;

在库的第2版中,它更新为:

typedef struct {
    int size;
    char* x;
    int y;
    int z;
} foo_2;

现在,库版本2想要检查应用程序是将新的foo_2还是旧的foo作为参数arg传递给函数。它假定应用程序已将arg.size设置为sizeof(foo)sizeof(foo_2),并尝试确定应用程序代码是否显示版本2.

if(arg.size == sizeof(foo_2)) {
    // The application groks version 2 of the library. So, arg.z is valid. 
} else {
    // The application uses of version 1 of the library. arg.z is not valid.
}

我想知道为什么这不会失败。在GCC 4.6.3上,使用-O3标志,sizeof(foo)sizeof(foo_2)都是24.因此,如果应用程序正在传递类型为{{{{}}的结构,则v2库代码无法理解1}}或foo?如果是的话,为什么这种方法似乎已被使用?

http://wezfurlong.org/blog/2006/dec/coding-for-coders-api-and-abi-considerations-in-an-evolving-code-base/

http://blogs.msdn.com/b/oldnewthing/archive/2003/12/12/56061.aspx


关注问题:是否有充分理由赞成使用foo_2进行版本歧视?正如评论中指出的那样,为什么不在共享结构中使用显式sizeof(struct)成员?

3 个答案:

答案 0 :(得分:2)

为了符合你的观察,我假设

  • char*的大小为8,对齐方式为8。
  • int的大小为4,对齐方式为4.
  • 您的实施使用最佳包装。

在这种情况下,您的旧结构和新结构都具有相同的大小,并且您的版本鉴别器是结构大小,升级是ABI突破性更改。 (很少有逻辑错误也是语法错误,前者不是由编译器诊断的。)

只有在导致更大尺寸的结构的更改中,新结构包含相同偏移的旧结构的所有字段,在该方案下可以与ABI兼容:添加一些虚拟变量。


有一种可能性可以挽救这一天:

  • 如果某个字段包含之前无效的值,则可能表示其他任何内容可能必须解释为不同。

答案 1 :(得分:1)

我建议使用中间结构。 例如:

typedef struct
{
    int           version;
    void*         data;
} foo_interface;

typedef struct
{
    char*         x;
    int           y;
} foo;

typedef struct
{
    char*         x;
    int           y;
    int           z;
} foo_2;

在我的库版本2中,我将按名称导出以下函数:

foo_interface* getFooObject()
{
    foo_interface* objectWrapper = malloc(sizeof(foo_interface));
    foo_2* realObject = malloc(sizeof(foo_2));

    /* Fill foo_2 with random data... */
    realObject.x = malloc(1 * sizeof(char));
    realObject.y = 2;
    realObject.z = 3;

    /* Fill our interface. */
    objectWrapper.version = 2; /* Here we specify version 2. */
    objectWrapper.data = (void*)realObject;

    /* Return our wrapped data. */
    return (objectWrapper);
}

然后在主应用程序中我会这样做:

int main(int ac, char **av)
{
    /* Load library + Retrieve getFooObject() function here. */

    foo_interface* objectWrapper = myLibrary.getFooObject();

    switch (objectWrapper->version)
    {
        case 1:
            foo*   realObject = (foo*)(objectWrapper ->data);
            /* Do something with foo here. */
            break;
        case 2:
            foo_2* realObject = (foo_2*)(objectWrapper ->data);
            /* Do something with foo_2 here. */
            break;
        default:
            printf("Unknown foo version!");
            break;
    }
    return (0);
}

像往常一样,为了代码的可读性,不包括安全检查(例如,在分配内存时)。

另外,我会使用 stdint.h 来确保数据类型的二进制兼容性(确保{​​{1}},int,{{的大小1}}等等在不同的架构中是相同的)。例如,我会使用double

而不是char*

答案 2 :(得分:1)

如果您想使用此方案来区分不同版本的API,您只需确保不同的结构版本具有不同的大小。

为此,您可以尝试通过强制编译器使用更严格的打包来使foo更小,或者通过添加其他(未使用的)字段来使foo_2更大。

无论如何,你应该为sizeof(foo) != sizeof(foo_2)添加一个断言(最好是在编译时),以确保结构总是实际上有不同的大小。