Saving a C file and running a C program are different actions. An editor may put them behind one convenient button, but the steps still matter when something goes wrong.
This walkthrough follows one small program from source code to output. Along the way, we will deliberately break it in two different ways, then investigate how an old executable can survive a failed build.
By the end, you should be able to build and run the program, recognize the files involved, and choose a sensible first check when the result is unexpected.
Series: C, One Experiment at a Time — Part 1.
Environment: Windows, MSVC, C17 mode, and an x64 Visual Studio developer command prompt. The commands below use Command Prompt syntax. This assumes the Visual Studio C/C++ build tools are already installed; it is not an installation guide. Other toolchains use different commands and sometimes different filenames.
1. Start with a program whose result you can check
Create a working folder and save this as hello.c. Make sure the filename really ends in .c, rather than .c.txt.
#include <stdio.h> int main(void) { printf("Hello from C.\n"); return 0; } Only a few details are needed for this experiment:
-
<stdio.h>provides the declaration ofprintf, the standard output function we use here. Including a header does not paste the implementation ofprintfinto this file. -
int main(void)defines the starting function for this ordinary hosted C program.voidhere means that this function has no parameters. -
printfwrites the message to standard output, which normally appears in our terminal.\nadds a newline. -
return 0;reports successful completion to the environment that started the program. It does not print a zero.
The goal is a visible, checkable result:
Hello from C. 2. Build first, then run
Open an x64 Native Tools Command Prompt for Visual Studio, then change to the folder containing hello.c. If its path contains spaces, put the path in double quotes.
Build with:
cl /nologo /std:c17 /W4 hello.c Once the command finishes successfully, run:
hello.exe The first command creates a program. The second starts it.
Here, cl is MSVC's compiler driver: it can control compilation and invoke the linker. /std:c17 selects C17 mode, /W4 enables a useful warning level, and /nologo hides the startup banner. Warnings deserve attention even when a build succeeds.
For this simple build, you should find hello.obj and hello.exe alongside your source file. A larger project or an IDE may place these outputs elsewhere.
If the terminal says that cl is not recognized, check that you opened the developer command prompt and installed the C/C++ tools. That message is about the tool environment, not a diagnosis of your C code.
3. Make the build stages visible
Our useful first model is:
hello.c --compile to object--> hello.obj hello.obj ------link---------> hello.exe hello.exe ------run----------> terminal output The middle file is worth noticing. An object file is an intermediate input to the linker; it is not the final executable you start in this walkthrough.
We can ask MSVC to stop before linking:
cl /nologo /std:c17 /W4 /c hello.c The /c option produces the object file without invoking the linker. Then link explicitly:
link /nologo hello.obj /OUT:hello.exe Finally:
hello.exe The output is still Hello from C. We changed how explicitly we requested the stages, not what the program does.
“Compile to object” is a simplified label. A conventional toolchain also preprocesses the source and translates it through compilation and assembly before linking. The diagram groups those earlier stages so we can focus on the distinction between building and running. It is not claiming that every implementation exposes identical files or separate commands for each stage.
Linking brings together the required object code and libraries and resolves references between them. In this example, it connects our program with the runtime support it needs. A header declaration and a function implementation have different jobs; including <stdio.h> does not remove the need for linking.
4. Break compilation: remove one semicolon
Save this separate file as syntax_error.c:
#include <stdio.h> int main(void) { printf("Hello from C.\n") return 0; } Try compiling it:
cl /nologo /std:c17 /W4 /c syntax_error.c The semicolon after printf(...) is missing. In the tested MSVC environment, this reports C2143, pointing near return.
An error location is where the compiler detected a problem; the edit you need may be just before it. Check the diagnostic and the surrounding code, rather than assuming the highlighted token is always the cause.
This attempt has not reached a successful link or run. Fix the syntax before investigating the program's output.
5. Break linking: promise a function, but never define it
Save this as missing_definition.c:
int missing_function(void); int main(void) { return missing_function(); } The first line declares a function. For now, think of it as describing how the function can be called, without providing its body.
Compile it:
cl /nologo /std:c17 /W4 /c missing_definition.c This succeeds in the tested environment and creates missing_definition.obj. Now try to link it:
link /nologo missing_definition.obj /OUT:missing_definition.exe MSVC reports LNK2019, followed by LNK1120. Our object code refers to missing_function, but we have supplied no definition in an object file or library.
Why can compilation succeed? In a multi-file program, a function's definition can live elsewhere. The linker is where that missing piece must be resolved for this build.
The useful distinction is already visible: code can compile into an object file and still fail to become a usable executable. We will return to function declarations and definitions in the functions unit.
6. Why can an old message appear after a failed build?
Return to a working hello.c. Change its message to Version 1, build successfully, and run it:
Version 1 Now change the message to Version 2, but deliberately remove the semicolon:
printf("Version 2\n") Save the file and run the build command again:
cl /nologo /std:c17 /W4 hello.c It fails. If you then explicitly run the existing executable:
hello.exe it can still print:
Version 1 No time travel involved. In this tested sequence, compilation failed before replacing the previous executable, and we manually launched that older file.
This does not mean every IDE automatically runs an old build after an error. An IDE may stop, prompt you, or handle output differently. The experiment deliberately separates the failed build from the later command that starts the existing file.
Restore the semicolon, save, build successfully, and run again. The output becomes Version 2.
When an edit appears to do nothing, check:
- Did you save the intended source file?
- Did the latest build actually succeed?
- Are you running the executable from that build, in the intended directory?
Saving a new source file does not by itself update an existing executable.
7. A successful build still needs a result check
There is another kind of mistake that these build stages cannot diagnose for you:
#include <stdio.h> int main(void) { printf("2 + 3 = %d\n", 2 - 3); return 0; } This builds and runs in the tested environment, producing:
2 + 3 = -1 The label says addition, but the expression performs subtraction. Both are valid C; the compiler does not know which task we intended.
“It builds” is good news. It is not a correctness certificate. Compare the output with an expected result.
Try it yourself
- Change the greeting, predict the output, then save, rebuild, and run.
- Repeat the missing-semicolon experiment. Before running anything, identify whether the latest build succeeded.
- Fix the arithmetic example, then explain why its earlier version could build successfully.
We now have a practical foundation for the next unit: variables, types, and expressions. That is where we will start replacing fixed examples with values we can store and calculate with.
If you have seen a program ignore your edits, what turned out to be the cause: an unsaved file, a failed build, a different executable, or something else?
References and testing
The normal build, separate compilation and linking, intentional compile/link failures, arithmetic mistake, and old-executable sequence were executed locally with MSVC on Windows in C17 mode. Diagnostic numbers and wording may differ with another compiler. This walkthrough does not claim to test runtime crashes or other toolchains.
- MSVC compiler driver and environment
- MSVC: compile without linking
- MSVC: language standard modes
- MSVC: LNK2019
- GCC: conventional build stages
Writing note: Prepared with AI assistance; code examples were tested locally.



