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-ই মুখস্থ রাখো।
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 নেই, কেবল তারাই পাশাপাশি চলতে পারে।
End of Chapter 12.