📜 ⬆️ ⬇️

About implicit ads, backwards compatibility and ABI

Baseline : C, gcc4.4, x86, GNU / Linux

struct.h :
 struct S
 {
     int * a;
     int * b;
 };

ac :
 #include <stdio.h>
 #include "struct.h"

 struct S f (struct S v)
 {
     printf ("va =% d, vb =% d \ n", * va, * vb);
     return v;
 }

bc :
 #include <stdio.h>
 #include "struct.h"

 int main ()
 {
     int a = 1, b = 2;
     struct S v = {& a, & b};
     f (v);
     printf ("a =% d, b =% d \ n", a, b);
     return 0;
 }

makefile :
 all: test
         ./test

 test: ac bc struct.h
         gcc ac bc -g -o test


Question : What will be printed at runtime? Think at least a minute. And better take a debugger and walk around this simple program.

Answer : it is not known in advance, and this is all due to the fact that its prototype is not available at the f function call point. In this case, the passed parameters correspond exactly to what the called function expects .
')
On my system, for example, the output is:
 va = 2, vb = 0
 a = 8559808, b = -2398008

Of course, the absence of a prototype at the point of its challenge is visible to the naked eye, but why does the usually innocuous “implicit declaration of function” have such grave consequences in this case?

All thanks to ABI , which follows gcc.
The ABI defines many aspects of what the standard language refers to as “implementation specific” or is taken for granted. In particular, the i386 ABI determines the appearance of the machine stack when calling a function. For functions that return structures / unions, the following is prescribed:

 Position After Call After Returning Position

 4n + 4 (% esp) |  word n ​​|  |  word n ​​|  4n-4 (% esp)       
            |  ... | |  |  ... | |
    8 (% esp) |  word 1 |  |  word 1 |  0 (% esp)
            | ------------ |  | -------------- |
    4 (% esp) |  address |  |  not defined |
            |  result |  |  |
            | ------------ |  |  |
    0 (% esp) |  address |  |  |
            |  return |  |  |
            | ------------ |  | -------------- |



As you can see, in comparison with a normal call, one more parameter is added that removes the called function from the stack — the address at which the return value will be written.

What happens in the case in question (drawn, that the calling function placed on the stack, what the callee thinks about it and what is on the stack after the return):
             Calling Immediately
             function function after return
                            (expected)
   16 (% esp) |  local |  |  |  |  local |
            |  |  | ------------ |  |  |
   12 (% esp) |  variables |  |  vb |  |  variables |
            | ============ |  |  |  | -------------- |
    8 (% esp) |  vb |  |  va |  |  vb |  0 (% esp)
            | ------------ |  | ------------ |  | ============== |
    4 (% esp) |  va |  |  address |  |  not defined |
            |  |  |  result |  |  |
            | ============ |  | ------------ |  |  |
    0 (% esp) |  address |  |  address |  |  |
            |  return |  |  return |  |  |
            | ------------ |  | ------------ |  | -------------- |


The following anomalies are present:

Here is a story.

Instead of concluding :

Compiler warnings may be more important than they are thought of.
ABI's knowledge of its platform sometimes makes life much easier.

And in x86_64 there is no such effect.

Have a nice day (:

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


All Articles