C Fundamentals

Preprocessor Macros

C Fundamentals

Lesson Description

The "Preprocessor Macros" Lesson is part of the full, C Fundamentals course featured in this preview video. Here's what you'd learn in this lesson:

Richard introduces #define, another preprocessor directive. Anything starting with a "#" is a preprocessor directive, and the define directive allows variables and functions to be defined and copied throughout the program before compile time. Functions defined this way are called preprocessor macros.

Preview
Close

Transcript from the "Preprocessor Macros" Lesson

[00:00:00]
>> Richard Feldman: Unfortunately, this doesn't quite work. So if you have a thing marked as a const in C, first of all, depending on the C compiler, some of them will or will not let you use these in a switch. But even assuming that you're using one of the C compilers, it does allow you to use a constant in a switch.

[00:00:18]
This still won't work, because unfortunately, one of the rules of const is that if you're using it at like the top level of the program, you're actually just not allowed to call arbitrary functions like toint at that time, you just can't do it. One of Zig's banner features is called comptime, comptime and Zig does a whole bunch of different things, and it's short for compile time.

[00:00:36]
One of the cool things that it does is that it allows you to basically just run zig functions at compile time like this. So in zig, this would actually just work fine, but in C, unfortunately, it does not. So basically what we want is we wanna make a constant at compile time.

[00:00:52]
So, one of the ways that we can work around this is by converting it from a constant to something else. So here is an example of our code from the previous section. We said like listening on local host port, whatever. We didn't actually do this in the example, but a really obvious thing that you might wanna do is to make a concept for the port so that you can just change it in one place instead of having to go and make sure that the string that we're printing out is, you know, coordinated with that thing.

[00:01:16]
We'd have to change it in two places, that would be annoying, let's just define it as a constant. Another thing you can do is you can use pound define. Now so far, we've only seen one instruction that starts with a pound, which is pound include, which we've been using for imports, importing libraries and stuff.

[00:01:33]
Everything that starts with a pound is a preprocessor directive. And what that means is essentially that, C actually has these two distinct phases to the compiler. First, there's the preprocessor, which basically runs before the actual compiler proper. And the preprocessor's job is to essentially to just sort of copy and paste a bunch of strings together, different parts of your file.

[00:01:51]
It has very, very minimal logic that you can actually run, we'll see a tiny, tiny bit of that later, but it's not like a full programming language. Whereas zig, one of the design things of zig was to get rid of the concept of preprocessor directives and instead just be like, yeah, just just write more Zig code.

[00:02:06]
Just write the language, and then you can just mark it as like, this is running at compile time versus at runtime. But C didn't have that, so C is basically, okay, one of the things you can do is you can say, pound, define port and 8080. Now what does this mean?

[00:02:18]
Well, very similarly to how pound include meant take this library and just copy and paste the entire thing into here exactly as if I had copy pasted it by hand in here. Pound define, same thing, it's basically like everywhere you see a port, every time I reference this, like in my code, copy-paste this exact thing right here into the source code.

[00:02:37]
So literally this code right here it is as if, instead of P-O-R-T, I had written 8080 that's what this does. So this is not a type checked constant that's gonna give me an error if I mess up, no, no, no, no, none of that. It's just like, yeah, this is exactly the same thing as if I had written 8080 right here, instead of P-O-R-T, that is what pound defined does.

[00:02:58]
Now you can also take this a step further and write functions with Conde fine. So the way these functions work is essentially you say, here's the name of the function. In this case, I've called it four char because it takes four chars and then does the exact bit wide, bit shifting and bitwise, or trick that we were doing with HTML earlier.

[00:03:16]
The backslash, by the way, is just C's way of saying, I want the thing that I'm defining here to go on a different line, you have to be kind of careful with new lines and stuff in these pound defines because they don't have curly braces delivering them functions do.

[00:03:30]
So this backslash is basically a way of saying, yeah, just pretend this was on the previous line [COUGH] and essentially what this will do is, much how P-O-R-T gets substituted in. This is basically saying, everywhere you call this preprocessor macro, that's what this is called, this is a preprocessor macro.

[00:03:48]
Passing whatever arguments you want for these things, it's gonna be exactly as if you had each of those arguments in these different positions, followed by whatever is surrounding them. So it's just a little bit more of an advanced substitution, but again, this is all just happening before the compiler even starts.

[00:04:05]
So when the compiler sees your code, it is as if you had not even written these in the first place. It's just yeah, these things are just like already copy pasted in at every single place that you use these. So [COUGH] at this point we can now say, okay, cool.

[00:04:19]
Constant HTML equals a four char, and now we actually are working around that limitation of not being able to do this at compile time. And as a bonus, if you are in one of those situations where you're using a compiler that does not support constant for this, on some Linux distributions that I tried, it didn't support it out the box.

[00:04:43]
Some did, some didn't, but anyway, if you're using one of the ones that doesn't, then instead of this constant, you can just do another pound to find right here. And then you've worked around that too because you're basically defining this thing to be equal to this pre-processor macro.

[00:04:56]
So at the end of the day, you're ending up with this entire thing being substituted every single time you reference HTML, CSS, or JS. Now that might be kind of conceptually clunky, but from a code readability perspective, again, if we are doing this very silly hack that is not really strictly necessary and maybe more of a performance optimization that is reasonable for a thing on a workshop, but also maybe kinda nice excuse to learn about bit shifts and preprocessor macros.

[00:05:18]
But if you're doing this type of thing, this is actually quite a reasonable way to do it. It does give you, at least when I'm looking at this code right here, HTML convert this thing to the integer thing. I can see that it's the right letters are here, it's a lot easier to debug than back when we were dealing with these weird hard coded numbers.

[00:05:36]
And this still lets me use these things in a switch statement. Cool, and yeah, so this will result in exactly the bits in memory that we want, and then we get to do all the nice comparisons, okay. So this is going to be not only nicer to read, but also quite fast, because, again, compared to the stir comp way, which, honestly, if I were just writing this for personal use, I would probably go with the stir comp way.

[00:05:58]
I wouldn't bother to do this, but in addition to allowing me to use an actual switch statement on this, it is gonna run very fast because we aren't having to go through in March one, bite at a time through each of these, okay? Worst noting though that if you are using a programming language that's not c, almost certainly this is what is actually the equivalent of what your language Just doing.

[00:06:22]
If you have a language that supports saying extension double equals png, this is almost certainly what that's compiling to. It's compiling to the much less performant and much less, I don't know, nicer for the CPU representation, it's doing much, much more operations. And at the end of the day, the fundamental reason that we're able to get better performance in sea here is that we are doing some pretty heinous memory tricks.

[00:06:50]
We're reinterpreting these bytes, we're adding these extra zero bytes if necessary to the extension over here we're doing these pre-processor macros. All this stuff, we're reinterpreting the ones and zeros to be integers at runtime. Only if they happen to be four bytes or less, we're relying on the domain knowledge that in the particular case of a static HTTP server, everything that we need happens to be under four bytes.

[00:07:12]
And thankfully.woff happens to be application octet stream, which is the default we want anyway, all of these things, you can look at that, and you can say, this is a giant pile of hacks. This is all very brittle, why don't you just use stir, comp, a normal programmer, which is all very fair.

[00:07:24]
But the point is, with C, this is there if you need it, if you're trying to write something like postgres or an operating system, and you really, really need that absolute last tiny drop of performance. Because something like this is in a really hot loop, and it's the stakes are a lot higher than a silly little static HTTP server.

[00:07:42]
This is one of the reasons that C has the absolute highest performance ceiling you can do tricks like this if you need to, not saying you need to all the time, but if you really, really need to, and you want to just be the fastest thing you possibly can be.

[00:07:54]
These type of tricks are things that C lets you do, and this is where the trade-off of all of the downsides we've seen, and we've seen plenty of downsides of C's memory unsafety and all the stuff that it lets you do. At the end of the day, if you're trying to think back to some of these original games that ran on PCs and the 90s that were doing things with graphics that people thought was just totally wild, quake and stuff doom, they were doing a lot of these kinds of tricks, and in some cases, even going further to do.

[00:08:21]
Do assembly language tricks just to squeeze every last drop of performance they could out of the processors. And the results they got were really amazing. If you want to do the most hardcore performance optimization stuff you can possibly imagine, there's this whole world of this kind of stuff that exists in C that most high level languages just never even get into because it's wildly unsafe [LAUGH] and that is the downside that comes with it.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now