The C language was created at a time when object file linking systems were very primitive; unfortunately, in many cases, they still are.
Suppose that the following .C file
struct sheep1 {int16_t a, b;}
struct sheep2 {int16_t a; unsigned char b[2];}
int foo1(struct sheep1 bah) { return a+(b & 255)+(b >> 8); }
int foo2(struct sheep2 bah) { return a+b[0]+b[1]; }
int foo2(struct sheep2 bah) { return a+b[0]+b[1]; }
is called from a different .C file
struct sheep1 {int16_t a, b;}
struct sheep2 {int16_t a; unsigned char b[2];}
extern int foo1(struct1 sheep bah);
extern int foo2(struct2 sheep bah);
extern int foo3(struct1 sheep bah);
...
struct sheep1 marysLamb;
struct sheep2 marysLamb;
...
foo1(marysLamb1);
foo2(marysLamb2);
foo3(marysLamb1);
Note that both sheep1
and sheep2
are four bytes long, and both have identical alignment requirements. Nonetheless, the call to foo3
may fail in spectacular fashion.
The code produced for foo1
may expect the caller to put the fields of bah
into two CPU registers, since that would be faster than putting them in memory. When the calling code invokes foo1
, it will put the values in registers as expected, and everything will work as it should.
Because because structure sheep2
includes an array, the calling convention may not allow it to be passed in registers (even though it's small enough that could fit, compilers are generally not equipped to address individual bytes within a register). Thus, the caller would be required to put it into four bytes of physical memory on the stack. When the method is called by code which expects this (as is the case with the invocation of foo2
), the caller will put four bytes on the stack, which the called method will use and (in some environments) remove. Even though foo2
must be invoked differently from foo1
, the compiler will know this and generate code accordingly.
Things may fail spectacularly with the invocation of foo3
, however. The compiler will load the fields of marysLamb1
into registers and call foo3
. That function, however, will expect the caller to have pushed four bytes of data on the stack. It will thus read four bytes of who-knows-what from the stack, perform the indicated arithmetic on them. It may then, in some environments, pop those four bytes from the stack as it returns. If the caller had something important in those four bytes of stack (as would typically be the case), there's no telling what could happen.
On the systems were C was originally designed, the linker would know nothing about methods foo1, foo2, and foo3 other than their names and where the code for them was placed. Some newer systems, however, in an effort to avoid problems such as the above, define ways by which compilers can supply more information about what the parameters methods are expecting from callers, or the parameters that callers are going to supply; linkers can then make use of this information to provide warnings or errors in cases where a caller wouldn't supply the correct thing. The maintainers of the C standard to not wish to discourage such efforts, but they wish to avoid cases where a linker squawks at code which would--but for such squawking--work just fine. In cases where there are potential differences, it is necessary for the standard to specify the cases where linkers are allowed to be "picky" and cases which linkers are expected to accept even if they might seem "suspicious".