Chapter 12: Build Process — Compiling, Linking, Make, CMake¶
Source slides:
V10_buildProcess_compiling_linking.pdf. Exercise:E10_buildProcess_compiling_linking.pdf. Code:demos/demo01_compiling/{main.cpp, source.h, ast/},demos/demo02_makefile/Makefile,demos/demo03_cmake/CMakeLists.txt,exercise_task/class_Complex_task_solution.cpp.
1. Chapter Overview¶
When you type g++ hello.cpp -o hello, four sub-tools run in sequence: preprocessor → compiler → assembler → linker. Understanding each is essential for HPC because:
- A solver split across 100
.cppfiles needs a build system (Make / CMake). - Linking can use static (
.a) or dynamic (.so) libraries; choice affects portability and performance. - Optimization flags (
-O2,-O3,-fPIC,-march=native) decide whether the code runs fast. - Header guards (
#ifndef … #define … #endif) and#includeorder determine if the program compiles at all.
Why it matters in HPC/CFD: every CFD code is a multi-file C++ project (often >100k lines) built with CMake (OpenFOAM uses wmake; SU2 uses Meson; both replace classical Make at scale). On a cluster, you load modules (module load gcc/12 cmake/3.27) and run cmake -B build && cmake --build build -j.
What the examiner asks (very common):
- "Sketch the build pipeline."
- "What is the difference between
.cpp,.i,.s,.o,.exe?" - "What does
g++ -E,-S,-cproduce?" - "What is a header guard?"
- "Difference between static and dynamic libraries."
- "Write a minimal Makefile for a 3-file project."
- "Explain the lecture's
MakefileandCMakeLists.txtline by line."
What you must master for top grade:
- The four-stage pipeline with intermediate file extensions.
- The header-guard idiom.
- Make's syntax:
target: prereqs <TAB> recipe. - CMake's basic commands:
project,add_library,add_executable,target_link_libraries. - E10 task: split
ComplexintoComplex.h,add.cpp,sub.cpp,mult.cpp,mod.cpp,main.cpp+ Makefile.
2. Basics from Zero¶
A compiler turns human-readable source code into a machine-readable binary. It does this in stages because each stage has its own goal:
- Preprocessor — handles
#include,#define,#ifdef. Output is still C++ but with all macros expanded and all headers inlined. Extension.i. - Compiler — converts the preprocessed C++ into assembly for the target CPU. Extension
.s. - Assembler — turns assembly into machine code (an "object file"). Extension
.o. - Linker — combines all the object files plus libraries into a single executable. Extension on Linux is none (or
.exeon Windows).
Pipeline picture:
বাংলায়: চারটা ধাপ চারটা আলাদা কাজ করে: preprocessor শুধু টেক্সট কাটাকাটি করে (#include-এর জায়গায় হেডার বসায়), compiler C++ কে assembly-তে অনুবাদ করে, assembler সেটাকে machine code-এর .o ফাইল বানায়, আর linker সব .o আর library জোড়া লাগিয়ে executable বানায়। মাঝের ফাইলগুলোর extension (.i, .s, .o) আর কোন g++ flag কোথায় থামায় (-E, -S, -c) — এটা পরীক্ষার সবচেয়ে নিশ্চিত প্রশ্ন।
When your project has more than one .cpp file, you compile each into a separate .o and let the linker glue them. A change in one file requires only that file's recompilation — much faster than rebuilding everything. That is why Make exists: it computes which files actually need to be rebuilt by comparing timestamps.
বাংলায়: Make-এর পুরো বুদ্ধিটা একটা সহজ নিয়মে: target ফাইলটা তার কোনো prerequisite-এর চেয়ে পুরনো হলে (বা না থাকলে) recipe চালাও, নাহলে কিছুই কোরো না। তাই ১০০ ফাইলের প্রজেক্টে একটা .cpp বদলালে শুধু সেই .o আর শেষের link ধাপটা চলে — বাকি সব আগের মতোই থেকে যায়। এটাই incremental build, আর §4.11-এ এর পুরো হিসাব timestamp দিয়ে করা আছে।
Real-life analogy.
- Preprocessor = a copy-editor pasting cited paragraphs into your draft.
- Compiler = translator turning English into French.
- Assembler = scribe converting French into shorthand.
- Linker = bookbinder combining chapters with a glossary.
Real-life HPC use. OpenFOAM has 600+ .cpp files. A single edit triggers re-compilation of only the touched ones — saves hours.
What if you misunderstand?
- Forget header guard → same function defined twice → linker error "multiple definition".
- Forget
-fPICfor a shared lib → "relocation R_X86_64_32S against.text". - Edit a header but Make doesn't know it's a dependency → stale binary uses old logic.
3. Hard English Made Easy¶
| Hard Term | Simple English | বাংলা | Example |
|---|---|---|---|
| Build | Producing the final binary | বিল্ড | make |
| Toolchain | Set of tools used to build | বিল্ড টুলসেট | gcc + ld + ar |
| Preprocessor | Resolves #include and macros |
প্রিপ্রসেসর | g++ -E |
| Compiler | Translates to assembly | কম্পাইলার | g++ -S |
| Assembler | Translates to machine code | অ্যাসেম্বলার | g++ -c |
| Linker | Combines .o and libs |
লিংকার | ld / g++ |
| Object file | Compiled but not linked | লিংকহীন কম্পাইলড | .o |
| Executable | Final runnable | এক্সিকিউটেবল | ./prog |
| Header file | Declarations only | হেডার ফাইল | .h, .hpp |
| Header guard | Prevents double inclusion | দ্বিগুণ ইনক্লুড রক্ষা | #ifndef X #define X |
| Static library | Archive of .o linked at build |
বিল্ড-টাইম লিংকড লাইব্রেরি | libname.a |
| Shared / dynamic library | Linked at runtime | রানটাইম লিংকড | libname.so |
-fPIC |
Position-independent code (for shared libs) | পজিশন-নিরপেক্ষ কোড | -fPIC |
| Symbol | Function/variable name in object | অবজেক্টের নাম | nm a.o |
| Linker error | Missing/duplicate symbol | লিংকার এরর | undefined reference |
| Make | Build automation tool | বিল্ড অটোমেশন টুল | make |
| Makefile | Build script | মেক স্ক্রিপ্ট | Makefile |
| Target / prereq / recipe | What/from/how | লক্ষ্য / প্রয়োজনীয়তা / রেসিপি | target: dep \n\trecipe |
| CMake | Build-system generator | বিল্ড সিস্টেম জেনারেটর | cmake -B build |
CMakeLists.txt |
CMake project description | CMake বর্ণনা | add_executable(...) |
4. Deep Theory Explanation¶
4.1 The four stages with intermediate files¶
hello.cpp ──g++ -E──▶ hello.i (preprocessed C++)
hello.i ──g++ -S──▶ hello.s (assembly)
hello.s ──g++ -c──▶ hello.o (object code)
hello.o ──g++ ──▶ hello (executable)
g++ runs all four by default; the flags above stop early.
The full annotated pipeline — memorise this picture:
┌────────────┐ ┌──────────────┐ ┌────────────┐ ┌──────────────┐ ┌──────────────┐
│ file.cpp │ │ file.i │ │ file.s │ │ file.o │ │ executable │
│ C++ source │ ───► │ preprocessed │ ───► │ assembly │ ───► │ object code │ ───► │ (prog) │
└────────────┘ └──────────────┘ └────────────┘ └──────────────┘ └──────────────┘
│PREPROCESSOR │COMPILER │ASSEMBLER │LINKER
│#include pasted, │C++ ─► asm │asm ─► machine │resolves symbols,
│#define expanded, │(lexer, AST, │code + relocation │joins all .o files
│#ifdef resolved │ -O2 optimisation) │entries │plus libraries
│ │ │ │
│stop here: g++ -E │stop here: g++ -S │stop here: g++ -c │ ▲
│ │
┌──────────────┴──────┴────┐
│ libraries: │
│ .a static (copied in) │
│ .so shared (runtime) │
└──────────────────────────┘
4.2 g++ flags reference¶
| Flag | Effect |
|---|---|
-o file |
output filename (else a.out) |
-c |
compile + assemble only (produce .o) |
-S |
compile only (produce .s) |
-E |
preprocess only (produce .i) |
-I path |
search header path |
-L path |
search library path |
-l name |
link libname.a or libname.so |
-O0/-O1/-O2/-O3/-Ofast |
optimisation |
-g |
debug info |
-Wall -Wextra -Wpedantic |
warnings |
-std=c++17 |
C++ standard |
-fPIC |
position-independent code (shared lib) |
-shared |
build shared library |
-static |
link statically |
-fopenmp |
OpenMP |
-march=native |
tune for current CPU |
-fdump-tree-all-graph |
dump AST (used in lecture) |
4.3 Compiler families (lecture slide 4)¶
| C | C++ | |
|---|---|---|
| GCC | gcc |
g++ |
| Clang | clang |
clang++ |
| Intel | icc |
icpc (or icx/icpx) |
| PGI / NVHPC | pgicc |
pgicxx |
| Cuda (Nvidia GPU) | n/a | nvcc |
Specialty: mpicc / mpic++ are wrappers that auto-add MPI flags.
4.4 Preprocessor in action (slide 8–9, demo01)¶
source.h (correct version with header guard):
main.cpp:
#include "source.h"
#include "source.h" // duplicate include — guard prevents double definition
int main() {
int a = add(23, 34);
return 0;
}
g++ -E main.cpp shows the preprocessor inlining source.h exactly once thanks to the guard.
Without the guard, you'd see int add(int,int){...} twice and the linker would say "multiple definition of add". Header guards (or #pragma once) prevent this.
বাংলায়: #include মানে আক্ষরিক অর্থে copy-paste — হেডারের পুরো লেখাটা ওই জায়গায় বসে যায়। একই হেডার দুবার include হলে একই function-এর definition দুবার বসে, আর linker "multiple definition" বলে আটকে দেয়। Header guard-এর কৌশল: প্রথমবার SOURCE_H macro define হয়ে content ঢোকে, দ্বিতীয়বার #ifndef মিথ্যা হওয়ায় পুরো body বাদ পড়ে। §4.13-এ এটাকেই idempotence হিসেবে ব্যাখ্যা করা হয়েছে।
4.5 Compiler internals (slides 11–16)¶
The compiler stages: lexical analysis (tokens) → syntax analysis (AST) → semantic analysis (types) → IR optimisation → code generation. The lecture demo dumps AST with -fdump-tree-all-graph -g and visualises with xdot.
You don't need to memorise all stages, but the take-away: the compiler builds a tree representation, optimises (-O3), and emits assembly.
4.6 Static vs Shared libraries¶
Static .a |
Shared .so |
|
|---|---|---|
| Build | ar rcs lib.a *.o |
g++ -fPIC -shared -o lib.so *.cpp |
| Linked | At build time | At runtime via LD_LIBRARY_PATH |
| Binary size | Bigger | Smaller |
| Updates | Need rebuild | Just replace .so |
| Distribution | Self-contained | Need libraries at install |
| Naming | libfoo.a |
libfoo.so (versioned libfoo.so.1.0) |
To link: g++ -L./lib -lfoo main.cpp. To run a program using shared libs not in the system paths: LD_LIBRARY_PATH=./lib ./prog.
STATIC libfoo.a SHARED libfoo.so
───────────────────────────────── ─────────────────────────────────
link time (g++ main.o -lfoo): link time (g++ main.o -lfoo):
┌──────────────┐ ┌──────────────┐
│ main.o │ │ main.o │
└──────┬───────┘ └──────┬───────┘
│ machine code of foo() │ only a NOTE is recorded:
▼ is COPIED into the binary ▼ "needs libfoo.so"
┌────────────────────┐ ┌────────────────────┐
│ prog │ │ prog (small) │
│ [main + foo code] │ │ [main code only] │
└────────────────────┘ └─────────┬──────────┘
run time: run time: │ dynamic linker (ld.so)
prog runs alone — self-contained, ▼ searches LD_LIBRARY_PATH
bigger file; library update ┌────────────────────┐
requires re-linking │ libfoo.so in RAM │ one copy shared
└────────────────────┘ by all processes
inspect: ldd ./prog ─► libfoo.so => ./lib/libfoo.so (0x00007f...)
(a statically linked prog shows no libfoo line at all)
বাংলায়: Static library (.a) মানে link-এর সময়ই function-এর machine code executable-এর ভেতরে কপি হয়ে যায় — ফাইল বড় হয়, কিন্তু একা একাই চলে। Shared library (.so) মানে executable-এ শুধু একটা নোট থাকে "চলার সময় libfoo.so লাগবে" — ফাইল ছোট, এক কপি library সব প্রোগ্রাম মিলে ব্যবহার করে, কিন্তু runtime-এ LD_LIBRARY_PATH-এ খুঁজে না পেলে প্রোগ্রাম চালুই হবে না। ldd কমান্ড দেখায় কোন .so কোথা থেকে আসছে — পরীক্ষায় এই তুলনার টেবিলটা প্রায়ই লিখতে হয়।
4.7 Make basics¶
A Makefile is a list of rules:
The recipe must be TAB-indented. Make rebuilds the target if any prerequisite is newer than the target.
Variables and built-ins:
$@= target,$<= first prereq,$^= all prereqs.CXX = g++,CXXFLAGS = -O2 -Wall -std=c++17..PHONY: all cleandeclares targets that aren't files.
Example:
CXX = g++
CXXFLAGS = -O2 -Wall -std=c++17
OBJECTS = main.o add.o sub.o mult.o mod.o
prog: $(OBJECTS)
$(CXX) $(CXXFLAGS) -o $@ $^
%.o: %.cpp Complex.h
$(CXX) $(CXXFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -f *.o prog
make builds prog. make clean removes artefacts.
বাংলায়: Makefile-এর প্রতিটা rule-এর গঠন এক: target (যা বানাতে চাই), কোলনের পরে prerequisites (যা যা লাগবে), আর নিচের লাইনে TAB দিয়ে recipe (কীভাবে বানাব)। Recipe-র শুরুতে TAB-ই লাগবে — স্পেস দিলে "missing separator" error, এটা পরীক্ষার প্রিয় debugging প্রশ্ন। Automatic variable তিনটা মুখস্থ: $@ মানে target, $< মানে প্রথম prerequisite, $^ মানে সব prerequisite। আর .PHONY বলে দেয় clean-এর মতো target কোনো আসল ফাইল নয়।
4.8 The lecture's Makefile (demo02_makefile/Makefile) — line by line¶
LIBPATH=./lib
INCLUDE=./include
./bin/main : ./src/main.cpp lib/libadd.so lib/libsub.so lib/libmult.so
g++ -I$(INCLUDE) -L$(LIBPATH) -o ./bin/main ./src/main.cpp -ladd -lsub -lmult
lib/libadd.so: ./src/add.cpp
g++ -fPIC -shared -o lib/libadd.so ./src/add.cpp
lib/libsub.so: ./src/sub.cpp
g++ -fPIC -shared -o lib/libsub.so ./src/sub.cpp
lib/libmult.so: ./src/mult.cpp
g++ -fPIC -shared -o lib/libmult.so ./src/mult.cpp
clean:
rm -f lib/*.so ./bin/*
Three shared libraries (libadd.so, libsub.so, libmult.so) are built from three sources with -fPIC -shared. The main program is compiled and linked against all three with -L$(LIBPATH) -ladd -lsub -lmult.
বাংলায়: Lecture-র Makefile-টা তিনটা shared library বানায়, প্রতিটা -fPIC -shared দিয়ে — -fPIC লাগে কারণ .so মেমরির যেকোনো ঠিকানায় load হতে পারে, তাই code-টাকে position-independent হতে হয়। তারপর main-কে -L দিয়ে library-র পথ আর -ladd -lsub -lmult দিয়ে নামগুলো জানিয়ে link করা হয় (-ladd মানে libadd.so খোঁজো)। চালানোর সময় LD_LIBRARY_PATH=./lib দিতে হয়, নাহলে dynamic linker .so গুলো খুঁজে পাবে না — এই তিন ধাপের যেকোনোটা পরীক্ষায় ব্যাখ্যা করতে বলা হয়।
4.9 CMake basics¶
CMake generates a Makefile (or Ninja, Visual Studio…). You write declarative CMakeLists.txt, run cmake -B build, then cmake --build build.
The lecture's CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(MyProject)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
include_directories(include)
add_library(add SHARED src/add.cpp)
add_library(sub SHARED src/sub.cpp)
add_library(mult SHARED src/mult.cpp)
add_executable(main src/main.cpp)
target_link_libraries(main add sub mult)
set_target_properties(add PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set_target_properties(sub PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set_target_properties(mult PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set_target_properties(main PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
Build:
বাংলায়: CMake নিজে কিছু compile করে না — এটা একটা generator: CMakeLists.txt পড়ে platform অনুযায়ী Makefile (বা Ninja ফাইল) বানিয়ে দেয়। আপনি শুধু ঘোষণা করেন কী চান (add_library, add_executable, target_link_libraries), কীভাবে হবে সেটা CMake বের করে। দুটো কমান্ড মুখস্থ: cmake -B build -S . (configure, out-of-source) আর cmake --build build -j (আসল build)। Make বনাম CMake তুলনা — runner বনাম generator — পরীক্ষার নিয়মিত প্রশ্ন।
4.10 Diagrams from V10¶
- "Build Pipeline" full-page diagram: arrows from
.cpp→.i→.s→.o→.exewith the intermediate file extensions; the lecture overlays "Linking static and dynamic libraries (.h and .so files)" at the linker stage. Memorise this picture. - "Compiler internals": token → parser → AST → IR → assembly. Mention if asked to "describe how a compiler works".
4.11 Incremental-rebuild logic — the timestamp rule, formally¶
Let \(m(x)\) be the modification time of file \(x\), and \(P(t)\) the prerequisite set of target \(t\). Make's rule, applied recursively bottom-up through the dependency graph:
In words: a target is rebuilt iff it is missing or older than at least one of its prerequisites.
The 3-file project. main.cpp (includes mathlib.h), mathlib.cpp (includes mathlib.h), mathlib.h. Rules: prog depends on main.o mathlib.o; main.o on main.cpp mathlib.h; mathlib.o on mathlib.cpp mathlib.h.
Dependency DAG with rebuild propagation:
┌────────────┐
│ prog │ (link step)
└─────┬──────┘
┌───────────────┴────────────────┐
▼ ▼
┌───────────┐ ┌────────────┐
│ main.o │ │ mathlib.o │ (compile steps)
└─────┬─────┘ └─────┬──────┘
┌──────┴────────┐ ┌───────┴─────────┐
▼ ▼ ▼ ▼
┌──────────┐ ┌───────────┐ ┌─────────────┐ ┌───────────┐
│ main.cpp │ │ mathlib.h │ │ mathlib.cpp │ │ mathlib.h │ (sources)
└──────────┘ └───────────┘ └─────────────┘ └───────────┘
arrows point from target DOWN to what it depends on;
staleness propagates UPWARD:
touch mathlib.cpp ─► mathlib.o stale ─► prog stale
touch mathlib.h ─► main.o AND mathlib.o stale ─► prog stale
touch nothing ─► everything up to date ─► make does nothing
Worked numeric example. After a clean build the timestamps are:
| File | \(m(x)\) |
|---|---|
main.cpp |
10:00 |
mathlib.cpp |
10:00 |
mathlib.h |
10:00 |
main.o |
10:01 |
mathlib.o |
10:01 |
prog |
10:02 |
Case (a) — edit mathlib.cpp at 10:30. Now \(m(\texttt{mathlib.cpp}) = 10{:}30 > m(\texttt{mathlib.o}) = 10{:}01\) ⇒ rebuild mathlib.o (its new \(m\) ≈ 10:31). For main.o: \(m(\texttt{main.cpp}) = 10{:}00 < 10{:}01\) and \(m(\texttt{mathlib.h}) = 10{:}00 < 10{:}01\) ⇒ not rebuilt. For prog: \(m(\texttt{mathlib.o}) = 10{:}31 > 10{:}02\) ⇒ relink. Commands run: 2 (one compile, one link).
Case (b) — edit mathlib.h at 10:40. The header is a prerequisite of both object files: \(10{:}40 > 10{:}01\) for each ⇒ rebuild main.o and mathlib.o, then \(m(\text{both .o}) > m(\texttt{prog})\) ⇒ relink. Commands run: 3. This is why listing headers as prerequisites matters — omit mathlib.h from the rule and Make happily ships a stale binary.
Case (c) — nothing changed. Every target is newer than all of its prerequisites; the condition is false everywhere. Make prints make: 'prog' is up to date. Commands run: 0.
| Change | main.o |
mathlib.o |
prog |
Commands |
|---|---|---|---|---|
(a) mathlib.cpp |
kept | recompiled | relinked | 2 |
(b) mathlib.h |
recompiled | recompiled | relinked | 3 |
| (c) nothing | kept | kept | kept | 0 |
বাংলায়: Make-এর সিদ্ধান্ত নেওয়ার পুরো গণিত একটা তুলনায়: prerequisite-এর timestamp target-এর চেয়ে নতুন কি না। .cpp বদলালে শুধু তার .o আর link — দুটো কমান্ড; কিন্তু .h বদলালে যে যে .o ওই header-এর ওপর নির্ভর করে সবগুলো recompile হয় — তাই header বদলানো "দামি"। আর rule-এ header-টা prerequisite হিসেবে না লিখলে Make বুঝতেই পারবে না, পুরনো binary-ই থেকে যাবে — এই stale-binary bug-টা পরীক্ষায় ধরতে দেওয়া হয়।
4.12 A complete worked Makefile — line by line — and the CMake equivalent¶
The Makefile for the 3-file project:
CXX = g++
CXXFLAGS = -O2 -Wall -std=c++17
OBJ = main.o mathlib.o
prog: $(OBJ)
$(CXX) $(CXXFLAGS) -o $@ $^
%.o: %.cpp mathlib.h
$(CXX) $(CXXFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -f $(OBJ) prog
Line by line:
| Line | Meaning |
|---|---|
CXX = g++ |
Variable holding the compiler — change once, applies everywhere. |
CXXFLAGS = -O2 -Wall -std=c++17 |
Variable holding the flags used in every compile/link recipe. |
OBJ = main.o mathlib.o |
The list of object files, reused in three places. |
prog: $(OBJ) |
Rule head: target prog depends on both .o files. |
<TAB> $(CXX) $(CXXFLAGS) -o $@ $^ |
Link recipe. $@ expands to the target (prog), $^ to all prerequisites (main.o mathlib.o). Must start with a TAB. |
%.o: %.cpp mathlib.h |
Pattern rule: any X.o is built from X.cpp plus the header. The % stem matches main/mathlib. Listing mathlib.h makes case (b) above work. |
<TAB> $(CXX) $(CXXFLAGS) -c $< -o $@ |
Compile recipe. -c stops before linking, producing the .o; $< is the first prerequisite (the .cpp). |
.PHONY: clean |
Declares clean as not-a-file, so a file named clean can never shadow it. |
clean: rm -f *.o prog |
Housekeeping target: removes everything generated. |
The CMake equivalent (CMakeLists.txt):
cmake_minimum_required(VERSION 3.16)
project(prog CXX)
set(CMAKE_CXX_STANDARD 17)
add_executable(prog main.cpp mathlib.cpp)
target_include_directories(prog PRIVATE .)
target_compile_options(prog PRIVATE -O2 -Wall)
Build out-of-source:
CMake generates the Makefile for you (with automatic header-dependency scanning — you don't list mathlib.h anywhere).
বাংলায়: Makefile আর CMake-এর সম্পর্কটা এভাবে ভাবো: Makefile হলো হাতে-লেখা রান্নার রেসিপি — কোন ধাপের পর কোন ধাপ, কোন উপকরণ বদলালে কী আবার রান্না করতে হবে, সব নিজে লিখতে হয়। CMake হলো সেই রেসিপি-জেনারেটর — তুমি শুধু বলো কী বানাতে চাও (executable, library), সে নিজেই platform-অনুযায়ী Makefile বানিয়ে দেয়, header dependency-ও নিজে খুঁজে নেয়। পরীক্ষায় দুটোই লিখতে বলা হয়, তাই দুটো template-ই মুখস্থ রাখো।
4.13 Compiler anatomy — front end, back end, LLVM (expanded 2026)¶
The 2026 deck deepens the compiler-theory slides. The compiler splits into two halves around an intermediate representation (IR):
FRONT END (machine-independent) BACK END (machine-dependent)
source ──► lexer ──► parser ──► AST ──► IR ═════► optimiser ──► code gen ──► assembly
(tokens) (syntax) (Abstract (IR-level (registers,
Syntax Tree) passes) instructions)
Why the split matters: N languages × M architectures need only N front ends + M back ends (not N×M compilers). LLVM (Low Level Virtual Machine) is exactly this: clang is a C/C++ front end producing LLVM IR; back ends target x86, Arm, GPUs. GCC (GNU Compiler Collection) bundles front ends for C, C++, Fortran — Fortran still matters in HPC legacy codes.
Language levels (2026 slide): high-level = machine-independent (C++, Python, Fortran); low-level = machine-dependent (assembly, machine code); C/C++ are often called middle-level — high-level abstractions with direct memory access.
Conditional preprocessing — the full directive family: #if, #ifdef, #ifndef, #elif, #else, #endif. Used for platform switches and debug builds:
#ifdef USE_MPI
MPI_Init(&argc, &argv);
#elif defined(USE_OPENMP)
omp_set_num_threads(nt);
#else
// serial build
#endif
Linking several of your own libraries (2026 example): g++ main.o -L. -ladd -lmult -lsub -o prog — each -lX resolves libX.so/libX.a; order matters with static archives (dependents first).
বাংলায়: নতুন স্লাইডের মূল ছবি: compiler = front end (ভাষা বোঝে, AST/IR বানায়) + back end (মেশিন বোঝে, assembly বানায়) — মাঝখানে IR-সেতু। LLVM-এর জাদু এটাই: এক IR, বহু ভাষা, বহু চিপ। আর preprocessor-এর
#ifপরিবার মানে compile-এর আগেই কোড বাছাই — MPI/OpenMP/serial তিন রূপের এক সোর্স। পরীক্ষায় "কেন front/back ভাগ" এলে N+M বনাম N×M যুক্তিটা দাও।
5. Command / Syntax / Code Breakdown¶
g++ -E main.cpp¶
Preprocess only — outputs .i to stdout.
g++ -S main.cpp¶
Compile only — outputs main.s.
g++ -c main.cpp¶
Preprocess+compile+assemble — outputs main.o.
g++ main.o add.o -o main¶
Link object files into executable.
g++ -shared -fPIC -o libfoo.so foo.cpp¶
Build a shared library.
ar rcs libfoo.a foo.o bar.o¶
Build a static library archive.
nm libfoo.a / nm libfoo.so¶
List symbols (functions/vars) in a library.
ldd ./main¶
Print dynamic libs the executable needs.
LD_LIBRARY_PATH=./lib ./main¶
Run with custom shared-lib search path.
Make rule¶
CMake basics¶
6. Mandatory Practical Examples¶
Example 6.1 — See each stage individually (demo01)¶
Files (source.h with guard, main.cpp from §4.4).
g++ -E main.cpp -o main.i # preprocessed C++
g++ -S main.i -o main.s # assembly
g++ -c main.s -o main.o # object
g++ main.o -o main # executable
./main
Inspect:
head -20 main.i # see headers inlined
head -30 main.s # assembly directives like .cfi_def_cfa_register
nm main.o # symbols: T main, T _Z3addii (mangled add)
file main # ELF 64-bit LSB pie executable
Real-Life HPC/CFD Meaning. You can stop at any stage to debug what the compiler is doing — invaluable when chasing template-instantiation bloat or vectorisation issues.
Written Exam Relevance. Classic question: "What do g++ -E, -S, -c do?" → answer with the file extension produced.
Example 6.2 — Header guards (slide 9)¶
source.h (no guard) included twice → duplicate-definition errors. Add #ifndef … #define … #endif to fix.
Written Exam Tip. "Why do we use header guards?" — Because #include literally pastes content. Without guards, the same declarations/definitions appear multiple times in one translation unit.
Example 6.3 — Mini Makefile¶
CXX = g++
CXXFLAGS = -O2 -Wall -std=c++17
OBJ = main.o add.o
prog: $(OBJ)
$(CXX) $(CXXFLAGS) -o $@ $^
%.o: %.cpp source.h
$(CXX) $(CXXFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -f *.o prog
make →
g++ -O2 -Wall -std=c++17 -c main.cpp -o main.o
g++ -O2 -Wall -std=c++17 -c add.cpp -o add.o
g++ -O2 -Wall -std=c++17 -o prog main.o add.o
Edit add.cpp, run make again → only add.o and prog rebuild.
Example 6.4 — Lecture's Makefile (demo02) — see §4.8¶
Example 6.5 — CMake (demo03) — see §4.9¶
Example 6.6 — E10 Task (Complex split into multiple files)¶
Layout:
project/
├─ include/Complex.h
├─ src/add.cpp
├─ src/sub.cpp
├─ src/mult.cpp
├─ src/mod.cpp
├─ src/main.cpp
├─ Makefile
include/Complex.h:
#ifndef COMPLEX_H
#define COMPLEX_H
#include <iostream>
#include <cmath>
class Complex {
private:
float a, b;
public:
Complex(float x, float y) : a(x), b(y) {}
void display() { std::cout << a << "+" << b << "i\n"; }
float real() const { return a; }
float img() const { return b; }
Complex add(const Complex& c1, const Complex& c2);
Complex sub(const Complex& c1, const Complex& c2);
Complex multiply(const Complex& c1, const Complex& c2);
float modulus();
};
#endif
src/add.cpp:
#include "Complex.h"
Complex Complex::add(const Complex& c1, const Complex& c2) {
return Complex(c1.real()+c2.real(), c1.img()+c2.img());
}
(sub.cpp, mult.cpp, mod.cpp analogously.)
src/main.cpp:
#include "Complex.h"
int main() {
Complex c1(3,4), c2(4,5), c3(0,0);
c3 = c3.add(c1,c2); c3.display(); // 7+9i
c3 = c3.sub(c1,c2); c3.display(); // -1-1i
c3 = c3.multiply(c1,c2); c3.display(); // -8+31i
std::cout << c3.modulus() << "\n"; // ≈ 32.02
return 0;
}
Makefile:
CXX = g++
CXXFLAGS = -O2 -Wall -std=c++17 -Iinclude
OBJ = build/main.o build/add.o build/sub.o build/mult.o build/mod.o
bin/prog: $(OBJ) | bin
$(CXX) $(CXXFLAGS) -o $@ $^
build/%.o: src/%.cpp include/Complex.h | build
$(CXX) $(CXXFLAGS) -c $< -o $@
bin build:
mkdir -p $@
.PHONY: clean run
clean:
rm -rf build bin
run: bin/prog
./bin/prog
make run → builds and runs.
Real-Life HPC/CFD Meaning. Same skeleton scales to a CFD solver: each physics module is its own .cpp, header in include/, main driver in src/main.cpp.
Written Exam Relevance. Top-mark exam item: "Split the Complex class across files and write the Makefile."
বাংলায়: এই E10 প্রশ্নটা পরীক্ষার "বড় মাছ": class-কে কয়েকটা ফাইলে ভাগ করা + header guard +
-Iinclude+ pattern rule — সব এক প্রশ্নে। মুখস্থ নয়, গঠনটা বোঝো: header-এ ঘোষণা, প্রতিটা .cpp-তে একটা করে সংজ্ঞা, Makefile জোড়া লাগায়।| bin(order-only prerequisite) মানে "bin ফোল্ডারটা আগে থাকতে হবে, কিন্তু তার টাইমস্ট্যাম্প দেখে rebuild কোরো না"।
7. Real HPC/CFD Workflow¶
ssh hpc
module load gcc/12 cmake/3.27 openmpi/4.1
git clone git@gitlab:cfd/solver.git
cd solver
cmake -B build -S . -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_FLAGS="-O3 -march=native -fopenmp"
cmake --build build -j$(nproc)
ldd ./build/bin/solver | head
sbatch run.sbatch
Update modules to gcc/13 for AVX-512 →
8. Exercises and Solutions¶
E10 — see 6.6.
Marking scheme (12 marks)
- 1 directory layout.
- 2
Complex.hwith header guard. - 2 add/sub/mult/mod
.cpp(each 0.5). - 1
main.cpptest driver. - 2 Makefile rule compiling each
.cppto.o. - 2 Makefile linking
.oto prog with-O2 -Iinclude. - 1 clean target.
- 1 make run target.
Common mistakes
- Forgetting
#include "Complex.h"in each.cpp. - Forgetting
-Iincludeso headers can't be found. - TAB vs spaces in Makefile (
Makefile:5: *** missing separator. Stop.). - Forgetting header guards → "multiple definition of …".
- Defining method bodies in the header → linker errors when included multiple times unless
inline.
Harder version — convert to CMake
cmake_minimum_required(VERSION 3.16)
project(complex_demo CXX)
set(CMAKE_CXX_STANDARD 17)
add_library(complex STATIC
src/add.cpp src/sub.cpp src/mult.cpp src/mod.cpp)
target_include_directories(complex PUBLIC include)
add_executable(prog src/main.cpp)
target_link_libraries(prog PRIVATE complex)
Build: cmake -B build && cmake --build build && ./build/prog
9. Written Exam Focus¶
9.1 Short Answers¶
Q. What is the build process?
A. Converting source code into an executable. Stages: preprocessing → compilation → assembling → linking, producing .i, .s, .o, and finally an executable.
Q. What does g++ -c file.cpp do?
A. Preprocess + compile + assemble, producing file.o. It does not link.
Q. What is a header guard?
A. #ifndef SYM #define SYM ... #endif (or #pragma once) ensuring a header is included only once per translation unit, avoiding duplicate definitions.
Q. Difference between .a and .so.
A. .a is a static archive copied into the executable at link time; .so is a shared object loaded at runtime. .so saves disk space and allows library updates without relinking.
Q. What is the role of CMake?
A. A cross-platform build-system generator reading CMakeLists.txt and emitting Makefiles (or Ninja, MSVC). It handles dependencies, compilers, libraries, install rules, tests.
9.2 Medium Answers¶
Q. (8 marks) Explain the four-stage build pipeline for main.cpp.
A.
- Preprocess (
g++ -E main.cpp→main.i): expands#includeand macros, handles#ifdef. Produces a still-C++ source. - Compile (
g++ -S main.i→main.s): translates C++ into target-CPU assembly via lexer → parser → AST → IR → optimisation → code-gen. - Assemble (
g++ -c main.s→main.o): converts assembly into a machine-code object file with relocation entries. - Link (
g++ main.o -lfoo -o main): resolves symbol references across.ofiles and libraries (.a/.so) and writes the executable image.
Q. (5 marks) Write a Makefile rule that builds prog from main.cpp and add.cpp with a shared header Complex.h.
A.
CXX=g++
CXXFLAGS=-O2 -Wall -std=c++17
prog: main.o add.o
$(CXX) -o $@ $^
%.o: %.cpp Complex.h
$(CXX) $(CXXFLAGS) -c $< -o $@
.PHONY: clean
clean: ; rm -f *.o prog
9.3 Long Answer (12 marks)¶
Q. Explain the lecture's Makefile that builds three shared libraries and the main executable.
(Use §4.8 expanded to show: -fPIC is needed because shared-library code is loaded at arbitrary addresses; -shared produces an .so; -L and -l connect at link time; runtime needs LD_LIBRARY_PATH or rpath.)
9.4 Output Prediction¶
g++ -E main.cpp (with header guard) → headers expanded once.
g++ main.cpp add.cpp -o prog → builds prog directly without intermediate .o files kept.
make (in lecture demo) → builds three .so then bin/main.
9.5 Comparison¶
Static vs shared: see §4.6.
| Make | CMake | |
|---|---|---|
| What it is | Build runner | Generator + cross-platform DSL |
| Files | Makefile | CMakeLists.txt → Makefile/Ninja |
| Cross-platform | Limited | Yes |
| Out-of-source build | Manual | Default (-B build) |
| HPC adoption | Common | Modern standard |
-O0 vs -O2 vs -O3 — debug vs balanced vs aggressive (inlining, vectorising, loop fusion).
9.6 Templates¶
Pipeline template: "preprocess → compile → assemble → link, producing .i/.s/.o/exec".
Makefile template: target/prereqs/recipe + variables.
CMake template: cmake_minimum_required → project → add_library → add_executable → target_link_libraries.
9.7 Marking Scheme — "Build pipeline" (5 marks)¶
- 1 each: preprocess / compile / assemble / link.
- 1 mention intermediate file extensions.
10. Very Hard Questions¶
Beginner
- Default executable name? →
a.out. - Flag for include path? →
-Ipath. - Flag for library path? →
-Lpath. - Flag for linking libm (math)? →
-lm. - What does
make cleantypically do? → remove generated artefacts.
Intermediate
- Compile a single
.cppto.o. →g++ -c file.cpp. - Link two
.o. →g++ a.o b.o -o prog. - Build a static lib. →
ar rcs libx.a a.o b.o. - Build a shared lib. →
g++ -shared -fPIC -o libx.so a.cpp b.cpp. - List symbols. →
nm libx.a.
Hard
- Why
-fPICfor shared libs? → shared libs are mapped at varying addresses; PIC uses relative addressing. - Why does make rebuild only changed files? → timestamp comparison target-vs-prerequisites.
- What is
LD_LIBRARY_PATH? → dirs the dynamic linker searches at runtime. - Difference
-Lvs-l. →-Ladds a search path;-lnamelinkslibname. - What is rpath? → run-time library paths embedded in the binary.
Very Hard
- Why might
ldd ./progshow "not found"? → required.soabsent fromLD_LIBRARY_PATH/system paths. - How to find which header brought in 100k lines? →
g++ -H -c …. - Why is
make -j$(nproc)faster? → builds independent targets in parallel.
Deep Integration
- How do header guards interact with templates and
inline? → templates/inline must live in headers; guards prevent duplicate non-inline definitions per TU. - Why do CFD solvers prefer CMake today? → portability,
find_packagedependency handling, tests/install support.
Coding/Command
- Makefile rule for
Complex.h-aware.cpp→.o. → see 9.2. - CMakeLists.txt for the same project. → see §8 harder version.
Debugging
Makefile:5: *** missing separator. Stop.→ recipe lines must start with TAB, not spaces.undefined reference to add(int,int)→ forgot to compile/linkadd.cpp(or name mismatch).
Long Written
- (250 words) Compare Make and CMake for a CFD project of 100 files. (Use §4.7–4.9 + §10.)
11. Debugging and Mistake Analysis¶
| Mistake | Why wrong | Correct | Explanation |
|---|---|---|---|
| Body in header | redefinition errors | body in .cpp or inline |
one-definition rule |
| Missing header guard | duplicate definitions | #ifndef ... #endif |
preprocessor |
| Spaces instead of TAB | "missing separator" | TAB | Make syntax |
Forgotten .h dependency |
stale binary | list header in prereqs | dep tracking |
g++ a.cpp b.cpp always |
full rebuild every time | use .o files |
incremental builds |
-shared without -fPIC |
relocation error | add -fPIC |
PIC needed |
Forgot -Ipath |
header not found | add -I |
include path |
Forgot -llib |
undefined reference | -lname |
linker |
LD_LIBRARY_PATH unset |
"lib not found at runtime" | export it or rpath | runtime |
cmake . in-source |
messy tree | cmake -B build -S . |
out-of-source |
বাংলায়: Linker error দুই জাতের — চিনে রাখো: "No such file or directory" আসে compile-এ (header পাওয়া যায়নি →
-I); "undefined reference" আসে link-এ (definition পাওয়া যায়নি →.oবা-lবাদ পড়েছে)। কোন stage-এ ভুল, সেটা বলে দেওয়াই অর্ধেক উত্তর।
12. Mini Project for Mastery¶
Goal: OpenFOAM-style mini project with libraries + executable + CMake.
miniCFD/
├─ include/
│ ├─ Vector.h
│ └─ Field.h
├─ src/
│ ├─ Vector.cpp
│ ├─ Field.cpp
│ └─ main.cpp
├─ CMakeLists.txt
└─ Makefile
CMakeLists:
cmake_minimum_required(VERSION 3.16)
project(miniCFD CXX)
set(CMAKE_CXX_STANDARD 17)
add_library(core SHARED src/Vector.cpp src/Field.cpp)
target_include_directories(core PUBLIC include)
add_executable(miniCFD src/main.cpp)
target_link_libraries(miniCFD PRIVATE core)
target_compile_options(miniCFD PRIVATE -O3 -march=native)
Connection to exam: library + exec + include path + optimisation + out-of-source — all top-graded items.
13. Final Chapter Cheat Sheet¶
| Item | Memorise |
|---|---|
| Pipeline | preprocess (-E) → compile (-S) → assemble (-c) → link |
| File ext | .i .s .o, then exec |
| Header guard | #ifndef X #define X ... #endif or #pragma once |
| Static lib | ar rcs lib.a *.o |
| Shared lib | g++ -fPIC -shared -o lib.so *.cpp |
| Link | -Lpath -lname |
| Run-time path | LD_LIBRARY_PATH or rpath |
-O2 -Wall -std=c++17 |
sane defaults |
-fopenmp |
OpenMP |
| Make rule | target: deps + TAB recipe |
| Make autovars | $@ $< $^ |
| Rebuild rule | target older than any prerequisite ⇒ rebuild |
.PHONY |
non-file targets |
| CMake | cmake -B build && cmake --build build |
add_library SHARED/STATIC |
library type |
target_link_libraries |
link |
nm, ldd, file, objdump |
inspect |
| Trap | TAB vs spaces in Makefile |
| Top phrase | "g++ pipelines four stages: preprocess→compile→assemble→link, joined by intermediate .i .s .o files." |
14. Mock Exam — Four Levels¶
Level 1 — Basic (definitions & syntax)¶
Q1. Which g++ flag stops after preprocessing, and what file results?
Solution: -E; a .i file (preprocessed C++ source).
Q2. Name the four build stages in order.
Solution: Preprocess → compile → assemble → link.
Q3. Write the Make rule skeleton (3 parts).
Solution: target: prerequisites newline TAB recipe.
Q4. What does $@ mean in a Makefile recipe?
Solution: The target name of the rule being executed.
Q5. Which file does g++ -c add.cpp produce, and is it executable?
Solution: add.o — an object file; not executable (no linking happened).
Level 2 — Intuitive (predict / explain why)¶
Q1. You run make twice in a row. Why does the second run print "Nothing to be done"?
Solution: All targets are newer than their prerequisites; the timestamp rule finds nothing outdated.
Q2. mathlib.h changes. With the rule %.o: %.cpp mathlib.h, which files rebuild in the 3-file project, and why?
Solution: BOTH main.o and mathlib.o (header is a prerequisite of every .o), then prog relinks — three commands total.
Q3. Your program builds but ./prog says error while loading shared libraries: libcore.so: cannot open.... Compile-time or runtime problem? Fix?
Solution: Runtime: the dynamic loader can't find the .so. Fix: export LD_LIBRARY_PATH=./lib:$LD_LIBRARY_PATH or embed rpath (-Wl,-rpath,'$ORIGIN/lib').
Q4. Why does deleting prog but keeping all .o files make the next make fast?
Solution: Only the link step reruns; compilation is skipped because each .o is still newer than its sources.
Q5. Predict what happens: a file named clean exists in the project and the Makefile lacks .PHONY: clean. You run make clean.
Solution: Make sees target clean exists as a file with no newer prerequisites → "is up to date" — the recipe never runs. .PHONY fixes it.
Level 3 — Hard (exam level)¶
Q1. (10 marks) Project: main.cpp, mathlib.cpp, mathlib.h. Write the COMPLETE Makefile (variables, pattern rule, clean), then state exactly what runs after touch mathlib.cpp.
Solution:
CXX = g++
CXXFLAGS = -O2 -Wall -std=c++17
OBJ = main.o mathlib.o
prog: $(OBJ)
$(CXX) $(CXXFLAGS) -o $@ $^
%.o: %.cpp mathlib.h
$(CXX) $(CXXFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -f $(OBJ) prog
touch mathlib.cpp: only mathlib.o recompiles (its source is newer), then prog relinks. main.o untouched.
বাংলা ইঙ্গিত: উত্তরে দুই ধাপ আলাদা করে লেখো — recompile (১টা ফাইল) আর relink (সবসময়, কারণ prog-এর prerequisite বদলেছে)।
Q2. (8 marks) Decode this error and give the precise fix: undefined reference to 'Complex::modulus()' while compiling with g++ src/main.cpp -Iinclude -o prog.
Solution: Link-stage error: the declaration exists (header found) but no definition was linked — src/mod.cpp was never compiled into the command. Fix: g++ src/main.cpp src/mod.cpp src/add.cpp ... -Iinclude -o prog (or build .o files / library and link them).
বাংলা ইঙ্গিত: "undefined reference" = linker খুঁজছে body — header নয়; যে .cpp-তে definition আছে সেটা command-এ আছে কি না দেখো।
Q3. (8 marks) Explain why -fPIC is required for .so but not for .a, in two sentences of substance.
Solution: A shared object is mapped at a different virtual address in every process, so its code must be position-independent — all jumps/data accesses relative, no absolute addresses. Static-archive code is fixed into the executable at link time at known addresses, so PIC is unnecessary (though harmless).
বাংলা ইঙ্গিত: .so ভাড়াটে — যেকোনো ঠিকানায় উঠতে হয়, তাই আসবাব relative; .a মালিকের বাড়িতে ঢালাই হয়ে যায়।
Q4. (10 marks) Write the complete CMakeLists.txt for: static library geom from src/point.cpp src/mesh.cpp (headers in include/), executable app from src/main.cpp linking geom, C++17, and -O3 only for the executable.
Solution:
cmake_minimum_required(VERSION 3.16)
project(geomapp CXX)
set(CMAKE_CXX_STANDARD 17)
add_library(geom STATIC src/point.cpp src/mesh.cpp)
target_include_directories(geom PUBLIC include)
add_executable(app src/main.cpp)
target_link_libraries(app PRIVATE geom)
target_compile_options(app PRIVATE -O3)
PUBLIC on include dirs propagates them to consumers of geom — app inherits the path automatically.
বাংলা ইঙ্গিত: PUBLIC/PRIVATE-এর মানে মুখস্থ: PUBLIC = আমার ও আমার ব্যবহারকারীর, PRIVATE = শুধু আমার; include path প্রায় সবসময় PUBLIC।
Q5. (10 marks) make -j8 builds your project but intermittently fails with "No such file or directory: build/main.o", while make (serial) always works. Diagnose.
Solution: A race: some rule using build/ doesn't declare the directory as an (order-only) prerequisite, so with parallel jobs the compile rule can run before mkdir -p build finished. Fix: build/%.o: src/%.cpp | build plus a build: ; mkdir -p $@ rule — the | order-only prerequisite guarantees ordering without timestamp coupling.
বাংলা ইঙ্গিত: "-j-তে ভাঙে, serial-এ চলে" = dependency ঘোষণা অসম্পূর্ণ — Make জানে না কোন কাজ আগে দরকার; | order-only prerequisite-ই প্রতিষেধক।
Level 4 — Beyond the lecture (transfer + coding)¶
Q1. Your solver must ship as libsolver.so plus header. A customer reports your v2.0 update crashes their pre-built app. Explain ABI compatibility: name TWO header changes that break binary compatibility without breaking source compatibility.
Solution: (1) Adding/reordering data members of a class used by value — object size/offsets change, caller code computed with old layout; (2) adding a virtual function — vtable layout shifts, all virtual calls land on the wrong slots. Source still compiles (API same), but pre-built binaries break — that's why .so versioning (libsolver.so.2) exists.
বাংলা ইঙ্গিত: API = সোর্স-চুক্তি, ABI = বাইনারি-চুক্তি; size/layout/vtable বদলালেই ABI ভাঙে — recompile ছাড়া পুরোনো বাইনারি আর মিলে না।
Q2. Write a bash one-liner (Ch 8 transfer) that finds every Makefile recipe line accidentally starting with spaces instead of a TAB under src/.
Solution:
Recipe lines must begin with TAB; lines starting with spaces then content are suspects. (A stricter check:awk 'prev ~ /:/ && /^ / {print FILENAME":"FNR}' Makefile flags space-lines following a rule header.)
বাংলা ইঙ্গিত: এই প্রশ্ন দুই chapter মেলায় — Make-এর TAB নিয়ম + grep/awk; পরীক্ষক দেখতে চায় তুমি নিয়মটা যাচাইযোগ্য ভাবে লিখতে পারো কি না।
Q3. A header-only library (everything inline in .h) vs a static .a library: give two build-time and one run-time consequence of choosing header-only for a 100-file CFD project.
Solution: Build-time: (1) every TU re-compiles the library code → much longer compile times; (2) any header tweak forces recompiling all 100 dependents (no .o reuse). Run-time: typically equal or faster (inlining across boundaries), no extra library to load — but binary may be larger from duplicated instantiations.
বাংলা ইঙ্গিত: header-only মানে সুবিধা linker-ঝামেলা নেই, দাম হলো compile-time — বড় প্রজেক্টে সেই দামটাই মুখ্য।
Q4. CI question: write a shell script check_build.sh that configures with CMake into a fresh build-ci/, builds with all cores, runs ctest, and prints "BUILD OK"/"BUILD FAIL" with a proper exit code, cleaning up on any failure path.
Solution:
#!/bin/bash
set -euo pipefail
trap 'echo "BUILD FAIL" >&2' ERR
rm -rf build-ci
cmake -B build-ci -S . -DCMAKE_BUILD_TYPE=Release > build-ci.log 2>&1
cmake --build build-ci -j"$(nproc)" >> build-ci.log 2>&1
ctest --test-dir build-ci --output-on-failure >> build-ci.log 2>&1
echo "BUILD OK"
set -e + trap ... ERR turns any failing stage into the FAIL message and a nonzero exit; logs are kept for inspection.
বাংলা ইঙ্গিত: CI-script-এর তিন স্তম্ভ: fresh build dir, set -e, আর exit code-ই সত্য — stdout-এর "OK" শুধু মানুষের জন্য।
End of Chapter 12.
#include "mathlib.h" (or add the prototype int add(int,int);) in main.cpp. (ii) is a link-time error: the declaration was fine, but no linked object/library contains the definition. Fix: compile and link mathlib.cpp (g++ main.o mathlib.o -o prog) or add the right -L path -lmathlib. Rule of thumb: "not declared" = compiler, missing .h; "undefined reference" = linker, missing .o/.so/.a.
বাংলা ইঙ্গিত: Error-টা কে দিয়েছে দেখুন — ফাইল:লাইন নম্বরসহ হলে compiler (declaration সমস্যা), আর ld লেখা থাকলে linker (definition সমস্যা)।
Q4.3. (HPC build) Write the commands and a minimal CMakeLists.txt to build a solver that uses both MPI and OpenMP, and explain what the mpic++ wrapper actually adds.
Solution: Manual build: mpic++ -O3 -fopenmp solver.cpp -o solver. mpic++ is just a wrapper around the underlying g++ that injects the MPI include path (-I.../openmpi/include), the library path (-L.../openmpi/lib) and the link flags (-lmpi ...) — verify with mpic++ -show. CMake version:
cmake_minimum_required(VERSION 3.16)
project(solver CXX)
find_package(MPI REQUIRED)
find_package(OpenMP REQUIRED)
add_executable(solver solver.cpp)
target_link_libraries(solver PRIVATE MPI::MPI_CXX OpenMP::OpenMP_CXX)
Build and run on the cluster: module load gcc openmpi cmake, cmake -B build -S . -DCMAKE_BUILD_TYPE=Release, cmake --build build -j, then mpirun -np 4 ./build/solver (with OMP_NUM_THREADS set for the hybrid case).
বাংলা ইঙ্গিত: mpic++ কোনো আলাদা compiler নয় — g++-এর গায়ে MPI-র -I, -L, -l flag গুলো জড়িয়ে দেওয়া একটা wrapper মাত্র; mpic++ -show দিয়ে নিজেই দেখা যায়।
Q4.4. (Build + shell integration) Write a one-liner that rebuilds the project in parallel and submits the job only on success, then explain why make -j$(nproc) is legal at all — what property of the Makefile permits parallel execution?
Solution:
&& submits only when make exits 0; || reports the failure (Chapter 8 exit-status algebra). Parallelism is legal because the Makefile is a DAG: main.o and mathlib.o have no edge between them, so Make may run their recipes simultaneously; only the link rule must wait for both prerequisites to finish. Independence in the dependency graph = parallelisable work, the same principle as task parallelism in MPI/OpenMP. (Caveat: this is also why hidden dependencies that are not written in the Makefile cause flaky parallel builds.)
বাংলা ইঙ্গিত: -j নিরাপদ শুধু তখনই যখন dependency গুলো Makefile-এ সৎভাবে লেখা আছে — DAG-এ যাদের মধ্যে edge নেই, কেবল তারাই পাশাপাশি চলতে পারে।
15. MCQ Drill — Exam Section (36 questions)¶
Difficulty ramps from basic (1) to very hard (36). Questions marked (select all that apply) have more than one correct option.
1. The four build stages in order: A. compile→link→assemble→preprocess B. preprocess→compile→assemble→link C. parse→build→run→debug D. edit→make→run→install
Answer: B. With intermediate files .i → .s → .o → executable.
2. g++ -E main.cpp stops after…
A. compiling B. preprocessing (macro/include expansion, .i output) C. assembling D. linking
Answer: B. -E preprocess, -S compile (assembly), -c assemble (object).
3. g++ -c add.cpp produces…
A. an executable B. add.o — machine code with unresolved references, NOT runnable C. add.s D. a library
Answer: B. The linker later resolves symbols across objects.
4. The default executable name without -o: A. main B. a.out C. prog D. run
Answer: B. A Unix fossil worth one easy mark.
5. Header guards prevent… A. viruses B. double inclusion of a header within one translation unit (duplicate definitions) C. linking D. recursion
Answer: B. #ifndef X #define X … #endif — or #pragma once.
6. #include literally… A. links a library B. pastes the file's text into the source before compilation C. imports symbols D. compiles the header
Answer: B. Why guards are needed at all.
7. Which flags add search paths? (select all that apply) A. -Iinclude (headers) B. -Llib (libraries at link time) C. -lname D. -Ppath
Answer: A, B. -lname selects a LIBRARY (libname), not a path; -P isn't a path flag.
8. -lm links…
A. the math library libm B. main.o C. memory checks D. nothing
Answer: A. -lX ⇒ libX.so or libX.a.
9. Static (.a) vs shared (.so): A. same B. .a is copied INTO the executable at link time; .so is loaded at RUN time C. .so is copied in D. .a loads at runtime
Answer: B. Hence .so updates don't need relinking, and ldd lists them.
10. Build a static library from objects: A. g++ -static B. ar rcs libx.a a.o b.o C. ld -a D. make lib
Answer: B. Archiver ar; index with s.
11. Build a shared library: A. g++ -shared -fPIC -o libx.so x.cpp B. ar rcs libx.so C. g++ -o libx.so D. share x.cpp
Answer: A. Both flags — see Q26 for why -fPIC.
12. ldd ./solver shows…
A. static symbols B. the shared libraries the dynamic linker will load (or "not found") C. source files D. compile flags
Answer: B. First diagnostic for runtime library problems.
13. "error while loading shared libraries: libcore.so: cannot open…" is fixed by… (select all that apply) A. export LD_LIBRARY_PATH=./lib:$LD_LIBRARY_PATH B. embedding an rpath at link time (-Wl,-rpath,…) C. recompiling with -O3 D. installing the .so into a standard path
Answer: A, B, D. It's a RUNTIME search-path issue; optimisation is irrelevant.
14. Make rebuilds a target when… A. always B. the target is OLDER than any prerequisite (timestamp comparison) C. the recipe changed D. you ask twice
Answer: B. The one-sentence heart of Make.
15. A Make rule's recipe line MUST begin with… A. spaces B. a TAB character C. a dash D. $
Answer: B. "missing separator. Stop." = spaces instead of TAB.
16. Automatic variables: $@, $<, $^ = A. target, first prerequisite, all prerequisites B. all, first, target C. shell args D. macros
Answer: A. Read any Makefile aloud with these three.
17. %.o: %.cpp mathlib.h is…
A. a phony rule B. a PATTERN rule: any X.o from X.cpp, plus the header as prerequisite C. a variable D. wrong syntax
Answer: B. Listing the header is what makes header edits trigger rebuilds.
18. After touch mathlib.h (header listed as prerequisite of both .o files), make rebuilds…
A. nothing B. BOTH .o files and relinks prog C. only mathlib.o D. only the link
Answer: B. Every dependent object is now older than a prerequisite.
19. After touch mathlib.cpp, make rebuilds…
A. everything B. mathlib.o, then relinks prog — main.o untouched C. only prog D. main.o too
Answer: B. Incremental builds: exactly the stale path through the DAG.
20. .PHONY: clean exists so that…
A. clean runs faster B. a FILE named clean can never shadow the target ("is up to date") C. clean is hidden D. make skips it
Answer: B. Phony = not-a-file targets (clean, all, run, debugcheck).
21. Running make twice prints "Nothing to be done" because…
A. make is lazy B. all targets are newer than their prerequisites — the DAG is satisfied C. caching D. an error
Answer: B. Timestamp logic again — say it with the rule.
22. "undefined reference to Complex::modulus()" at build's END is a… A. compiler error (missing header) B. LINKER error: declaration existed, but no object with the DEFINITION was linked (mod.cpp missing from the command) C. runtime error D. preprocessor error
Answer: B. Compile vs link errors — locate the stage, name the fix.
23. "fatal error: Complex.h: No such file or directory" is fixed by… A. -lComplex B. -Iinclude (header search path) — a COMPILE-stage fix C. LD_LIBRARY_PATH D. ar
Answer: B. Contrast with Q22: the two errors live in different stages.
24. Defining a (non-inline) function BODY in a header included twice causes… A. nothing B. "multiple definition" at LINK time — one-definition rule; move the body to a .cpp or mark inline C. faster code D. a warning only
Answer: B. Header guards do NOT save you across translation units — subtle and graded.
25. Which must live in headers? (select all that apply) A. templates B. inline functions C. class DEFINITIONS used by multiple TUs D. large function bodies (non-inline)
Answer: A, B, C. D belongs in .cpp files.
26. -fPIC is required for shared libraries because… A. speed B. .so code is mapped at different addresses per process — it must be Position-Independent Code (relative addressing) C. licensing D. debugging
Answer: B. Static code is fixed at link time; PIC is the price of runtime relocation.
27. CMake is best described as… A. a compiler B. a cross-platform build-system GENERATOR: CMakeLists.txt → Makefiles/Ninja C. a Make replacement at runtime D. an IDE
Answer: B. CMake generates; Make executes.
28. The canonical out-of-source build: A. cmake . B. cmake -B build -S . && cmake --build build -j C. make -B D. cmake --run
Answer: B. Keeps the tree clean; rm -rf build resets everything.
29. target_include_directories(core PUBLIC include) — PUBLIC means…
A. visible on the web B. the include path applies to core AND propagates to targets that link core C. public headers only D. nothing
Answer: B. PRIVATE = only core; INTERFACE = only consumers. Usage-requirement propagation is CMake's superpower.
30. Link an executable against your library in CMake: A. add_link(app core) B. target_link_libraries(app PRIVATE core) C. link app core D. -lcore in CMakeLists
Answer: B. With add_library(core …) and add_executable(app …) beforehand.
31. Which CMake lines create (a) a static library, (b) an executable? (select all that apply) A. add_library(geom STATIC src/point.cpp) B. add_executable(app src/main.cpp) C. make_library(geom) D. build_exe(app)
Answer: A, B. The two target-creating verbs to memorise.
32. make -j$(nproc) speeds builds by…
A. optimising code B. building INDEPENDENT targets in parallel across cores C. skipping tests D. caching
Answer: B. Parallelism follows the dependency DAG.
33. make -j8 intermittently fails ("No such file: build/main.o") but serial make works. Diagnosis:
A. broken make B. a missing dependency (e.g. the build/ dir rule not declared as order-only prerequisite | build) lets jobs race C. disk full D. -j maximum is 4
Answer: B. "Parallel fails, serial works" ⇒ incomplete dependency declaration — always.
34. The compiler's FRONT end vs BACK end: A. GUI vs CLI B. front: lex/parse → AST → IR (language-specific); back: optimise IR → machine code (target-specific) C. front: linking D. same thing
Answer: B. N languages + M targets = N front ends + M back ends around one IR — LLVM's design (2026 slides).
35. #ifdef USE_MPI … #elif defined(USE_OPENMP) … #else … #endif selects code…
A. at runtime B. at PREPROCESS time — one source, three build variants via -DUSE_MPI etc. C. at link time D. never
Answer: B. Conditional compilation — the mechanism behind portable HPC sources.
36. With static archives, g++ main.o -lsub -ladd can fail while -ladd -lsub works because…
A. alphabetical order required B. the linker scans archives LEFT to RIGHT, pulling only symbols already needed — dependents must come BEFORE their dependencies C. random D. .a files conflict
Answer: B. Link-order sensitivity — the sharpest classic linker question; shared libs are more forgiving.
End of Chapter 12.