Skip to content

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 .cpp files 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 #include order 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, -c produce?"
  • "What is a header guard?"
  • "Difference between static and dynamic libraries."
  • "Write a minimal Makefile for a 3-file project."
  • "Explain the lecture's Makefile and CMakeLists.txt line 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 Complex into Complex.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:

  1. Preprocessor — handles #include, #define, #ifdef. Output is still C++ but with all macros expanded and all headers inlined. Extension .i.
  2. Compiler — converts the preprocessed C++ into assembly for the target CPU. Extension .s.
  3. Assembler — turns assembly into machine code (an "object file"). Extension .o.
  4. Linker — combines all the object files plus libraries into a single executable. Extension on Linux is none (or .exe on Windows).

Pipeline picture:

.cpp/.c  --preprocess-->  .i  --compile-->  .s  --assemble-->  .o  --link-->  exe
                                                                    ^
                                                          libs (.a / .so)

বাংলায়: চারটা ধাপ চারটা আলাদা কাজ করে: 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 -fPIC for 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):

#ifndef SOURCE_H
#define SOURCE_H
int add (int a, int b) { return a + b; }
#endif

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 optimisationcode 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:

target: prerequisites
<TAB>recipe

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 clean declares 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 -B build -S .
cmake --build build -j
./build/bin/main

বাংলায়: 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.exe with 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:

\[ \text{rebuild}(t) \iff t \ \text{does not exist} \ \lor \ \exists\, p \in P(t) : m(p) > m(t) \]

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 -B build -S .
cmake --build build -j
./build/prog

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

target: prerequisites
    recipe

CMake basics

cmake -B build
cmake --build build -j
cmake --install build --prefix /opt

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

cd demo02_makefile
make
LD_LIBRARY_PATH=./lib ./bin/main

Example 6.5 — CMake (demo03) — see §4.9

cd demo03_cmake
cmake -B build -S .
cmake --build build -j
./build/bin/main

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 →

module switch gcc/12 gcc/13
cmake --build build -j --clean-first

8. Exercises and Solutions

E10 — see 6.6.

Marking scheme (12 marks)

  • 1 directory layout.
  • 2 Complex.h with header guard.
  • 2 add/sub/mult/mod .cpp (each 0.5).
  • 1 main.cpp test driver.
  • 2 Makefile rule compiling each .cpp to .o.
  • 2 Makefile linking .o to prog with -O2 -Iinclude.
  • 1 clean target.
  • 1 make run target.

Common mistakes

  • Forgetting #include "Complex.h" in each .cpp.
  • Forgetting -Iinclude so 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.

  1. Preprocess (g++ -E main.cppmain.i): expands #include and macros, handles #ifdef. Produces a still-C++ source.
  2. Compile (g++ -S main.imain.s): translates C++ into target-CPU assembly via lexer → parser → AST → IR → optimisation → code-gen.
  3. Assemble (g++ -c main.smain.o): converts assembly into a machine-code object file with relocation entries.
  4. Link (g++ main.o -lfoo -o main): resolves symbol references across .o files 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_requiredprojectadd_libraryadd_executabletarget_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

  1. Default executable name? → a.out.
  2. Flag for include path? → -Ipath.
  3. Flag for library path? → -Lpath.
  4. Flag for linking libm (math)? → -lm.
  5. What does make clean typically do? → remove generated artefacts.

Intermediate

  1. Compile a single .cpp to .o. → g++ -c file.cpp.
  2. Link two .o. → g++ a.o b.o -o prog.
  3. Build a static lib. → ar rcs libx.a a.o b.o.
  4. Build a shared lib. → g++ -shared -fPIC -o libx.so a.cpp b.cpp.
  5. List symbols. → nm libx.a.

Hard

  1. Why -fPIC for shared libs? → shared libs are mapped at varying addresses; PIC uses relative addressing.
  2. Why does make rebuild only changed files? → timestamp comparison target-vs-prerequisites.
  3. What is LD_LIBRARY_PATH? → dirs the dynamic linker searches at runtime.
  4. Difference -L vs -l. → -L adds a search path; -lname links libname.
  5. What is rpath? → run-time library paths embedded in the binary.

Very Hard

  1. Why might ldd ./prog show "not found"? → required .so absent from LD_LIBRARY_PATH/system paths.
  2. How to find which header brought in 100k lines? → g++ -H -c ….
  3. Why is make -j$(nproc) faster? → builds independent targets in parallel.

Deep Integration

  1. How do header guards interact with templates and inline? → templates/inline must live in headers; guards prevent duplicate non-inline definitions per TU.
  2. Why do CFD solvers prefer CMake today? → portability, find_package dependency handling, tests/install support.

Coding/Command

  1. Makefile rule for Complex.h-aware .cpp.o. → see 9.2.
  2. CMakeLists.txt for the same project. → see §8 harder version.

Debugging

  1. Makefile:5: *** missing separator. Stop. → recipe lines must start with TAB, not spaces.
  2. undefined reference to add(int,int) → forgot to compile/link add.cpp (or name mismatch).

Long Written

  1. (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)
cmake -B build -S . && cmake --build build -j && ./build/miniCFD

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
After 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:

grep -rnE '^ +[^ #]' --include='Makefile*' src/ | grep -v ':[[:space:]]*#'
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:

make -j$(nproc) && sbatch job.sbatch || { echo "build failed" >&2; exit 1; }

&& 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.