Program Structure, Translation Units, and the First Program
This chapter introduces the skeleton of every C program and the concept of a translation unit. The translation unit is the fundamental unit of compilation, and everything later depends on understanding it.
Why This Matters
You cannot reason about scope, linkage, headers, or multi-file programs without knowing what a translation unit is. The first program also anchors the exact shape of C: a main function, a return value, and the boundary between your code and the runtime.
Prerequisites
c.orientation.1— what C is and what its layers are.
Core Concept
The minimal program
Every hosted C program has a main function. The simplest conforming program is:
int main(void)
{
return 0;
}
int is the return type, main is the function name, (void) says the function takes no arguments, and return 0 returns a status to the environment. Returning 0 conventionally means success.
What a translation unit is
A translation unit is what the compiler actually translates. It is a source file *after* preprocessing. In simple terms, it is your .c file with every #include replaced by the included file's contents and every macro expanded.
Each .c file (plus everything it includes) becomes one translation unit, and is compiled independently of every other translation unit. The linker later combines the results.
Syntax
main forms
The C standard specifies two portable forms of main:
int main(void) /* no command-line access */
int main(int argc, char *argv[]) /* command-line access */
Some implementations accept void main(void) or other forms, but those are non-portable. int is the portable return type. The parameters are covered in c.core.17 (functions) and c.stdlib.3 (command-line processing).
Comments
C89 supports block comments only:
/* This is a block comment. */
C99 and later support line comments:
// This is a line comment, available since C99.
Block comments do not nest. A /* inside a /* ... */ comment is just a character; the comment ends at the first */.
Examples
Minimal program with command-line access
int main(int argc, char *argv[])
{
(void)argc; /* unused for now */
(void)argv; /* unused for now */
return 0;
}
The (void)argc casts suppress "unused parameter" warnings. This is the portable idiom; some compilers also support __attribute__((unused)) (GCC) or [[maybe_unused]] (C23), but the cast works everywhere.
Program that returns a nonzero status
int main(void)
{
return 1; /* conventionally indicates an error */
}
Returning EXIT_SUCCESS or EXIT_FAILURE from <stdlib.h> is more portable than hard-coding 0 and 1, but 0 and EXIT_SUCCESS are both defined to report success.
A complete translation unit after preprocessing (conceptual)
Given prog.c:
#include <stdio.h>
int main(void)
{
return 0;
}
The translation unit is prog.c with the contents of <stdio.h> (typically thousands of lines of declarations) inserted in place of the #include directive. The compiler sees the *combination* as one unit.
How It Works
When you compile prog.c:
1. Preprocessing produces the translation unit: directives are executed, macros expanded, includes inserted, and comments removed. 2. Compilation proper turns the translation unit into assembly or an intermediate representation. 3. Assembly turns that into an object file (machine code plus symbols and relocation records). 4. Linking combines object files and libraries into an executable.
A single translation unit can reference a name defined in another translation unit (an *external* symbol). The linker resolves those references. This is why declarations and definitions are separate concepts (c.core.6) and why linkage matters (c.core.35).
Variations
Free-standing main
In a freestanding environment there is no required main. The entry point is implementation-defined and is usually the reset handler. Part 13 covers this.
Implementation-defined startup
The C standard says the function called at program startup is named main. Everything *before* main — the runtime startup that initializes the C runtime, sets up argv, and calls main — is implementation-specific. It exists, but it is not part of ISO C.
Common Mistakes
- Using
void main(). It may work on some compilers but is not portable.
Use int main.
- Forgetting
returninmain. In C99 and later, reaching the closing
} of main implicitly returns 0. In C89 the return value was unspecified. Always write the return explicitly.
- Assuming
#includecopies text only. It also brings in declarations
whose *definitions* live in a separately compiled library.
- Nesting block comments. The inner
/*does not start a new comment.
Undefined Behavior
- Reaching the end of a non-
mainfunction that is declared to return a
non-void value without a return is undefined behavior. (The exception is main, where falling off the end is defined in C99 and later to return 0.) VERIFIED
- Defining
mainwith a return type other thanint(in a hosted
environment) is not portable and may be undefined or implementation-defined depending on the implementation. STANDARD-VERSION-DEPENDENT
Portability
int main(void)andint main(int, char *[])are the two portable forms.
VERIFIED
char *argv[]vs.char **argvare equivalent as a parameter declaration
(array parameters decay; see c.arr.decay).
- Some embedded toolchains do not use
mainat all.
Under the Hood
The object file produced from a translation unit contains:
- Text section: machine code (your function bodies).
- Data sections: initialized and uninitialized global/static data.
- Symbol table: names (like
main) and their addresses/sizes. - Relocations: notes telling the linker "this address refers to
printf;
patch it at link time."
The linker merges these and produces an executable. Part 5 develops all of this.
Practical Usage
Every real C program is a set of translation units. The most common structure is:
src/
main.c // defines main
util.c // some functions
util.h // declarations for util.c
main.c includes util.h to get declarations, and the linker joins the two object files. This is covered fully in c.core.39.
Exercises
1. Write, compile, and run a minimal int main(void) program that returns 0. Verify with your shell that the exit status is 0. 2. Write a program that returns a nonzero value and verify the exit status. 3. Determine what compiler you are using and which C standard it defaults to. For GCC/Clang: gcc -dM -E - < /dev/null | grep __STDC_VERSION__ (this prints the standard version macro). 4. Run the preprocessor on a simple file and observe the translation unit. For GCC/Clang: gcc -E prog.c.
Deep Challenge
Using only the C standard (or the official documentation for your compiler), answer: in a hosted environment, what are the exact constraints the standard places on the declaration of main? What happens if main is declared static? Write a short note with citations to the relevant section of the standard.
Related Concepts
c.core.6— Declarations and definitions.c.core.17— Functions.c.core.35— Linkage.c.core.39— Multi-file programs and headers.c.build.1— The compilation pipeline.
References
- ISO/IEC 9899:2018 §5.1.2.2.1 (program startup).
- ISO/IEC 9899:2018 §5.1.1.2 (translation phases).
- GCC documentation on
-Eand__STDC_VERSION__.
Verification
- The minimal program and the two portable
mainforms are standard.
VERIFIED
//comments are C99 and later.VERIFIED- The claim that reaching the end of
mainreturns 0 is C99 and later.
STANDARD-VERSION-DEPENDENT
- No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] c.lang.program — Program structure
- [ ] c.lang.tu — Translation units
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.lang.program | 0 | 3 |
| c.lang.tu | 0 | 3 |