假设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://blogs.msdn.com/b/oldnewthing/archive/2003/12/12/56061.aspx
关注问题:是否有充分理由赞成使用foo_2
进行版本歧视?正如评论中指出的那样,为什么不在共享结构中使用显式sizeof(struct)
成员?
答案 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)
添加一个断言(最好是在编译时),以确保结构总是实际上有不同的大小。