How does sizeof (struct) help ensure compatibility with ABI?

Assume that the C library needs to share the details of the structure with the application code and must support backward compatibility of the API and ABI. He tries to do this by checking the size of the structure passed to him.

Let's say you need to update the following structure. In library version 1

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

In version 2, the library is updated:

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

Now, version 2 library wants to check if the application passes the new foo_2 or the old foo as an arg argument to the function. It is assumed that the application has set arg.size to sizeof(foo) or sizeof(foo_2) and will try to find out if the application code is running version 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. } 

I wonder why this will not fail. In GCC 4.6.3, with the -O3 flag, both sizeof(foo) and sizeof(foo_2) are 24. So, would the library code v2 not understand if the application is passing a structure like foo or foo_2 ? If so, how did this approach seem to be used?

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


Follow the question: is there any good reason to use sizeof(struct) for version discrimination? As pointed out in the comments, why not use an explicit version member in the overall structure?

+5
source share
3 answers

To fit your observations, I suppose

  • char* has size 8 and alignment 8.
  • int has size 4 and alignment 4.
  • Your implementation uses optimal packaging.

You are absolutely right that in this case your old and new structure will have the same size, and your version identifier is the size of the structures, updating is a change in the ABI. (A few logical errors are also syntax errors, and the former are not diagnosed by the compiler).

Only changes in the structure that lead to a larger size, with a new structure containing all the fields of the old, with the same offsets, can be ABI-compatible according to this scheme: Add some dummy variables.


There is one opportunity that can save you a day:

  • If the field contains a previously invalid value, this may indicate that something else might be interpreted as a difference.
+2
source

I suggest using an intermediate structure. For instance:

 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; 

In my version of library 2, I exported the following function by name:

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

Then in the main application I would do:

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

As usual, security checks (for memory allocation, for example) are not included for readability of the code.

In addition, I would use stdint.h to ensure binary data files are compatible (to make sure that the sizes of int , double , char* etc. are the same for different architectures). For example, instead of int I would use int32_t .

+1
source

If you want to use this scheme to distinguish between different versions of your API, you just need to make sure that different versions of the structure have different sizes.

To do this, you can try to make foo smaller by forcing the compiler to use denser packaging, or you can make foo_2 bigger by adding extra (unused) fields.

In any case, you should add a statement (preferably at compile time) for sizeof(foo) != sizeof(foo_2) to make sure that the structures always have different sizes.

+1
source

Source: https://habr.com/ru/post/1203031/


All Articles