ROB BOWDEN: Let's talk about compilers.
Until this point, you've just typed up your source code into
some files, sent them through this big black box that is
Clang, and out comes your executable file that does
exactly what you wrote in your source code.
As magical as that's been, we're going to take a closer
look at what's actually happening
when we compile a file.
So what does it mean to compile something?
>> Well, in the most general sense, it just means
transforming code written in one
programming language to another.
But usually when people say they compile something, they
mean they're taking it from a higher level programming
language to a lower level programming language.
These may seem like very subjective terms.
For example, you probably don't think of C as a high
level programming language, but you do compile it.
But it's all relative.
As we'll see, the assembly code and eventually machine
code that we compile down to is undeniably a lower level
than C.
Although we'll be using Clang in today's demonstration, a
lot of the ideas here carry over to other compilers.
>> For Clang, there are four major steps in the overall
compilation.
These are one, preprocessing done by the preprocessor; two,
compilation done by the compiler; three, assembling
done by the assembler; and four,
linking done by the linker.
It may be confusing that one of the substeps of the overall
Clang compilers is called the compiler, but
we'll get to that.
We'll be using a simple hello world program as our example
throughout this video.
Let's take a look.
>> The first step is preprocessing.
What does the preprocessor do?
In pretty much every C program you've ever read or written,
you've used lines of code that begin with a hash.
I'll call it hash, but you may also call it pounds, number
sign, or sharp.
Any such line is a preprocessor directive.
You've probably seen #define and #include before, but there
are several more that the preprocessor recognizes.
Let's add a #define to our hello world example.
Now let's run just the preprocessor on this file.
By passing clage the -E flag, you're instructing it to run
just the preprocessor.
Let's see what happens.
It looks like Clang just spits out everything
at the command line.
In order to save all of this output to a new file called
hello2.c, we'll append > hello2.c to our command.
Now let's take a look at our preprocessed file.
>> Whoa, what happened to our short little program?
If we go all the way to the bottom of this file, we'll see
some of the code that we actually wrote.
Notice that the #define is gone and all instances of name
have been replaced with exactly what we specified in
the #define line.
So what are all these typedefs and function declarations
at the top of the file?
Notice that the #define wasn't the only preprocessor
directive that we specified.
We also have #include stdio.h.
So all of the crazy lines are actually just stdio.h copied
and pasted into the top of this file.
That's why header files are so useful for function
declarations.
Instead of needing to copy and paste all of the function
declarations you plan on using at the top of your file, the
preprocessor will copy and paste them from the header
file for you.
>> Now that we're done preprocessing, we move onto
compilation.
The reason we call this step compilation is because this is
the step where Clang actually does its compiling from C to
assembly code.
In order to have Clang compile a file down to assembly, but
continue no further, pass it the -S flag
at the command line.
Let's take a look at the assembly
file that was outputted.
It looks like quite a different language.
Assembly code is very processor specific.
In this case, since the CS50 appliance is running on a
virtual x86 processor, this is x86 assembly code.
Very few people write directly in assembly code these days,
but every C program you ever write gets transformed down
into assembly.
Again, we call this step compiling the C into assembly
since we are going from a higher level to a lower level
programming language.
>> What makes assembly lower level than C?
Well, in assembly, we are very limited in what we can do.
There are no if's, while's, for's, or loops of any kind.
But you can accomplish the same things that these control
structures offer using the limited operations that
assembly does provide.
But to see just how low level assembly really is, let's go
one step further in our compilation, assembling.
It's the assembler's job to transform the assembly code
into object or machine code.
Remember that the assembler does not output assembly;
rather, it takes in assembly and outputs machine code.
Machine code is the actual 1's and 0's that a CPU can
understand, although we still have a tiny bit of work left
before we can run our program.
Let's assemble our assembly code by passing
Clang the -c flag.
Now let's see what's in the assembled file.
>> Well, that doesn't help us very much.
Remember that the machine code is the ones and zeros that
your computer can understand.
That doesn't mean it's easy for us to understand.
So exactly how low level is assembly?
It's nearly identical to object code.
Going from assembly to object code is much more of a
translation than a transformation, which is why
one might not consider the assembler to
do any actual compiling.
In fact, it's pretty easy to manually translate from
assembly to machine code.
Looking at the assembly for a main function, that first line
happens to correspond to hexadecimal 0x55.
In binary, that's 1010101.
The second line happens to correspond hexadecimal 0x895.
And the next, 0x56.
Given a relatively simple table, you could translate
assembly into the code that machines can understand too.
>> So there's one remaining step in
compilation, which is linking.
Linking combines a bunch of object files into one big file
that you can actually execute.
Linking is very system dependent.
So the easiest way to get Clang to just link object
files together is to call Clang on all of the files that
you want to link together.
If you specify .o files, then it won't need to reprocess,
compile, and assemble all of your source code.
Let's throw a math function into our file, so we have
something to link in.
Now let's compile it back down to object code and
call Clang on it.
Oops.
Since we included a math function, we need to link in
the math library with -lm.
>> If we wanted to link together bunch of .o files that we
wrote on our own, we'd just specify them all at the
command line.
The restriction is that only one of these files must
actually specify a main function, or else the
resulting executable wouldn't know where to start
running your code.
What's the difference between specifying a file to link in
with -l and just specifying a file directly?
Nothing.
It's just that Clang happens to know exactly what file
something like -lm happens to refer to.
If you knew that file yourself, you could specify it
explicitly.
Just remember that all -l flags have to come at the end
of your client demand.
>> And that's all there is to it.
When you just run Clang on some files, this is what it's
actually doing.
My name is Rob Bowden, and this is CS50.