We will use the compiler that comes with Microsoft Visual Studio Community 2022.
Google: msvs community edition
During install, it will ask what Workloads you want to install, choose Desktop development with C++.
From the start menu, open the developer command prompt installed by Visual Studio.
Developer Command Prompt for VS 2022
It should start you in a directory like this:
C:\Program Files\Microsoft Visual Studio\2022\Community>
Change directory to your desktop:
C:\Program Files\Microsoft Visual Studio\2022\Community>cd %homepath%\desktop
C:\Users\eowilson\Desktop>
Create a folder to work in and change directory into it:
C:\Users\eowilson\Desktop>mkdir hello-c
C:\Users\eowilson\Desktop>cd hello-c
C:\Users\eowilson\Desktop\hello-c>
Create your first source file:
C:\Users\eowilson\Desktop\hello-c>notepad 1.c
Save this code in it:
#include <stdio.h>
void main()
{
printf("Hello, World!");
}
Compile and link your code into an executable:
C:\Users\eowilson\Desktop\hello-c>cl 1.c
Microsoft (R) C/C++ Optimizing Compiler Version 19.35.32215 for x86
Copyright (C) Microsoft Corporation. All rights reserved.
1.c
Microsoft (R) Incremental Linker Version 14.35.32215.0
Copyright (C) Microsoft Corporation. All rights reserved.
/out:1.exe
1.obj
Two files are created. Each file is created by a different step in the build process.
01/01/2023 09:53 PM 110,080 1.exe
01/01/2023 09:53 PM 1,414 1.obj
When you run cl
with no options it will run the compiler and the linker.
You can see that in the output. First, the compiler runs, creating 1.obj
.
Microsoft (R) C/C++ Optimizing Compiler Version 19.35.32215 for x86
Copyright (C) Microsoft Corporation. All rights reserved.
1.c
Then the linker runs, automatically, since we didn't tell it not to, creating 1.exe
.
Microsoft (R) Incremental Linker Version 14.35.32215.0
Copyright (C) Microsoft Corporation. All rights reserved.
/out:1.exe
1.obj
Later, we may want to split these two steps, but for now this is fine.
Run your program to see the output:
C:\Users\eowilson\Desktop\hello-c>1
Hello, World!
The compiler creates obj files from source files.
The linker creates executables by putting together different chunks of compiled code.
Here, the linker created 1.exe from 1.obj and a .lib
file containing the standard C library.
A lib file is simply an archive of obj files, usually representing a particular library of functions.
The lib file needed in this case is for the printf
call, which is part of the standard C library.
Further, the Microsoft tools automatically link the standard C library for us.
And, by default, the linker did so "statically".
When the linker includes the code from a lib in your exe it's called static linking.
The code for printf
is literally in your exe, since the linker put it there.
However, the linker can also create your exe without adding the lib file code to your exe.
This is how programs re-use library code in Windows and other operating systems.
In Windows the shared libraries are .dll
files. DLL stands for Dynamic Link Library.
When the linker associates your exe to shared libraries it is called dynamic linking.
The code for printf
then comes from a dll which your exe now depends on.
Let's compile and link both ways to new executables and investigate the differences.
Copy these commands after the >
and right click to paste into the command prompt.
> cl /MT 1.c /link /out:1-static.exe
> cl /MD 1.c /link /out:1-dynamic.exe
The dynamic exe is under 9k and the static one is 110k.
01/01/2023 10:39 PM 8,704 1-dynamic.exe
01/01/2023 10:39 PM 110,080 1-static.exe
That's 110k just to print one line, because it includes the standard C runtime code.
However, the 9k exe now depends on an external dll file, which itself depends on more dll's.
You can see the dll files a binary depends on with dumpbin
:
C:\Users\eowilson\Desktop\hello-c>dumpbin /IMPORTS 1-dynamic.exe
But, for better usability, redirect the output to a text file and open it:
C:\Users\eowilson\Desktop\hello-c>dumpbin /IMPORTS 1-dynamic.exe > _.txt & notepad _.txt
This output tells you what code the exe has to import in order to run.
You can see that the 9k exe depends on 7 dll's.
KERNEL32.dll
VCRUNTIME140.dll
api-ms-win-crt-stdio-l1-1-0.dll
api-ms-win-crt-runtime-l1-1-0.dll
api-ms-win-crt-math-l1-1-0.dll
api-ms-win-crt-locale-l1-1-0.dll
api-ms-win-crt-heap-l1-1-0.dll
The first one, KERNEL32, provides Windows api functions. It EXPORTS them.
You can see the functions exported from that dll with:
> dumpbin /EXPORTS C:\Windows\System32\kernel32.dll > _.txt & notepad _.txt
It's a big list of 1600+ Windows api functions.
Even an old function to make your PC beep is in there.
104 67 00037F50 Beep
The second dll, VCRUNTIME140, provides the Microsoft implementation of the standard C runtime.
> dumpbin /EXPORTS C:\Windows\System32\VCRUNTIME140.dll > _.txt & notepad _.txt
It lists standard C functions, like these memory functions:
59 3A 00001170 memchr
60 3B 00001210 memcmp
61 3C 00001310 memcpy
62 3D 00001310 memmove
63 3E 000019C0 memset
A function named printf isn't in there though, because Microsoft defines a wrapper function named printf in stdio.h which calls the external implementation, with a different mangled name depending on build configuration.
But, the underlying implementation is still imported by VCRUNTIME140 from one of the other 6 dll's.
> dumpbin /IMPORTS c:\windows\system32\vcruntime140.dll > _.txt & _.txt
api-ms-win-crt-stdio-l1-1-0.dll
180011160 Import Address Table
180014D80 Import Name Table
0 time date stamp
0 Index of first forwarder reference
D __stdio_common_vsprintf
F __stdio_common_vsprintf
Which gets it from other dll's all the way down.
> dumpbin /EXPORTS c:\windows\system32\downlevel\api-ms-win-crt-stdio-l1-1-0.dll > _.txt & _.txt
14 D __stdio_common_vsprintf (forwarded to ucrtbase.__stdio_common_vsprintf)
> dumpbin /EXPORTS c:\windows\system32\downlevel\ucrtbase.dll > _.txt & _.txt
108 6B 0001AD80 __stdio_common_vfprintf
With this new understanding of linking, let's look at the statically linked exe.
> dumpbin /IMPORTS 1-static.exe > _.txt & _.txt
It only has one dll requirement: KERNEL32.dll
All of the necessary functionality of VCRUNTIME140 is included in the exe, statically linked.
Now, out of curiosity, let's check the size of VCRUNTIME140.dll.
> dir c:\windows\system32\vcruntime140.dll
12/13/2022 09:41 AM 109,392 vcruntime140.dll
And the others:
> dir c:\windows\system32\downlevel\api-ms-win-crt-stdio-l1-1-0.dll
12/07/2019 05:09 AM 17,960 api-ms-win-crt-stdio-l1-1-0.dll
> dir c:\windows\system32\downlevel\api-ms-win-crt-runtime-l1-1-0.dll
12/07/2019 05:09 AM 16,400 api-ms-win-crt-runtime-l1-1-0.dll
> dir c:\windows\system32\downlevel\api-ms-win-crt-math-l1-1-0.dll
12/07/2019 05:09 AM 21,008 api-ms-win-crt-math-l1-1-0.dll
> dir c:\windows\system32\downlevel\api-ms-win-crt-locale-l1-1-0.dll
12/07/2019 05:09 AM 12,088 api-ms-win-crt-locale-l1-1-0.dll
> dir c:\windows\system32\downlevel\api-ms-win-crt-heap-l1-1-0.dll
12/07/2019 05:09 AM 12,600 api-ms-win-crt-heap-l1-1-0.dll
From this you can see how the static version ends up being so much larger.
But, more importantly, you can see the different ways linking works.