2014-09-20 30 views
5

假设C库必须与应用程序代码共享结构的细节,并且必须保持API和ABI向后兼容性。它试图通过检查传递给它的结构的大小来做到这一点。sizeof(struct)如何帮助提供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.sizesizeof(foo)sizeof(foo_2),并尝试找出应用程序代码是否groks版本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库中的代码看不懂,如果应用程序正在通过foo型或foo_2的结构?如果是,那么这种方法似乎是如何被使用的?

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


按照第一个问题:是否有一个很好的理由为有利于区分版本使用sizeof(struct)?正如在评论中指出的,为什么不在共享结构中使用明确的version成员?

+1

你从哪里得到24? – 2014-09-20 10:58:53

+0

这可能不起作用。 'sizeof'是一个编译时的事情,你想检查* runtime *的大小。 – 2014-09-20 11:03:11

+0

@BasileStarynkevitch:嗯,什么?我们知道调用者使用哪个版本的结构,而不是被调用者,因此从这个方向看起来很好。尽管如此,棘手的是,在大多数64位平台上,指针是8字节对齐和大小的,int 4,因此两个结构之间没有尺寸差异。 – Deduplicator 2014-09-20 11:06:34

回答

2

为了搭配你的观察,我断定

  • char*有大小8和对齐8
  • int有大小4和对齐4.
  • 你实现使用最佳的包装。

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

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


有一个可能性,这可能会挽救虽然天:

  • 如果字段中包含这在以前是无效的值,这可能表明,什么都可能被解释differencty。
+2

正确的解决方案是填充结构的v2,所以它没有相同的'sizeof' – Mgetz 2014-09-20 11:14:44

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,以确保数据类型的二进制兼容性(可以肯定的intdoublechar*等的大小在不同的体系结构相同)。例如,而不是int我会使用int32_t

+0

APR?只需使用stdint.h – James 2014-09-20 13:40:15

+0

@James谢谢,编辑我的答案:) – HRold 2014-09-20 16:31:35

1

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

为此,您可以通过强制编译器使用更紧密的打包来尝试缩小foo,或者通过添加额外的(未使用的)字段来增大foo_2

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