1

I was reading about type promotion in C on this page and found something more about struct/union/enum:

6.12.2 Separate Compilation Compatibility

Since each compilation probably looks at different source files, most of the rules for compatible types across separate compiles are structural in nature:

Matching scalar (integral, floating, and pointer) types must be compatible, as if they were in the same source file.

Matching structures, unions, and enums must have the same number of members. Each matching member must have a compatible type (in the separate compilation sense), including bit-field widths.

Matching structures must have the members in the same order. The order of union and enum members does not matter.

Matching enum members must have the same value.

An additional requirement is that the names of members, including the lack of names for unnamed members, match for structures, unions, and enums, but not necessarily their respective tags.

My question: In which sense are struct "equals"? where and why could one use this? could someone give also a code example?

obviously the below code doesn't compile with no warnings without a cast. And since C isn't a strongly-typed language it might work with any kind of struct. So I don't really see where it may be useful;

struct foo_t
{
    int a;
    int b;
};

struct baa_t
{
    int a;
    int b;
};

void print(struct foo_t*);

int main(void)
{
    struct foo_t a = {1,2};
    struct baa_t b = {3,4};

    print(&a);
    print(&b); //cast needed
    return 0;   
}


void print(struct foo_t *f)
{
    printf("a = %d\r\n", f->a);
    printf("b = %d\r\n", f->b);
}
Jack
  • 16,276
  • 55
  • 159
  • 284
  • C not strongly typed? – mafso Aug 03 '14 at 08:50
  • @mafso Strongly typed can [mean many different things](http://stackoverflow.com/a/9929697/395760) but it's usually something separate from *static* typing, which you are probably thinking of right now. –  Aug 03 '14 at 08:57
  • @mafso: Yeah. Any pointer is converted to other pointer type just casting it. – Jack Aug 03 '14 at 08:58
  • Well, a pointer cast is an _explicit_ request to ignore the static type (and is needed _because_ C is strongly typed), at least, how I understand the term. But yes, I see the definition problems… I just was a little confused in combination with the code (which is an example not working because of type mismatch, so at least it's not an example for C being weakly-typed). – mafso Aug 03 '14 at 09:26

2 Answers2

3

This mostly pertains to having multiple .cpp files reference the same header file and use a struct of it (or worse, both independently define it).

Each .cpp file will be compiled in a completely separate, independent process and neither knows anything about the other (ignoring LTCG), but they will both handle the struct in the same way so a struct used in one .cpp file will have the exact same layout as in another one.

The same applies to a struct inside a struct - one .cpp file might have a struct that contains another one, and another .cpp might only be exposed to a pointer to the inner struct with no knowledge of what's around it. Still, the other .cpp will need to be able to operate on it, so the actual layout of the data members needs to match exactly.

EboMike
  • 76,846
  • 14
  • 164
  • 167
  • So, if I have two .c files(two translation units) with same tag name but different members in that struct the compiler will compare them find difference and give an error. If there's no differecen it will just work, correct? – Jack Aug 03 '14 at 09:00
  • 1
    The compiler won't necessarily compare them (and usually can't when compiled separately to object files, that's why headers usually are used to declare types used in separate translation units, to be sure they are equal in all of them). They must match, otherwise the behaviour is undefined. – mafso Aug 03 '14 at 09:32
2

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".

supercat
  • 77,689
  • 9
  • 166
  • 211