About Implicit Ads, Backward Compatibility, and ABI
Initial data : C language, gcc4.4, x86, GNU / Linux
struct.h :
ac :
bc :
makefile :
Question : what will be printed at runtime? Think at least a minute. Better take a debugger and walk around this simple program.
Answer : it is not known in advance, and all due to the fact that its prototype is not available at the call point of function f. At the same time, the passed parameters exactly correspond to what the called function expects .
On my system, for example, the output is as follows:
Of course, the absence of a prototype at the point of its call is visible to the naked eye, but why does the usually harmless “implicit declaration of function” have such grave consequences in this case?
All thanks to the ABI that gcc follows.
ABI defines many aspects of what the language standard calls "implementation specific" or is taken for granted. In particular, i386 ABI determines the type of machine stack when a function is called. For functions returning structures / unions, the following is prescribed:
As you can see, in comparison with a regular call, one more parameter is added, which the called function removes from the stack - the address to which the return value will be written.
What happens in the case under consideration (it is drawn that the calling function placed on the stack, what the called function thinks about it, and that on the stack after returning):
The following anomalies are evident:
Here is a story.
Instead of a conclusion :
Compiler warnings may be more important than they are usually thought of.
Knowing ABI of your platform sometimes makes life easier.
But in x86_64 there is no such effect.
Have a nice day (:
struct.h :
struct S { int * a; int * b; };
ac :
#include#include "struct.h" struct S f (struct S v) { printf ("va =% d, vb =% d \ n", * va, * vb); return v; }
bc :
#include#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. Better take a debugger and walk around this simple program.
Answer : it is not known in advance, and all due to the fact that its prototype is not available at the call point of function f. At the same time, the passed parameters exactly correspond to what the called function expects .
On my system, for example, the output is as follows:
va = 2, vb = 0 a = 8559808, b = -2398008
Of course, the absence of a prototype at the point of its call is visible to the naked eye, but why does the usually harmless “implicit declaration of function” have such grave consequences in this case?
All thanks to the ABI that gcc follows.
ABI defines many aspects of what the language standard calls "implementation specific" or is taken for granted. In particular, i386 ABI determines the type of machine stack when a function is called. For functions returning structures / unions, the following is prescribed:
Position After calling 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 regular call, one more parameter is added, which the called function removes from the stack - the address to which the return value will be written.
What happens in the case under consideration (it is drawn that the calling function placed on the stack, what the called function thinks about it, and that on the stack after returning):
Calling Called 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 evident:
- the called function sees the parameters with a shift of 1 (va "inside" = vb "outside");
- the called function overwrites with its return value the memory at the address numerically equal to the value of the first argument (garbage in va);
- after returning, the stack pointer is shifted 1 word back. It’s very bad, considering that the compiler expects to find it unchanged, to find the parameters that it pushed on the stack to be unchanged, it can access local variables at offsets from esp and try to perform the cleanup by adding to esp 8.
Here is a story.
Instead of a conclusion :
Compiler warnings may be more important than they are usually thought of.
Knowing ABI of your platform sometimes makes life easier.
But in x86_64 there is no such effect.
Have a nice day (: