How the C++ Compiler Works

The Cherno · Intermediate ·9y ago

Key Takeaways

Explains the inner workings of the C++ compiler

Full Transcript

hey what's up guys my name is Aero and welcome back to my C++ series So today we're going to learn all about how the C++ compiler works so let's take a step back and think about this for a minute what is the big picture here what is a C++ compiler actually responsible for so we write our C++ code AS text it that's all it is it's just a text file and then we need some way to transform that text into an actual application that our computer can run in going from that text form to an actual executable binary we basically have two main operations that need to happen one of them is called compiling and one of them is called linking in this video we're just going to talk about compiling I've actually made another video specifically covering linking so you might want to check that out as well the link will be in the description below so with that being said the only thing that the C++ compiler actually needs to do is to take our text files and convert them into an intermediate format called an object file those object files can then be passed on to the Linker and the Linker could do all of its linky things but anyway we're talking about compiling here the compiler actually does several things when it produces these object files firstly it needs to pre-process our code which means that any pre-processor statements get evaluated then and there once our code has been pre-processed we move on to more or less tokenizing and paing and basically sorting out this English C++ language into a format that the compiler can actually understand and reason with this basically results in something called an abstract syntax tree being created which is basically a representation of our code but as an abstract syntax tree the the compiler's job at the end of the day is to convert all of our code into either constant data or instructions once the compiler has created this abstract syntax tree it can begin actually generating code now this code is going to be the actual machine code that our CPU will execute we also wind up with various other data such as a place to store all of our constant variables and that's essentially all the compiler does it's not crazy complicated it of course does get very complicated as your actual code complexity grows but that is the gist of it that's all that it does we're going to go ahead and jump into this and take a look at what each stage actually does so that you guys can see how it all works so let's go okay so here we've got a simple Holo World application you might remember this from the house C++ Works video that I recently made we basically just got this main function which calls log which is actually defined inside this log. CPP file and it simply just prints our message to the screen and we wind up with a simple application which says hello world if we pop over into our output directory debug here you can see that it's generated a hell world.exe file and then back in the project directory in debug it's generated a main. obj and log. obj file so what the compiler has done is has generated object files for each of our C++ files for each of our translation units now every CPP file that our project contains that we actually tell the compiler hey compile this CPP file every single one of those files will result in an object file these CPP files are things called translation units essentially you have to realize that c C++ doesn't care about files files are not something that exists in C++ for example in Java your class name has to be tied to your file name and your folder hierarchy has to be tied to your package and there's all of this going on because Java expects certain FS to exist in C++ that is not the case there is no such thing as a file a file is just a way to feed the compiler with source code you're responsible for telling the compiler what kind of file type this is and how the compiler should treat that now of course if you create a file with the extension CPP the compiler is going to treat that as a C++ file similarly if I make a file with the extension C orh the compil is going to treat the C file like a C file and not a C++ file and it's going to treat theh file like a header file these are basically just default conventions that are in place you can override any of them and that's just how the compiler will will deal with it if you don't tell it how to deal with it I could go around making Cherno files and telling the compiler to compile that and that would be totally fine as long as I tell the compiler hey this file is a C++ file please compile it like a C++ file so just remember files have no meaning okay remember that it's important so that being said every C++ file that we feed into the compiler and we tell it this is a C++ file please compile it it will compile it as a translation unit and a translation unit will result in an object file it's actually quite common to sometimes include CPP files in other CPP files and create basically one big CPP file with a lot of files in it if you do something like that and then you only compile the one CPP file you're going to basically result in one translation unit and thus one object file so that's why there's that terminology split between what a translation unit is and what a CPP file actually is because a CPP file doesn't necessarily have to equal a translation unit however if you just make a project with individual CPP files and you never include them in each other then yes every CPP file will be a translation unit and every CPP file will generate an object file now these are actually pretty big you can see this one's 30 kiloby and this one's 46 KOB the reason for that is because we're including IO stream and that has a lot of stuff in it so that's why they're so big and because of that they're actually pretty complicated so before we dive in and take a look at what's actually in the files let's create something a little bit more simple I'm going to right click on source file hit add new item this is going to be a C++ file I'm going to call it math. CPP and hit add over here I'm just going to write a very basic multiply function which multiplies two numbers together I'm not going to include any files in here or anything I'm just going to write a very simple function it's going to return an integer it's going to be called multiply it's going to take two parameters int a and int B it's then going to create a result variable which stores the result of a * B and then we're going to return that result variable nice and simple that's it let's hit control 7 to build that file you can see over here that it's built it successfully I'm actually going to just resize Visual Studio a little bit just to make it easier so now you can see the output window a bit better if we look back into our output directory you can see that we've got this math. obj file now and it's 4 kilobytes before we take a look at what exactly is in that object file let's talk about the first edge of compilation which I mentioned earlier pre-processing during the pre-processing stage the compiler will basically just go through all of our pre-processing statements and evaluate them the ones that we commonly use are include Define if and if def there are also pragma statements which tell the compiler exactly what to do but we'll talk about them in another video so let's take a look at one of the most common pre-processor statements that we have hash include how does that work so hash include is actually really simple you basically specify which file you want to include and then the pre-processor will open that file read all of its contents and just paste it into the file where you wrote your include statement and that's it it's really really simple and I'm about to prove that so back over here I'm just going to make a header file I'm going to right click on header files hit add new item this is going to be a header file and I'm going to call it end brace and then click add okay we're going to wipe out whatever was in this file and I'm just going to type in a closing curve brace that is it that's our entire file so now back in math Topp you can see that we've reasonably written a closing curly bracket here for our multiply function let's go ahead and wipe that out if we compile our file Now by hitting contrl F7 you can see that the compiler complains about the left brace being unmatched at the end of the file so instead of fixing this like a normal person and just adding in our ending brace let's go ahead and include our end brace header file so I'll type in hash include and Brace and there we go let's hit contrl F7 to compile that and look it compiled successfully of course it did because all the compiler did was open this end brace file copy whatever was in here and then just paste it into here and that is it okay header files solved you should now know exactly how they work and how you can use them there's actually a way we can tell the compiler to Output a file which contains the result of all of these pre-processor evaluations that have happened if we bring back our include end brace and then right click on our hello world project and hit properties under cc++ and then pre-processor I'm going to set the pre-process to a file to yes make sure that you're editing your current configuration and platform so that these settings actually apply let's hit okay and then we're going to just hit contrl F7 to build this again if we bring up our output directory you'll see this new I file which is our pre-processed cc++ code let's open this in a text editor so that we can look at it okay so here you can see what the pre-processor has actually generated you can see that our source code had this include end brace and yet the pre-processor code has just inserted our end brace that was in that H file that we've included pretty simple stuff let's add some more pre-processor statements and see what it does so back in our file I'm going to restore our end brace because I'm getting tired at looking at that include I'm then going to come up here and Define something I'm going to define the word integer to be int now don't ask me why I would ever do this this is just an example the defined pre-processor statement will B basically just do a search for this word and replace it with whatever follows so let's replace our int here with the word integer so that we actually return this integer we can also do the same here let's hit contrl F7 and if we look back at our file you can see what's happened it just looks normal int result if we were to do something stupid here like write the word Cho and then hit contrl F7 if we go back to our file you can see it now says Cho multiply and Cho result pretty cool stuff let's play around with this a little bit more let's bring back our int we'll get rid of this Define and instead what I'm going to do is actually just use something called if the if pre-processor statement can let us include or exclude code based on a given condition so over here I'm just going to write if one which in other words means true and then just write an end if at the end of this function I'll hit C contrl F7 we'll go back to our preprocessor file and you can see that it looks exactly like it does here without the if statement if I go back here and I switch this off by writing if0 Visual Studio will fade out our code to show that it's disabled if I hit contrl F7 and take a look at this file we have no code so that's another great example of a pre-processor statement all right one more we'll look at include so let's get rid of our if0 and then I'm going to write include IO stream the massive massive IO stream let's hit control F7 let's look back at this and whoo take a look at this we have in here ,62 3 lines and and there's our function at the very bottom and then look this is all that include IO stream has done now of course IO stream also includes other files so it's kind of like rolling a snowball down a hill you can now hopefully see why those object files were so big because they included iostream and that is a lot of code all right great so that's the pre-processor once that stage has done we can move on to actually compiling our C++ code into machine code if we go back to our project here I'm going to get rid of this include because we don't need it and then I'm just going to hit contr F7 you should now see in our pre-process a file that we're back to normal and in fact I'm actually going to go into hello world Hit properties and then disable that pre-process to a file if you actually read what pre-process to a file does you'll see that it actually does not produce an obj file so we need to disable it so that we can actually build our project let's hit okay and then we'll hit contrl F7 to build our CPP file you'll see that we should now get a math. obj file which is actually up to date so let's take a look at what's actually inside our obj file if we open this file with a text editor you'll see that it's binary which doesn't really help us too much but part of what is actually inside here is the machine code that our CPU will run when we call this multiply function so because this is just binary and completely unreadable let's convert it into a form that might actually be more readable by us there are several ways we could do this but I'm just going to use Visual Studio I'll right click on Hollow world and hit properties under cc++ and then output files I'm going to set assembler output to be set to assembly only listing and then going to hit okay and we're going to hit contrl F7 inside our output directory you should see a math. Asm file let's go ahead and open that with a text editor okay so this is basically a readable result of what that object file actually contains if we go down over here you'll see that we actually have this function called multiply and then we have a bunch of assembly instructions these are the actual instructions that our CPU will execute when we run the function I'm not going to go into huge detail about all of this assembly code now I might save that for another video but if we take a look over here you'll see that our multiplication operation actually happens here basically we load the a variable into our eax register and then we perform an IML instruction which is a multiplication instruction on the B variable and that a variable we're then storing the result of that in a variable called result and then moving it back into eax to return it the reason this kind of double move happens is because I actually made a variable called result and then returned it instead of just returning a * B that's why we get this moving eax into result and then moving result into eax which is completely redundant this is another a great example of why if you set your compiler not to optimize you're going to wind up with slow code cuz it's doing extra stuff like this for no reason if I go back to my code and I actually get rid of that result variable by just returning a * B and then compile this you'll see the assembly looks slightly different because we're just doing imal on B and eax and then that's it eax is actually going to contain our return value now all of this may look like a lot of code and that's because we're actually compiling in debug which doesn't do any optimization and does extra things to make sure that our Cod is as verbose as possible and as easy to debug as possible if we go back into our project and right click here hit properties I'm going to go over here into optimization under the debug configuration let's select maximize speed if you try and compile this now it'll actually give you an error because you'll see that O2 and RTC is actually incompatible so we'll have to go back over here into code generation and make sure that our basic runtime checks are set to default which basically won't perform runtime checks this is basically just code that the compiler will insert to help us with debugging let's hit contrl F7 and look at that assembly file again wow that looks a lot smaller we've basically just got our variables being loaded into a register and then the multiplication and then that's it pretty simple stuff you should now have a basic idea of what the compiler actually does when you tell it to optimize it optimizes this is a pretty simple example so let's take a look at something a bit more advanced we'll take a look at a slightly different example in which case we don't actually take anything in here but I decide to do something like 5 time 2 we'll save that file I'll go into my properties and make sure that I disable optimization so let's hit contr F7 and take a look at our file you can see that what it's done is actually really simple it's simply moved 10 into our eax register which is the register that will actually store our return value in so if we take a look at our code again it's basically just simplified our 5 * 2 to be 10 because of course there's no need to do something like 5 * 2 two constant values at runtime this is something called constant folding where anything that is constant that can be worked out at compile time is let's make things more interesting by involving another function so for example I'm actually going to write a log function which is going to log a certain message of course I don't actually want to make it log anything because that would mean I would have to include iostream which will drastically complicate this so I'm just going to get it to return that message that it receives over here in multiply I'm going to call log with the word multiply I'm going to change this back to be a and B and we'll return a * B let's hit contr F7 all right so let's take a look at what our compiler has generated if we scroll down a bit you'll see that we've got this log function which doesn't really do much but this actually will just return our message you can see that it's moving our message pointer into eax which is our return register as we've established so this is the log function if we scroll up a little bit you'll see the multiply function and then all we have here is a call to log so right before we actually do multiplication by using the imal we actually call this log function now you might be wondering why this log function is decorated by what seems like random characters and at signs this is actually the function signature this needs to uniquely Define your function we'll talk more about this in the linking video but essentially when we have multiple objs and our functions are defined in multiple objs it's going to be the linker's job to link all of them together and the way that it's going to do that is it's going to look up this function signature so all you need to know here is that we're calling this log function that's what the compiler will actually do when you call a function it will generate a call instruction now in this case it might be a little bit stupid because you can see that we're simply calling log we're not even storing the return value basically this could be optimized quite a bit if we go back here and we turn on optimization to maximize speed and hit contrl F7 you'll actually see that that just disappears entirely yep the compiler just decided that does nothing I'm going to remove that code but you should basically Now understand the gist of how the compiler works it will take our source files and output an object file which contains machine code and any other constant data that we've defined that's basically it and now that we've got these object files we can link them into one executable which contains all of the machine code that we actually need to run and that's how we make a program in C++ pretty simple make sure that you check out my video on how the link works so that you can see the next step but apart from that hope you guys enjoyed this video hope you learned something new you should now have a basic understanding of what the C++ compiler actually does and that's going to be really important when it comes to debugging and also when we get into more advanced topics in the future make sure you follow me on Twitter and Instagram and if you really like this you can support me on patreon I'll see you guys next time goodbye Voom

Original Description

Twitter ► https://twitter.com/thecherno Instagram ► https://instagram.com/thecherno Patreon ► https://patreon.com/thecherno Series Playlist ► https://www.youtube.com/playlist?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb How the C++ Linker Works ► https://youtu.be/H4s55GgAg0I BEST laptop for programming! ► http://geni.us/pakTES My FAVOURITE keyboard for programming! ► http://geni.us/zNhB FAVOURITE monitors for programming! ► http://geni.us/Ig6KBq MAIN Camera ► http://geni.us/CYUQ MAIN Lens ► http://geni.us/ZM3CmG Microphone ► http://geni.us/wqO6g7K Slack ► https://slack.thecherno.com Stream ► http://www.twitch.tv/thecherno Website ► http://www.thecherno.com Facebook ► http://www.facebook.com/thecherno
Watch on YouTube ↗ (saves to browser)
Sign in to unlock AI tutor explanation · ⚡30

Playlist

Uploads from The Cherno · The Cherno · 0 of 60

← Previous Next →
1 3D Game Programming - Episode 1 - Window
3D Game Programming - Episode 1 - Window
The Cherno
2 3D Game Programming - Episode 2 - Game Loop
3D Game Programming - Episode 2 - Game Loop
The Cherno
3 3D Game Programming - Episode 3 - Arrays
3D Game Programming - Episode 3 - Arrays
The Cherno
4 3D Game Programming - Episode 4 - Drawing Pixels!
3D Game Programming - Episode 4 - Drawing Pixels!
The Cherno
5 3D Game Programming - Episode 4.5 - How Rendering Works
3D Game Programming - Episode 4.5 - How Rendering Works
The Cherno
6 3D Game Programming - Episode 5 - Playing with Pixels!
3D Game Programming - Episode 5 - Playing with Pixels!
The Cherno
7 3D Game Programming - Episode 6 - Performance Boosting
3D Game Programming - Episode 6 - Performance Boosting
The Cherno
8 3D Game Programming - Episode 7 - FPS Counter
3D Game Programming - Episode 7 - FPS Counter
The Cherno
9 3D Game Programming - Episode 8 - Alpha Support and More
3D Game Programming - Episode 8 - Alpha Support and More
The Cherno
10 3D Game Programming - Episode 9 - Beginning 3D
3D Game Programming - Episode 9 - Beginning 3D
The Cherno
11 3D Game Programming - Episode 10 - Floors and Animation
3D Game Programming - Episode 10 - Floors and Animation
The Cherno
12 3D Game Programming - Episode 11 - Rotation
3D Game Programming - Episode 11 - Rotation
The Cherno
13 3D Game Programming - Episode 12 - User Input
3D Game Programming - Episode 12 - User Input
The Cherno
14 3D Game Programming - Episode 13 - Render Distance Limiter!
3D Game Programming - Episode 13 - Render Distance Limiter!
The Cherno
15 3D Game Programming - Episode 14 - Basic Mouse Movement
3D Game Programming - Episode 14 - Basic Mouse Movement
The Cherno
16 3D Game Programming - Episode 15 - Textures + More!
3D Game Programming - Episode 15 - Textures + More!
The Cherno
17 3D Game Programming - Episode 16 - Walking, Crouching, Sprinting + More
3D Game Programming - Episode 16 - Walking, Crouching, Sprinting + More
The Cherno
18 3D Game Programming - Episode 16.5 - Exporting Runnable Jars
3D Game Programming - Episode 16.5 - Exporting Runnable Jars
The Cherno
19 3D Game Programming - Episode 17 - Small Adjustments + Birthday!
3D Game Programming - Episode 17 - Small Adjustments + Birthday!
The Cherno
20 3D Game Programming - Episode 17.5 - Creating an Applet
3D Game Programming - Episode 17.5 - Creating an Applet
The Cherno
21 3D Game Programming - Episode 18 - The Beginning of Walls
3D Game Programming - Episode 18 - The Beginning of Walls
The Cherno
22 3D Game Programming - Episode 18.1 - A Few More Things
3D Game Programming - Episode 18.1 - A Few More Things
The Cherno
23 Episode 18.5 - Creating an EXE File in Java
Episode 18.5 - Creating an EXE File in Java
The Cherno
24 3D Game Programming - Episode 19 - Rendering Walls
3D Game Programming - Episode 19 - Rendering Walls
The Cherno
25 3D Game Programming - Episode 20 - Continuing Walls, Fixing Bugs, and Managing Crashes
3D Game Programming - Episode 20 - Continuing Walls, Fixing Bugs, and Managing Crashes
The Cherno
26 3D Game Programming - Episode 21 - Texturing Walls, Fixing Clipping, and Fixing the Mouse
3D Game Programming - Episode 21 - Texturing Walls, Fixing Clipping, and Fixing the Mouse
The Cherno
27 3D Game Programming - Episode 22 - Random Level Generator + Properly Fixing Clipping
3D Game Programming - Episode 22 - Random Level Generator + Properly Fixing Clipping
The Cherno
28 3D Game Programming - Episode 23 - Graphical User Interface (GUI) Launcher
3D Game Programming - Episode 23 - Graphical User Interface (GUI) Launcher
The Cherno
29 3D Game Programming - Episode 24 - Making Our Launcher Work
3D Game Programming - Episode 24 - Making Our Launcher Work
The Cherno
30 3D Game Programming - Episode 25 - Writing and Reading Files
3D Game Programming - Episode 25 - Writing and Reading Files
The Cherno
31 3D Game Programming - Episode 26 - Custom Resolutions
3D Game Programming - Episode 26 - Custom Resolutions
The Cherno
32 3D Game Programming - Episode 27 - Decorating the Launcher
3D Game Programming - Episode 27 - Decorating the Launcher
The Cherno
33 3D Game Programming - Episode 28 - Continuing our Custom Launcher!
3D Game Programming - Episode 28 - Continuing our Custom Launcher!
The Cherno
34 3D Game Programming - Episode 29 - Launching The Game
3D Game Programming - Episode 29 - Launching The Game
The Cherno
35 3D Game Programming - Episode 30 - Colour Processing In-Depth
3D Game Programming - Episode 30 - Colour Processing In-Depth
The Cherno
36 3D Game Programming - Episode 31 - Sprites!
3D Game Programming - Episode 31 - Sprites!
The Cherno
37 3D Game Programming - Episode 32 - Sprite Mapping
3D Game Programming - Episode 32 - Sprite Mapping
The Cherno
38 3D Game Programming - Episode 33 - High Resolution Rendering
3D Game Programming - Episode 33 - High Resolution Rendering
The Cherno
39 3D Game Programming - Episode 34 - Entities
3D Game Programming - Episode 34 - Entities
The Cherno
40 Genesis - My Game for Ludum Dare 24
Genesis - My Game for Ludum Dare 24
The Cherno
41 Vlog + Ludum Dare Results
Vlog + Ludum Dare Results
The Cherno
42 Game Programming - Episode 1 - Resolution
Game Programming - Episode 1 - Resolution
The Cherno
43 Game Programming - Episode 2 - Threads
Game Programming - Episode 2 - Threads
The Cherno
44 Game Programming - Episode 3 - Game Loop
Game Programming - Episode 3 - Game Loop
The Cherno
45 Game Programming - Episode 4 - Window
Game Programming - Episode 4 - Window
The Cherno
46 Episode 5 - Buffer Strategy
Episode 5 - Buffer Strategy
The Cherno
47 Game Programming - Episode 6 - Graphics Initialized
Game Programming - Episode 6 - Graphics Initialized
The Cherno
48 Game Programming - Episode 7 - Buffered Image and Rasters
Game Programming - Episode 7 - Buffered Image and Rasters
The Cherno
49 Game Programming - Episode 8 - The Screen Class
Game Programming - Episode 8 - The Screen Class
The Cherno
50 Game Programming - Episode 9 - Rendering Pixels
Game Programming - Episode 9 - Rendering Pixels
The Cherno
51 Game Programming - Episode 10 - Clearing the Screen
Game Programming - Episode 10 - Clearing the Screen
The Cherno
52 Game Programming - Episode 11 - "Out of Bounds, Baby!"
Game Programming - Episode 11 - "Out of Bounds, Baby!"
The Cherno
53 Game Programming - Episode 12 - Negative Bounds
Game Programming - Episode 12 - Negative Bounds
The Cherno
54 Game Programming - Episode 13 - Timer
Game Programming - Episode 13 - Timer
The Cherno
55 Game Programming - Episode 14 - FPS Counter
Game Programming - Episode 14 - FPS Counter
The Cherno
56 Episode 15 - Tiles
Episode 15 - Tiles
The Cherno
57 Game Programming - Episode 16 - The Map
Game Programming - Episode 16 - The Map
The Cherno
58 The Walls 2 - Minecraft PvP Survival Map
The Walls 2 - Minecraft PvP Survival Map
The Cherno
59 Game Programming - Episode 17 - Key Input
Game Programming - Episode 17 - Key Input
The Cherno
60 Game Programming - Episode 18 - Controlling The Map
Game Programming - Episode 18 - Controlling The Map
The Cherno
Up next
George Hotz | Programming | tinygrad, starting on CLOUD=1 | Part 2
george hotz archive
Watch →