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:
- 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. Too bad, considering that the compiler expects to find it unchanged, find the parameters that it put on the stack, can access local variables at offsets from esp and try to clean up by adding to esp 8.
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 (: