Handmade Hero»Forums»Code
11 posts
Day 11 - "invisible" types?
Edited by Kknewkles on
When Casey started separating source code into multiple files, I noticed that handmade.h and handmade.cpp didn't have any hint about #define internal static type redefinition(or aliasing? what's the common terminology?), and yet everything compiled nicely with no complaints.
From what I gather there's some special key that goes into manual compiling that lets #define stuff be seen across multiple files(I could be VERY wrong). What are oafs like me, that use VS for everything? :)

(Please hold possible suggestions to move to Emacs/text editor + manual building.
Although I do suspect I will at the very least learn how to do that, if not swap altogether at some point.)
Abner Coimbre
320 posts
Founder
Day 11 - "invisible" types?
Edited by Abner Coimbre on Reason: Edited for clarity.
The preprocessor runs independently for each file. Should you have #define internal static on a single source file, then no other knows about it. You would have to type it again for the rest, and naturally no redefinition warning occurs.


From what I gather there's some special key that goes into manual compiling that lets #define stuff be seen across multiple files...

What you can probably do is inject a #define at the project level:

1
cc -Dinternal=static main.c database.c resources.c -o my_game

Where cc is your compiler. All instances of 'internal' would be replaced by 'static' for every file. No need to type in #define on each compilation unit. Normal use for this is cross-compilation: pass in something like -DWINDOWS=1 so all your code knows you're compiling under Windows. This is known as a compile-time #define.

An alternative is to take all the preprocessor directives that a lot of your files share and place them in a header file:

<my_defines.h>
1
2
3
4
5
6
7
8
#ifndef DEFINE_H
#define DEFINE_H

#define internal static
#define TRUE 1
#define FALSE 0

#endif /* DEFINE_H */

<main.c>
1
2
#include "my_defines.h"
// Rest of the file will now have internal replaced with static, TRUE with 1, and FALSE with 0

<database.c>
1
2
#include "my_defines.h"
// Rest of the file will now have internal replaced with static, TRUE with 1, and FALSE with 0

<resources.c>
1
2
#include "my_defines.h"
// Rest of the file will now have internal replaced with static, TRUE with 1, and FALSE with 0


Notice the preprocessor is still run on three separate occasions, once for each file.
11 posts
Day 11 - "invisible" types?
Edited by Kknewkles on
Stupid me. Make another header for custom types - I can swear I had that thought.
Thanks.

So I guess Casey's magic stands unrivaled by Visual Studio!
11 posts
Day 11 - "invisible" types?
Edited by Kknewkles on
I've forgotten the second part of my question, one that really baffles me.

Why the hell does it all work? "Internal" means precisely that you CAN'T use stuff from other files. Is it because it's a struct(game_offscreen_buffer is)? Is it because it's in a header?
Confusion!


Man, I must say it's such a joy to just get down to coding.
I just had a terrifying memory leak by misplacing a few pointers - the gradient quickly gobbled up hundreds of RAM megabytes and stalled my PC for a couple minutes. And I feel clearly disproportionate excitement about fixing something so minor. Joy. JOY.
Abner Coimbre
320 posts
Founder
Day 11 - "invisible" types?
Edited by Abner Coimbre on
Short Answer

#define internal static will replace every word called internal back to static, and this happens before the compiler starts running. It has nothing to do with defining types or anything.

The typical use for #define is to use it for an unchanging value:

my_game.c
1
2
3
4
5
6
7
8
9
// Headers...

#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600

int main()
{
    // Use SCREEN_WIDTH and SCREEN_HEIGHT as many times as you need to
}

Instead of directly using the values 800 and 600 all the time. It saves typing if you want the width and height to be different in the future.

Long Answer

Consider this file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>

int scores[5]; 
	
static void print_high_scores()
{
    for (int i = 0; i < 5; ++i)
    {
        printf("Score #%d: %d\n", i+1, scores[i]);
    }
}

int main()
{
    // Manually populate high scores
    scores[0] = 100;
    scores[1] = 80;
    scores[2] = 70;
    scores[3] = 50;
    scores[4] = 10;

    print_high_scores();
    return 0;
} 	

Notice how scores is in global scope. If I have another file in my project, it can access it by typing in:

extern int scores[5];

However, if you only want scores to be accessible in the original file (so only main() and print_high_scores() use it, for example), then you can add the static keyword:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>

static int scores[5]; // Now accessible in this file only 
	
static void print_high_scores()
{
    for (int i = 0; i < 5; ++i)
    {
        printf("Score #%d: %d\n", i+1, scores[i]);
    }
}

int main()
{
    // Manually populate high scores
    scores[0] = 100;
    scores[1] = 80;
    scores[2] = 70;
    scores[3] = 50;
    scores[4] = 10;

    print_high_scores();
    return 0;
} 	

Notice how print_high_scores() was already static, meaning it is also something restricted to file scope (i.e. no other file will know about it). Now, static can also be used in another context, and to avoid confusion, we can do the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>

#define internal static

internal int scores[5];
	
internal void print_high_scores()
{
    for (int i = 0; i < 5; ++i)
    {
        printf("Score #%d: %d\n", i+1, scores[i]);
    }
}

int main()
{
    // Manually populate high scores
    scores[0] = 100;
    scores[1] = 80;
    scores[2] = 70;
    scores[3] = 50;
    scores[4] = 10;

    print_high_scores();
    return 0;
} 	

"internal" makes it more clear that scores and print_high_scores() are internal to the file.

But in the end, #define is an instruction for the preprocessor (a program that runs before compiling your code) to replace all instances of the text "internal" back to "static". Therefore, the compiler will be seeing static all over again in the code, not internal. We only did this for clarity in reading the code.

Closing Statement

Now again, the preprocessor runs from the start every time it compiles a new file (or, more technically, a compilation unit)... so you need to write:

#define internal static

for every compilation unit that wants the word internal to be replaced back to static. See my previous post for more info.
11 posts
Day 11 - "invisible" types?
Edited by Kknewkles on
I understand what define does.
What I don't understand is how come the variable in a header file, prefixed with static, is seen and used from another file with no problems. ...Oh. Because addresses and pointers.
Mārtiņš Možeiko
2559 posts / 2 projects
Day 11 - "invisible" types?
Edited by Mārtiņš Možeiko on
In such case you are accessing completely different variable.
If you put something as "static" in header file and include in two different .c or .cpp files, then this "static" thing (variable or function) gets instantiated by compiler two times in separate places (two translation units).

You should think of header files as something like this:
* preprocessor sees #include statment so it copies whole content of include'd file
* it pastes content in place where #include statement is.

So everything that was in #include'd file gets compiled two times in two separate .c/.cpp files. Try to compile this and you'll see that this static variable in completely different variable in each file:

a.cpp
1
2
3
4
5
6
7
8
9
#include <stdio.h>
static int x = 10;
void SetAndPrintXinA(int new_value)
{
  printf("a.cpp: x variable is in %p location\n", &x);
  printf("a.cpp: x value before = %d\n", x);
  x = new_value;
  printf("a.cpp: x value after = %d\n", x);
}


b.cpp
1
2
3
4
5
6
7
8
9
#include <stdio.h>
static int x = 20;
void SetAndPrintXinB(int new_value)
{
  printf("b.cpp: x variable is in %p location\n", &x);
  printf("b.cpp: x value before = %d\n", x);
  x = new_value;
  printf("b.cpp: x value after = %d\n", x);
}


main.cpp
1
2
3
4
5
6
7
8
void SetAndPrintXinA(int new_value);
void SetAndPrintXinB(int new_value);

int main()
{
  SetAndPrintXinA(11);
  SetAndPrintXinB(22);
}


Step trough code in debugger, and when you are in SetAndPrintX.. functions examine address of x variable (&x) in watch window. You'll see that it is different in each translation unit.
Abner Coimbre
320 posts
Founder
Day 11 - "invisible" types?
Correct. And sorry for misunderstanding your second question!
11 posts
Day 11 - "invisible" types?
Thanks for your tips, guys :) Nothing special, but it did throw me for a loop a bit.
And well, one of the rare things that actually makes my darn lazy brain work is asking someone the questions. :D