Handmade Hero » Forums » Code » Incomplete Types.
jpmontielr
Juan Pablo Montiel
5 posts
#13581 Incomplete Types.
3 weeks, 3 days ago

Hi there! I recently learned about "incomplete types" in C/C++. You can't declare a struct, use it, and _then_ define it. I think this is kind of crazy since you can declare a function, call it, and then define its body.
But in day 011, when Casey explains the architecture style he doesn't use (virtualising the OS to the game), he says you declare an opaque structure, like "platform_window" in the shared header, and then define it in the win32/linux/mac_game.cpp file.
How is this possible with the "incomplete types" situation? Thanks in advance!
mmozeiko
Mārtiņš Možeiko
1520 posts
1 project
#13582 Incomplete Types.
3 weeks, 3 days ago Edited by Mārtiņš Možeiko on Nov. 22, 2017, 9:12 p.m.

As long as your code does not need access members of structure, or know its size, then compiler can generate code just fine.

For example:
1
2
3
4
5
6
7
8
9
struct Object; // no idea what it contains

// functions receives address to obj
void b(Object* obj);

void f(int x, Object* obj)
{
  b(obj);
}
Compiler can easily generate code for f. Because it does not need to access members of obj. It need to use only pointer to it which, if you think about it, it is just a 32-bit or 64-bit number. Compiler puts value into argument for b and calls b. No magic. Here's actual assembly for this example: https://godbolt.org/g/GmnbZz As you can see, compiler simply moved value from rsi (second argument "obj") into rdi, before jumping to function b. rdi is register where to put value for first argument when you call function. And it did not need to access contents of obj (dereference pointer from rsi register).

Only place where you actually need definition of structure is when you want to access members (obj->x) or know the size of object - "new Object" or "sizeof(Object)". Then compiler needs to know more information to emit correct code.
jpmontielr
Juan Pablo Montiel
5 posts
#13583 Incomplete Types.
3 weeks, 3 days ago

Ohh, I get it now, for some reason I assumed you could forward-declare anything at all, but assembly always has the last word. So a pointer is fine because an address is a fixed-size number. Is this the same in other languages? Thanks a lot by the way!
mmozeiko
Mārtiņš Možeiko
1520 posts
1 project
#13584 Incomplete Types.
3 weeks, 3 days ago Edited by Mārtiņš Možeiko on Nov. 22, 2017, 9:13 p.m.

Depends on language. Some languages don't care about order of declaring stuff. Like C# or Java. You can put types in whatever order you want and in whatever files you want. There's no need to declare anything. Compile will parse everything first and then resolve types - backward or forward, it doesn't matter.
pragmatic_hero
57 posts
#13595 Incomplete Types.
3 weeks, 1 day ago

Forward declarations and the ordering is necessary to be able to build a single-pass compiler.
That allows to compile a C program with less working memory (and probably slightly faster) which mattered *decades ago*.

C being a single-pass compiler is a bit of a misnomer since you'd still normally have to run the pre-processor first.

I believe the only compiler which actually takes advantage of this and compiles C in a single pass is TCC (excluding the preprocessor that has to be done separately anyway).

Every other C/C++ compiler is super slow (DESPITE the single-pass nature of the language).
ratchetfreak
322 posts
#13598 Incomplete Types.
3 weeks, 1 day ago

pragmatic_hero
Forward declarations and the ordering is necessary to be able to build a single-pass compiler.
That allows to compile a C program with less working memory (and probably slightly faster) which mattered *decades ago*.

C being a single-pass compiler is a bit of a misnomer since you'd still normally have to run the pre-processor first.

I believe the only compiler which actually takes advantage of this and compiles C in a single pass is TCC (excluding the preprocessor that has to be done separately anyway).

Every other C/C++ compiler is super slow (DESPITE the single-pass nature of the language).


you can stream the output of the preprocessor into the C compiler and that will be a proper single pass compiler.

C++ has elements that do away with the single pass nature, for example a class body cannot be compiled in a single pass. You can refer to members that are declared after the place they are used.
pragmatic_hero
57 posts
#13619 Incomplete Types.
2 weeks, 5 days ago Edited by on Nov. 27, 2017, 10:38 a.m.

ratchetfreak

you can stream the output of the preprocessor into the C compiler and that will be a proper single pass compiler.

Is C preprocessor single-pass?
Does the nested macro expansion thing still keep it single-pass?
ratchetfreak
322 posts
#13620 Incomplete Types.
2 weeks, 5 days ago

pragmatic_hero
ratchetfreak

you can stream the output of the preprocessor into the C compiler and that will be a proper single pass compiler.

Is C preprocessor single-pass?
Does the nested macro expansion thing still keep it single-pass?


you'll only need to pass over what you just expanded, so it's still single pass over the input.
Pseudonym
Andrew Bromage
174 posts
1 project
(tbd)
#13645 Incomplete Types.
2 weeks, 3 days ago

ratchetfreak
you can stream the output of the preprocessor into the C compiler and that will be a proper single pass compiler.

You can also make the preprocessor part of the lexical analyser, like Clang does. This is possible because the C preprocessor operates on tokens, not on text.

sub f{($f)[email protected]_;print"$f(q{$f});";}f(q{sub f{($f)[email protected]_;print"$f(q{$f});";}f});