Executable program files are created from readable source code.
main.c
executable
Creating a C executable is a four step process.
Download hello.c
By default, all four stages (preprocessor, compiler, assembler
and linker)
are done
clang hello.c # or ` hello.c`
./a.out
Provide a name for the created executable (instead of the default
a.out
clang hello.c -o hello
./hello
Compilation can be done with either clang
or
.
clang $INFILE -o $PROGRAM_NAME
$INFILE -o $PROGRAM_NAME
Stop after preprocessor and send output to stdout
clang -E hello.c
Creates assembler files (usually *.s
)
clang -S hello.c
*.o
filesmain(.)
main(.)
clang -c hello.c
clang -o hello hello.o
Imagine a project with three files:
main.c
and modules
array.c
and
ui.c
main.c
array.c
ui.c
executable
What do we have to do to satisfy the compiler when using functionality
of array.c
in all other files?
What if we have thousands of files and change
array.c
?
Write the declarations exported by a source file into a separate header file.
Include the header file where the declarations are used.
Header files do not need to be compiled separately since their content is copied into the source files by the preprocessor.
Example
extern
and static
Functions in CEvery identifier that is marked as extern
is part
of a file's public interface.
These identifiers can be used in other files by including the
header file.
extern
extern
keyword to share variables (if really,
really needed)static
keyword marks functions that should
not be exportedstatic
C functions cannot be used in
other files of the same project#include
GuardsA construct used to avoid the problem of double inclusion when dealing with the include directive.
#ifndef FILENAME_
#define FILENAME_
// ... your header file's code
#endif // FILENAME_
#include
guards
can also be referred to as
macro guards, header guards
or file guards
Compiling all source files produces a single executable.
Remember: header files are already included in the
*.c
files.
clang *.c
It would be much faster to re-compile
only the files that need to be re-compiled and then link the
object files.
How do we know what needs re-compilation?
Everything affected by a change must be re-compiled. If, for example,
a header file is changed, we need to re-compile all
*.c
files that #include
that header file.
Download
main.c,
ui.c,
ui.,
array.c and
array.
(or data/c/multiple_files).
Compile all files at once:
clang *.c -o array_tool
Since we did not create object files, any change would require complete re-compilation. Instead, we should create object files and link them together.
clang -c *.c
clang *.o -o array_tool
Now, make a change to main.c
.
Check the return value of get_dimension(.)
and issue a
warning if the value is 0.
Once done, we can just re-compile main.c
and link to the existing object files.
clang -c main.c
clang *.o -o array_tool
Extend the program to contain another function
int amax(int* array, size_t dimension)
that returns the
maximum of the given array. Add the function declaration to
array.
and the definition to
array.c
.
Call the function in main.c
and print the result
to stdout
.
Which files need to re-compiled to object files before a new executable can be linked? Once you know the answer, compile them and create a new executable.
Build tools allow you to re-compile exactly the files that need to be re-compiled with a single command or the click of a button.
There are many different build tools for each programming language.
For C and C++, popular examples are
Makefile
for the
projectall: array_tool # default target
array_tool: main.o ui.o array.o
gcc -o array_tool main.o ui.o array.o
array.o: array.c array.h
gcc -std=c11 -Wall -c array.c
ui.o: ui.c ui.h array.h
gcc -std=c11 -Wall -c ui.c
main.o: main.c ui.h array.h
gcc -std=c11 -Wall -c main.c
.PHONY: clean
clean:
find . -name '*~' -o -name '*.o' -o -name 'array_tool' | xargs rm
Download Makefile
Makefiles quickly get complicated: example Makefile using more features
To make a build, run make -f YourMakeFile
If no -f option is present, make will look for the makefiles
GNUmakefile
, makefile
, and
Makefile
, in that order.
make # run the default (first) target
make array_tool # run the target explicitly (usually it is aliased to `all`)
make clean # run the target clean
make array.o # run the target to build array.o if that is needed
make -j8 # run 8 jobs in parallel (use up to 8 CPU cores)
Makefile
cmake_minimum_required(VERSION 3.18)
project(array_tool VERSION 1.0)
set(C_SRCS # all c source files
array.c
main.c
ui.c
)
set(CMAKE_C_STANDARD 11)
add_executable(${PROJECT_NAME} ${C_SRCS})
Download CMakeLists.txt
CMake isn't trivial either: example CMakeLists.txt using more features
To make a build, run the following commands
mkdir build # create directory for all files created during build
cd build
cmake .. # create the Makefile for your setup
make -j8 # start actual build on 8 cores; alternatively `cmake --build . -j8`