views:

383

answers:

3

I've been using the following code to create various struct, but only give people outside of the C file a pointer to it. (Yes, I know that they could potentially mess around with it, so it's not entirely like the private keyword in Java, but that's okay with me).

Anyway, I've been using the following code, and I looked at it today, and I'm really surprised that it's actually working, can anyone explain why this is?

In my C file, I create my struct, but don't give it a tag in the typedef namespace:

struct LABall {
    int x;
    int y;
    int radius;
    Vector velocity;
};

And in the H file, I put this:

typedef struct LABall* LABall;

I am obviously using #include "LABall.h" in the c file, but I am NOT using #include "LABall.c" in the header file, as that would defeat the whole purpose of a separate header file. So, why am I able to create a pointer to the LABall* struct in the H file when I haven't actually included it? Does it have something to do with the struct namespace working accross files, even when one file is in no way linked to another?

Thank you.

+1  A: 

In

typedef struct A* B;

since all pointers' interfaces are the same, knowing that B means a pointer to a struct A contains enough information already. The actual implementation of A is irrelevant (this technique is called "opaque pointer".)

(BTW, better rename one of the LABall's. It's confusing that the same name is used for incompatible types.)

KennyTM
Really, I thought the fact that only one of them existed in the struct namespace, and one of them existed in the typedef namespace would be enough to distinguish them? Oh well, I'll do that, thank you.
Leif Andersen
@Leif: It's fine for the *compiler*, but the *users* will be confused, especially those using C++.
KennyTM
+2  A: 

A standard pattern for stuff like that is to have a foo.h file like

typedef struct _Foo Foo;

Foo *foo_new();
void foo_do_something(Foo *foo);

and a foo.c file like

struct _Foo {
   int bar;
};

Foo *foo_new() {
    Foo *foo = malloc(sizeof(Foo));
    foo->bar = 0;
    return foo;
}

void foo_do_something(Foo *foo) {
    foo->bar++;
}

This hides all the memory layout and size of the struct in foo.c, and the interface exposed via foo.h is completely independent of those internals: A caller.c which only does #include "foo.h" will only have to store a pointer to something, and pointers are always the same size:

#include "foo.h"

void bleh() {
    Foo *f = foo_new();
    foo_do_something(f);
}

Note: I have left freeing the memory as an exercise to the reader. :-)

Of course, this means that the following file broken.c will NOT work:

#include "foo.h"

void broken() {
    Foo f;
    foo_do_something(&f);
}

as the memory size necessary for actually creating a variable of type Foo is not known in this file.

ndim
+2  A: 

Since you're asking a precise reason as to "why" the language works this way, I'm assuming you want some precise references. If you find that pedant, just skip the notes...

It works because of two things:

  • All pointer to structure types have the same representation (note that it's not true of all pointer types, as far as standard C is concerned).[1] Hence, the compiler has enough information to generate proper code for all uses of your pointer-to-struct type.

  • The tag namespace (struct, enum, union) is indeed compatible accross all translation units.[2] Thus, the two structures (even though one is not completely defined, i.e. it lacks member declarations) are one and the same.

(BTW, #import is non-standard.)

[1] As per n1256 §6.2.5.27:

All pointers to structure types shall have the same representation and alignment requirements as each other. Pointers to other types need not have the same representation or alignment requirements.

[2] As per n1256 §6.2.7.1:

two structure, union, or enumerated types declared in separate translation units are compatible if their tags and members satisfy the following requirements: If one is declared with a tag, the other shall be declared with the same tag. If both are complete types, then the following additional requirements apply: [does not concern us].

rz0
"(BTW, #import is non-standard.)"Woops, I was mixing java...I meant to type #include. :) I'll fix that.
Leif Andersen