Chapter 11: C++ Basics for HPC¶
Source slides:
V09_Cpp_Basics.pdf. Exercise:E09_Cpp_Basics.pdf. Code:examples_exercises/example01.cpp..example10.cpp, task_01.cpp, task_02.cppandexercises_solutions/task_01_solution.cpp, task_02_solution.cpp(the Complex class).
1. Chapter Overview¶
C++ is the dominant language of CFD solvers (OpenFOAM, SU2, Code_Saturne, AMReX). It combines C-level performance with object-oriented abstractions for meshes, fields, boundary conditions. This chapter covers what an HPC user must know to read, modify and compile C++ code:
- The build cycle: edit → compile (
g++) → run. - Variables and primitive data types (
int,char,float,double,bool). - Scope and namespaces (
int avsbox::avs global::a). - Pointers (
int *p), addresses (&x), dereferencing (*p). - Arrays and pointer arithmetic.
- Conditionals (
if/else,?:),for/whileloops. - Functions: declaration, definition, call by value vs call by reference vs call by pointer.
- Classes and objects:
class Complex { ... };, constructors, member functions,private/public.
Why it matters in HPC/CFD: every solver hot-loop is C/C++; understanding pointers and call-by-reference is essential to know why code is fast or slow and how to avoid copying multi-GB fields.
What the examiner asks (very high frequency):
- "Compile and run example01.cpp."
- "Predict the output of example10.cpp (call-by-value vs reference)."
- "Difference between pointer and reference."
- "Implement
swapusing pointers." - "Complete the Complex class."
- "Predict size of
int,char,float."
What you must master for top grade:
- The compile command:
g++ -O2 -std=c++17 file.cpp -o exe. - The pointer trio:
&(address-of),*(declaration / dereference). - The 3 ways to pass to functions: by value (copy), by pointer (
int*), by reference (int&). - Class anatomy: declaration / definition / constructor / member function / private vs public.
- The
Complexexercise — Memorise it.
2. Basics from Zero¶
A C++ program is a text file (.cpp) the compiler turns into a binary you can execute. Minimum example:
Compile and run:
C++ has strict typing: every variable has a fixed type known at compile time (int, char, float, double, bool). Sizes are platform-dependent but typical:
int : 4 bytes (32-bit)
char : 1 byte
float : 4 bytes (32-bit IEEE-754)
double : 8 bytes (64-bit IEEE-754)
bool : 1 byte
A pointer stores an address. int *p = &b; makes p point at b; *p reads back the value at that address.
A reference is an alias for an existing variable: int& r = b; — r is just another name for b. References are like "pointers that can never be null and never re-bind".
বাংলায়: Pointer হলো এমন একটা variable যার ভেতরে মান নয়, আরেকটা variable-এর মেমরি ঠিকানা থাকে — &b দিয়ে ঠিকানা নেওয়া হয়, *p দিয়ে সেই ঠিকানায় গিয়ে মান পড়া হয়। Reference হলো একই variable-এর আরেকটা নাম — null হতে পারে না, পরে অন্য কিছুর দিকে ঘোরানোও যায় না। পরীক্ষায় "pointer vs reference" পার্থক্যটা প্রায় প্রতি বছর আসে, তিনটা পয়েন্ট মুখস্থ রাখুন: null সম্ভব কি না, re-bind সম্ভব কি না, dereference করতে * লাগে কি না।
Functions can take parameters by value (a copy is made — original untouched), by pointer (caller passes &x, callee writes *p = ...), or by reference (cleanest syntax; modifies original).
বাংলায়: Function-এ argument পাঠানোর তিনটা পথ: by value মানে কপি যায় (মূলটা অক্ষত), by pointer মানে ঠিকানা যায় (callee *p দিয়ে মূলটা বদলাতে পারে), আর by reference মানে আসল variable-টাই অন্য নামে যায়। HPC-তে এটা শুধু syntax নয়, performance-এর প্রশ্ন — কয়েক GB-র field কপি করা মানে simulation-এর সর্বনাশ। পরীক্ষায় example10.cpp-এর output predict করতে দেয়: value বদলায় না, reference বদলায়।
Classes group data + functions. The lecture's Complex class stores a real and imaginary part and provides display, add, multiply, modulus.
Real-life analogy. Pointers = postal addresses (you can copy the address; everyone holding the address visits the same house). References = nicknames (Mr. President = Joe Biden — both refer to the same person; you can't make "Mr President" point to someone else later). Classes = forms with fields plus filing instructions stapled together.
Real-life HPC use. A CFD field of 1e9 cells is 8 GB; passing it by value to a function would copy 8 GB; by reference passes a 8-byte address. Wrong choice = simulation 1000× slower.
What if you misunderstand? You write int *p; *p = 5; without setting p first → segmentation fault. Or you use = for compare in if(a=5) → always true (assignment). Or you forget ; after class definition → cryptic compile errors.
3. Hard English Made Easy¶
| Hard Term | Simple English | বাংলা | Example |
|---|---|---|---|
| Compiler | Translates source to binary | উৎস → বাইনারিতে অনুবাদ | g++ |
| Linker | Joins object files into binary | অবজেক্ট ফাইল যোগ করা | ld |
| Header file | Declarations | ঘোষণাপত্র ফাইল | <iostream> |
| Namespace | Naming scope | নাম-পরিধি | std::cout |
| Scope | Where a name is visible | পরিধি | local / global |
| Pointer | Variable holding address | অ্যাড্রেস ধরে রাখা ভেরিয়েবল | int *p |
| Reference | Alias for an existing var | বিদ্যমান ভেরিয়েবলের নামান্তর | int& r |
| Dereference | Access value at pointer | পয়েন্টার থেকে মান পড়া | *p |
| Address-of | Get the memory address | মেমরির ঠিকানা নেওয়া | &x |
| Stack / Heap | Auto / dynamic memory | অটো / ডায়নামিক মেমরি | int x; / new int |
| Class | Blueprint for objects | অবজেক্টের নকশা | class Complex |
| Object | Instance of class | ক্লাসের উদাহরণ | Complex c(3,4) |
| Member | Variable/function inside class | ক্লাসের সদস্য | c.real() |
| Constructor | Special init function | ইনিশিয়াল ফাংশন | Complex(float,float) |
| Destructor | Cleanup function | ক্লিনআপ ফাংশন | ~Complex() |
| Public / Private | Access level | পাবলিক / প্রাইভেট | private: |
| Function declaration | Signature only | শুধু সিগনেচার | bool is_prime(int); |
| Function definition | Body | বডি | bool is_prime(int n){…} |
| Pass by value | Copy the argument | কপি পাঠানো | void f(int) |
| Pass by reference | Share the argument | শেয়ার পাঠানো | void f(int&) |
| Pass by pointer | Address of argument | অ্যাড্রেস পাঠানো | void f(int*) |
| Inline | Compiler may substitute body | কম্পাইলার বডি বসাতে পারে | inline int sq(int) |
| Optimisation flag | Compiler speed setting | অপটিমাইজেশন স্তর | -O2, -O3 |
std::endl |
Newline + flush | নতুন লাইন + ফ্লাশ | cout<<endl; |
4. Deep Theory Explanation¶
4.1 Build cycle¶
hello.cpp ── preprocess ──▶ hello.i (#include resolved)
── compile ──▶ hello.s (assembly)
── assemble ──▶ hello.o (object)
── link ──▶ hello (executable)
g++ does all four steps by default: g++ hello.cpp -o hello. To stop earlier: -E preprocess, -S assembly, -c object only.
বাংলায়: g++ এক কমান্ডে চারটা ধাপ চালায়: preprocessor (#include বসানো), compiler (C++ থেকে assembly), assembler (assembly থেকে machine code), আর linker (সব জোড়া লাগিয়ে executable)। -E, -S, -c flag দিয়ে যেকোনো ধাপে থেমে যাওয়া যায় — কোন flag কোন ফাইল (.i, .s, .o) বানায় সেটা পরীক্ষার নিশ্চিত প্রশ্ন, Chapter 12-তে এর পূর্ণ বিবরণ আছে।
Useful flags:
| Flag | Meaning |
|---|---|
-O0/-O1/-O2/-O3 |
Optimisation level |
-Ofast |
Beyond -O3, may break IEEE-754 |
-g |
Debug info |
-Wall -Wextra -Wpedantic |
Warnings |
-std=c++17 / -std=c++20 |
Language standard |
-march=native |
Tune for current CPU |
-fopenmp |
Enable OpenMP |
-I<dir> |
Add include path |
-L<dir> -lname |
Library paths |
4.2 Variables, types, sizeof¶
(example02.cpp)
int a = 34; // 4 bytes
char b = 'k'; // 1 byte
float c = 32.56; // 4 bytes
std::cout << sizeof(int); // → 4
4.3 Scope & namespaces¶
(example03.cpp, example04.cpp)
int a = 34; // global
int main() {
int a = 23; // local — shadows global
std::cout << a; // 23
std::cout << ::a; // 34 — global qualifier
}
Namespaces:
namespace box {
int a = 0;
}
int main() {
int a = 23;
box::a = 34;
std::cout << a << " " << box::a; // 23 34
}
std:: is the namespace of the C++ standard library; using namespace std; makes names unqualified (avoid in headers).
বাংলায়: Scope মানে একটা নাম কোথা থেকে দেখা যায়। Local variable একই নামের global-কে ঢেকে দেয় (shadowing); তখন ::a লিখে global-টা ধরতে হয়, আর box::a লিখে box namespace-এরটা। using namespace std লিখলে সব std:: নাম খালি হয়ে যায় — header ফাইলে এটা করা খারাপ অভ্যাস, কারণ নামের সংঘর্ষ হতে পারে। পরীক্ষায় shadowing-এর output predict (23 নাকি 34?) খুব প্রিয় প্রশ্ন।
4.4 Pointers¶
(example05.cpp)
int b = 23;
int *a = &b; // a holds &b
std::cout << a; // address (e.g. 0x7ffd...)
std::cout << *a; // 23 — dereference
Three operators:
&x— address ofx.*p— value pointed to byp(as expression).int *p— declaration of pointer to int.
Null pointer: int *p = nullptr;. Dereferencing it crashes the program.
Pointer diagram — two boxes in memory, the pointer's value is the other box's address:
int b = 23; int *p = &b;
address: 0x7ffc1000 address: 0x7ffc1008
┌─────────────────────┐ ┌─────────────────────┐
│ b = 23 │ ◄──────────│ p = 0x7ffc1000 │
└─────────────────────┘ "points └─────────────────────┘
to"
&b ─► 0x7ffc1000 (address-of operator)
p ─► 0x7ffc1000 (the value stored in p IS that address)
*p ─► 23 (dereference: follow the arrow, read the box)
*p = 5; writes 5 into b's box ─► b is now 5
বাংলায়: ছবিটা মাথায় গেঁথে নিন: b একটা বাক্স যাতে 23 আছে, আর p আরেকটা বাক্স যাতে b-র ঠিকানা লেখা আছে। &b মানে "b-র ঠিকানা দাও", p-এর ভেতরের মানটাই সেই ঠিকানা, আর p মানে "তীরচিহ্ন ধরে গিয়ে বাক্সটা খোলো"। p = 5 লিখলে আসলে b-ই বদলায় — কারণ দুজনে একই বাক্সের কথা বলছে। উদ্যোগহীন (uninitialised) pointer dereference করলেই segfault — পরীক্ষার প্রিয় bug।
4.5 Arrays and pointer arithmetic¶
(example06.cpp, example07.cpp)
int a[5] = {34,76,48,55,7};
for (int i=0; i<5; ++i)
std::cout << a[i] << "\n";
int *b = &a[0]; // points at a[0]
for (int i=0; i<5; ++i)
std::cout << (b+i) << " : " << *(b+i) << "\n"; // pointer + int = next int
a[i]is sugar for*(a+i).- Pointer arithmetic moves by
sizeof(type)bytes per step. - Be careful not to read past
a[4]— undefined behaviour.
বাংলায়: Array-র নামটা আসলে প্রথম element-এর ঠিকানায় "ক্ষয়" (decay) হয়ে যায়, তাই a[i] আর *(a+i) হুবহু একই জিনিস। Pointer-এ +1 করলে 1 byte নয়, sizeof(type) byte এগোয় — int-এ 4, double-এ 8। ঠিক কত byte এগোবে সেই হিসাবটা §4.11-এ hex ঠিকানা দিয়ে করা আছে; পরীক্ষায় ঠিকানা মেলাতে দেওয়া হয়।
4.6 Conditionals & loops¶
(example08.cpp)
for (int i=0; i<5; ++i) {
if (a[i] % 2 == 0) {
std::cout << "a[" << i << "] = " << a[i] << "\n";
}
}
Other forms: if (...) {} else if (...) {} else {}, switch, while, do-while, for.
Ternary: cout << (n>0 ? "pos" : "non-pos");.
4.7 Functions¶
(example09.cpp)
bool is_prime(int num); // declaration
bool is_prime(int num) // definition
{
if (num<2) return false;
for (int i=2; i<num; ++i)
if (num%i==0) return false;
return true;
}
A declaration (prototype) tells the compiler the signature; a definition gives the body. You can put declarations in .h files and definitions in .cpp files.
4.8 Call by value / pointer / reference¶
(example10.cpp)
void callByValue(int a) { a++; } // copy → caller's var unchanged
void callByPointer(int* a) { (*a)++; } // modify via address
void callByReference(int& a) { a++; } // alias → modifies caller
Modern C++ prefers int&; pointers (int*) are needed when you may pass nullptr or arrays. Const-references (const T&) are the fast read-only idiom for big objects (CFD fields).
CALL BY VALUE: void f(int a) CALL BY REFERENCE: void f(int& a)
────────────────────────────────── ──────────────────────────────────
caller caller
┌────────────────┐ ┌────────────────┐
│ x = 34 │ │ x = 34 ─► 35 │◄──────────┐
└───────┬────────┘ └────────────────┘ │
│ the VALUE 34 is COPIED ▲ │
▼ │ alias: a IS x │
callee f callee f │ │
┌────────────────┐ ┌────────┴──────────────────┐│
│ a = 34 ─► 35 │ separate box! │ a has NO box of its own — ││
└────────────────┘ │ it refers straight into ├┘
a++ changes only the copy │ the caller's box │
copy destroyed at return └───────────────────────────┘
caller still sees x = 34 a++ ─► caller sees x = 35
বাংলায়: By value মানে callee নিজের আলাদা বাক্সে কপি পায় — সেখানে যা-ই করুক, caller-এর x অক্ষত থাকে আর return-এর সময় কপিটা মুছে যায়। By reference মানে a-র নিজের কোনো বাক্সই নেই, ওটা caller-এর x-এরই আরেক নাম — a++ করলেই x বদলে যায়। বড় object-এর জন্য const T& হলো সেরা সমাধান: কপিও হয় না, ভুলে বদলানোও যায় না। পরীক্ষায় example10.cpp-এর "34 নাকি 35" প্রশ্নটা এই ছবির ওপরেই দাঁড়িয়ে।
4.9 Classes and objects¶
(task_02_solution.cpp — Complex class, full)
class Complex {
private:
float a;
float b;
public:
Complex(float x, float y) : a(x), b(y) {} // constructor with init list
void display();
float real();
float img();
Complex add(Complex c1, Complex c2);
Complex multiply(Complex c1, Complex c2);
float modulus();
};
void Complex::display() { std::cout << a << "+" << b << "i\n"; }
float Complex::real() { return a; }
float Complex::img() { return b; }
Complex Complex::add(Complex c1, Complex c2) {
return Complex(c1.real()+c2.real(), c1.img()+c2.img());
}
Complex Complex::multiply(Complex c1, Complex c2) {
float r = c1.real()*c2.real() - c1.img()*c2.img();
float i = c1.real()*c2.img() + c1.img()*c2.real();
return Complex(r, i);
}
float Complex::modulus() { return std::sqrt(a*a + b*b); }
Concepts:
class { private: …; public: …; };— members default to private.- Constructor
Complex(float, float)— no return type, automatically called when an object is created. - Member functions accessed via
obj.method(). Complex::methoddefinition syntax outside the class.
বাংলায়: Class মানে data আর function একসাথে বেঁধে রাখা: a, b হলো private (বাইরে থেকে ছোঁয়া যায় না), আর display, add-এর মতো method গুলো public। Constructor-এর নাম class-এর নামের সমান এবং কোনো return type নেই — void-ও নয়, এটা লিখলেই compile error। আর class-এর শেষের ; (semicolon) ভুলে গেলে পরের লাইনে অদ্ভুত error আসে। Complex class-টা পরীক্ষার সবচেয়ে দামি প্রশ্ন — পুরোটা মুখস্থ করে হাতে লিখে প্র্যাকটিস করুন।
4.10 Diagrams from V09¶
- Memory layout diagram —
int a; int *p=&a;showing two boxes with arrow. - Call-by-value vs call-by-reference table — caller's
avs callee'sa. - Class diagram —
Complexwith private fields, public methods.
4.11 Memory mathematics — sizeof, pointer arithmetic, copy cost¶
The sizeof table (x86-64 Linux, LP64 model) — memorise:
| Type | sizeof (bytes) |
Notes |
|---|---|---|
char |
1 | by definition |
bool |
1 | |
int |
4 | 32-bit two's complement |
float |
4 | IEEE-754 single precision |
long |
8 | on Linux x86-64 |
double |
8 | IEEE-754 double precision |
any pointer T* |
8 | a 64-bit address — independent of T |
Pointer arithmetic rule. For a pointer p of type T* and integer \(k\):
Worked example 1 — int array. Let int a[5] start at address 0x7ffc2000. With \(\mathrm{sizeof(int)} = 4\):
| Expression | Computation | Address |
|---|---|---|
a + 0 |
\(\texttt{0x7ffc2000} + 0\cdot 4\) | 0x7ffc2000 |
a + 1 |
\(\texttt{0x7ffc2000} + 1\cdot 4\) | 0x7ffc2004 |
a + 2 |
\(\texttt{0x7ffc2000} + 2\cdot 4\) | 0x7ffc2008 |
a + 4 |
\(\texttt{0x7ffc2000} + 4\cdot 4 = +16 = \texttt{0x10}\) | 0x7ffc2010 |
Worked example 2 — double array. Let double d[5] start at 0x7ffc3000. With \(\mathrm{sizeof(double)} = 8\):
| Expression | Computation | Address |
|---|---|---|
d + 1 |
\(+1\cdot 8 = \texttt{0x8}\) | 0x7ffc3008 |
d + 3 |
\(+3\cdot 8 = 24 = \texttt{0x18}\) | 0x7ffc3018 |
d + 4 |
\(+4\cdot 8 = 32 = \texttt{0x20}\) | 0x7ffc3020 |
Same +1, different byte step — that is the whole point of typed pointers. Pointer difference works backwards: (a+4) - a = 4 elements = \(4 \cdot 4 = 16\) bytes.
Copy cost: pass-by-value vs pass-by-reference. A struct holding \(N\) doubles occupies \(8N\) bytes. Passing it to a function costs:
Worked numbers for a CFD field with \(N = 10^6\) cells: by value, every call copies \(8 \cdot 10^6 = 8\,\mathrm{MB}\); at a memory bandwidth of \(10\,\mathrm{GB/s}\) that is \(8\cdot 10^6 / 10^{10} = 0.8\,\mathrm{ms}\) per call — inside a loop over \(10^4\) timesteps that is 8 seconds of pure copying. By reference the cost stays at 8 bytes, a ratio of \(10^6\). Hence the HPC idiom void solve(const Field& f).
Process memory layout — where everything lives:
high addresses
┌────────────────────────────────┐ ~0x7fffffffffff
│ STACK │ function frames & locals:
│ main() frame: int x; p; ... │ int x; double y[10];
│ f() frame: params, locals │ every call PUSHES a frame,
│ │ │ every return POPS it
│ ▼ grows DOWN │ (size limit ~8 MB!)
├────────────────────────────────┤
│ (unused gap) │
├────────────────────────────────┤
│ ▲ grows UP │
│ │ │
│ HEAP │ new double[N] lives HERE;
│ new double[1000000] = 8 MB │ survives until delete[];
│ block sits here; only the │ the POINTER to it sits in a
│ pointer to it is on the stack│ stack frame
├────────────────────────────────┤
│ DATA + BSS │ globals & statics:
│ int g = 34; (data) │ initialised -> data
│ static int cnt; (bss, zeroed)│ uninitialised -> bss
├────────────────────────────────┤
│ TEXT │ the machine code itself:
│ main, swap, Complex::add ... │ read-only
└────────────────────────────────┘ low addresses
বাংলায়: Stack হলো ছোট আর স্বয়ংক্রিয় — প্রতিটা function call-এ একটা frame জমে, return করলেই মুছে যায়; সাধারণ local variable এখানেই থাকে। Heap হলো বড় কিন্তু manual — new দিয়ে নিতে হয়, delete দিয়ে ফেরত দিতে হয়, না দিলে memory leak। মনে রাখুন: stack-এর সীমা মোটে ~8 MB, তাই কোটি cell-এর CFD field সবসময় heap-এ (new বা std::vector) রাখতে হয় — stack-এ নিলেই stack overflow।
4.12 Complex-number mathematics and the Complex class¶
For \(z_1 = a + bi\) and \(z_2 = c + di\) (with \(i^2 = -1\)):
Worked example by hand: \((3+4i)\cdot(1-2i)\). Here \(a=3,\ b=4,\ c=1,\ d=-2\):
- Real part: \(ac - bd = 3\cdot 1 - 4\cdot(-2) = 3 + 8 = 11\)
- Imaginary part: \(ad + bc = 3\cdot(-2) + 4\cdot 1 = -6 + 4 = -2\)
- Result: \(11 - 2i\).
Also: \((3+4i) + (1-2i) = 4 + 2i\); \(\ |3+4i| = \sqrt{9+16} = \sqrt{25} = 5\); \(\ \overline{3+4i} = 3-4i\) and \((3+4i)(3-4i) = 9 + 16 = 25 = |z|^2\).
The code that produces exactly this (complete, compilable Complex class with constructor, operator+, operator*, abs, conj, print):
#include <iostream>
#include <cmath>
class Complex {
private:
double re, im;
public:
Complex(double r = 0.0, double i = 0.0) : re(r), im(i) {} // constructor
Complex operator+(const Complex& o) const { // (a+c) + (b+d)i
return Complex(re + o.re, im + o.im);
}
Complex operator*(const Complex& o) const { // (ac-bd) + (ad+bc)i
return Complex(re * o.re - im * o.im,
re * o.im + im * o.re);
}
Complex conj() const { return Complex(re, -im); } // a - bi
double abs() const { return std::sqrt(re*re + im*im); } // sqrt(a^2+b^2)
void print() const {
std::cout << re << (im >= 0 ? "+" : "") << im << "i\n";
}
};
int main() {
Complex z1(3.0, 4.0), z2(1.0, -2.0);
Complex s = z1 + z2; // 4+2i
Complex p = z1 * z2; // 11-2i
s.print();
p.print();
std::cout << "abs(z1) = " << z1.abs() << "\n"; // 5
z1.conj().print(); // 3-4i
return 0;
}
Compile and run: g++ -O2 -std=c++17 -Wall complex.cpp -o complex && ./complex
বাংলায়: Complex গুণের সূত্রটা \((ac-bd) + (ad+bc)i\) মুখস্থ রাখুন — মাইনাসটা real part-এ, কারণ \(i\cdot i = -1\)। Modulus মানে মূলবিন্দু থেকে দূরত্ব: \(\sqrt{a^{2}+b^{2}}\), আর conjugate মানে শুধু imaginary অংশের চিহ্ন উল্টে দেওয়া। হাতে হিসাব করে তারপর কোড মিলিয়ে দেখুন — পরীক্ষায় দুটোই চাওয়া হয়: হাতের হিসাব এবং operator overload — দুটোই প্রস্তুত রাখুন।
4.13 Pointer & call diagrams¶
(a) Pointer ↔ variable:
int a = 5; int *p = &a;
┌──────────────┐ ┌──────────────┐
│ a │ │ p │
│ value: 5 │◄───────┤ value:0x7ffc │ p stores a's ADDRESS
│ addr: 0x7ffc │ │ addr: 0x7ff4 │
└──────────────┘ └──────────────┘
*p reads/writes the box p points to: *p = 7 ⇒ a == 7
&a asks for a's address: &a == 0x7ffc
(b) Pass-by-value vs pass-by-reference:
void f(int x) — BY VALUE void g(int& x) — BY REFERENCE
caller: a=5 caller: a=5
┌────────┐ copy ┌────────┐ ┌────────┐ alias ┌────────┐
│ a = 5 │ ───────► │ x = 5 │ │ a = 5 │◄────────►│ x │
└────────┘ └────────┘ └────────┘ └────────┘
x++ → x=6, a STILL 5 x++ → a == 6 (same box!)
copy dies at return no copy, no extra memory
বাংলায়: ছবি দুটো মাথায় গেঁথে নাও: pointer হলো আলাদা একটা বাক্স যাতে অন্য বাক্সের ঠিকানা লেখা; reference হলো একই বাক্সের দ্বিতীয় নাম। value-তে পাঠালে ফটোকপি যায় — আসল কাগজ অক্ষত; reference/pointer-এ পাঠালে আসল কাগজটাই যায়। CFD-তে কোটি-cell-এর array ফটোকপি করা অসম্ভব — তাই সব বড় জিনিস
const T&বা pointer দিয়ে যায়।
5. Command / Syntax / Code Breakdown¶
g++ src.cpp -o exe¶
Compile + link to exe. Use -O2 -Wall -std=c++17 in production.
#include <iostream>¶
Bring in the iostream header so std::cout works.
int main() { … return 0; }¶
Program entry point. 0 indicates success.
int a = 5;¶
Variable declaration + init.
int& r = a;¶
Reference declaration. Must be initialised.
int *p = &a;¶
Pointer declaration + init. &a = address of a.
*p = 7;¶
Write through pointer.
for (int i=0; i<n; ++i) { … }¶
C-style loop.
class C { public: int x; };¶
Trivial class.
void f(int); then void f(int x){…}¶
Declaration + definition.
Complex c(3,4);¶
Construct an object via constructor.
6. Mandatory Practical Examples¶
Example 6.1 — Hello world (example01.cpp)¶
Expected output: Hello World
#include <iostream>brings in the IO library.int main()mandatory entry.std::cout << "Hello World" << std::endl;print + newline + flush.return 0;exit success.
Example 6.2 — Sizes (example02.cpp)¶
(Note: the lecture's example03 prints sizeof(c) (a float) under "Size of int" — typo. Exam-tip: mention this.)
Example 6.3 — Local vs global, scope resolution (example03.cpp)¶
::a accesses the global.
Example 6.4 — Namespace (example04.cpp)¶
Example 6.5 — Pointers (example05.cpp)¶
Example 6.6 — Array iteration (example06.cpp)¶
Example 6.7 — Pointer arithmetic over array (example07.cpp)¶
(Each address is +4 bytes apart since int is 4 bytes.)
Example 6.8 — Conditional filter (example08.cpp)¶
(Only even values printed.)
Example 6.9 — Function: is_prime (example09.cpp)¶
Example 6.10 — Call by value vs reference (example10.cpp)¶
By value: copy is incremented locally and discarded. By reference: original is modified.
Example 6.11 — Swap (E09 Task 1, solution)¶
#include <iostream>
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int a=34, b=25;
std::cout << "Before: a=" << a << " b=" << b << "\n";
swap(&a, &b);
std::cout << "After: a=" << a << " b=" << b << "\n";
return 0;
}
Output:
Real-Life HPC/CFD Meaning. Swap-by-pointer demonstrates how solvers pass fields by address — never copy multi-MB arrays.
Written Exam Relevance. "Implement swap" appears in nearly every C++ basics exam.
Example 6.12 — Complex class (E09 Task 2, full solution)¶
#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() { return a; }
float img() { return b; }
Complex add(Complex c1, Complex c2) {
return Complex(c1.real()+c2.real(), c1.img()+c2.img());
}
Complex multiply(Complex c1, Complex c2) {
return Complex(c1.real()*c2.real() - c1.img()*c2.img(),
c1.real()*c2.img() + c1.img()*c2.real());
}
float modulus() { return std::sqrt(a*a + b*b); }
};
int main() {
Complex c1(3,4), c2(4,5), c3(0,0);
c1.display();
c2.display();
c3 = c3.add(c1,c2); c3.display(); // 7+9i
c3 = c3.multiply(c1,c2); c3.display(); // -8+31i
std::cout << "Modulus of c3 = " << c3.modulus() << "\n"; // sqrt(64+961) ≈ 32.02
return 0;
}
Expected output:
Check the multiplication by hand: \((3+4i)(4+5i) = (3\cdot4 - 4\cdot5) + (3\cdot5 + 4\cdot4)i = -8 + 31i\). Modulus: \(\sqrt{(-8)^2+31^2} = \sqrt{64+961} = \sqrt{1025} \approx 32.0156\).
Real-Life Meaning. The same OO pattern is used in CFD for Vector, Tensor, Field, BoundaryCondition classes.
Written Exam Relevance. Top-mark question often: "Complete the class so that c3 = c3.add(c1,c2) works." Provide the constructor + member function definitions.
7. Real HPC/CFD Workflow¶
# 1. Edit
vim solver.cpp
# 2. Compile fast
g++ -O3 -march=native -std=c++17 -fopenmp solver.cpp -o solver
# 3. Run (single-thread)
./solver in.dat > out.log
# 4. Run (8 OpenMP threads)
OMP_NUM_THREADS=8 ./solver in.dat > out.log
# 5. Profile
g++ -O2 -g -pg solver.cpp -o solver_prof
./solver_prof in.dat
gprof solver_prof gmon.out | head
# 6. Debug (Ch.13)
g++ -O0 -g solver.cpp -o solver_dbg
gdb ./solver_dbg
8. Exercises and Solutions¶
E09 Task 1 (swap) — see 6.11.
Marking scheme (8 marks):
- 1 declaration
void swap(int*, int*). - 2 use of
*a,*bto read/write. - 1 temporary variable.
- 2 main: call as
swap(&a,&b). - 2 correct output before/after.
Common mistake. Writing void swap(int a, int b){ int t=a; a=b; b=t; } (call-by-value) — does nothing.
Harder version. Use references: void swap(int& a, int& b){ int t=a; a=b; b=t; } and call swap(a,b).
E09 Task 2 (Complex class) — see 6.12.
Marking scheme (12 marks):
- 1
private:data members. - 2 constructor.
- 1
display. - 1
real/imggetters. - 2
addcorrectness. - 2
multiplycorrectness. - 1 modulus formula.
- 1 main: construct + invoke.
- 1 expected output.
Common mistakes.
- Forgot
;after class definition. - Constructor with
voidreturn type → compile error (constructors have no return type). - Used
pow(a,2)+pow(b,2)instead ofa*a+b*b— works but slower in HPC; prefer multiplication. - class default access is private; forgot
public:→ main can't call methods.
Harder version. Add an operator+ / operator* overload so c3 = c1 + c2. Use a const-ref signature to avoid copies:
বাংলায়: operator overloading-এর তিনটা exam-checkpoint: (১) parameter
const Complex&— copy এড়াতে, (২) ফাংশনের শেষেconst— নিজের object বদলায় না বোঝাতে, (৩) নতুন object return। এই তিনটা লিখলেই "harder version" -এর পুরো নম্বর।
9. Written Exam Focus¶
9.1 Short Answers¶
Q. Difference between pointer and reference.
A. A pointer holds an address — can be null, can be re-assigned, requires * to dereference. A reference is an alias to an existing variable — cannot be null, cannot be re-bound, used like the original.
Q. What does & do in C++?
A. As a unary operator on a value (&x), it returns the address. In a type declaration (int& r), it indicates a reference type.
Q. Why use call-by-reference? A. Avoids copying the argument and lets the function modify the caller's variable. Crucial for large CFD fields.
Q. Difference between class declaration and definition.
A. A declaration introduces the name (class Complex;); a definition provides the full body (class Complex { … };).
Q. What is std::endl?
A. Inserts '\n' AND flushes the stream. For performance use '\n' alone.
9.2 Medium Answers¶
Q. (8 marks) Explain pointers and arrays in C++ with code.
A. A pointer (int *p) stores a memory address. &x yields the address of x; *p accesses the value at the address. An array int a[5] is contiguous memory; the name decays to a pointer to its first element. Pointer arithmetic *(a+i) is equivalent to a[i]. Example:
Common mistake: dereferencing past the end → undefined behaviour.
Q. (5 marks) Compare call by value vs reference vs pointer.
A. Value: a copy is made; original untouched (void f(int)). Reference: alias to original; modifications propagate (void f(int&)). Pointer: caller passes the address (f(&x)), callee modifies via *p (void f(int*)). References are cleanest; pointers are needed when nullability or array passing is required.
9.3 Long Answer (12 marks)¶
Q. Walk through the Complex class implementation.
A.
Introduction. The Complex class encapsulates a complex number with real (a) and imaginary (b) parts and provides operations.
Main concept. OO encapsulation: data is private, methods are public. A constructor Complex(float, float) initialises the state.
Step-by-step.
- Headers
<iostream>,<cmath>forstd::sqrt. class Complex { private: float a,b; public: … };- Constructor with member-init list:
Complex(float x,float y) : a(x), b(y) {}. - Member functions defined outside with
Complex::name. - add: \((c_1.a+c_2.a) + (c_1.b+c_2.b)i\).
- multiply: \((ac-bd) + (ad+bc)i\).
- modulus: \(\sqrt{a^2+b^2}\).
Example. \((3+4i)(4+5i) = (12-20) + (15+16)i = -8+31i\).
Real HPC/CFD link. The same OO pattern wraps Vector, Tensor, Field, BoundaryCondition in OpenFOAM. Composition + inheritance = the C++ way to model physics.
Conclusion. Classes provide reusable, type-safe abstractions central to HPC code.
9.4 Output Prediction¶
example10.cpp predicted output:
9.5 Comparison¶
| Pointer | Reference | |
|---|---|---|
| Syntax | int *p; *p |
int& r; r |
| Reseat | yes | no |
| Null | yes | no |
| Indirection | explicit | implicit |
| Use | nullable, array, dynamic | safer alias |
| class | struct | |
|---|---|---|
| Default access | private | public |
| Used for | rich objects | POD |
9.6 Templates¶
Compile template: g++ -O2 -std=c++17 -Wall -fopenmp file.cpp -o exe; ./exe
Pointer template: int x=5; int *p=&x; *p=7; cout<<x; → 7.
Class template:
class T {
private: /* data */;
public:
T(args) : /* init list */ {}
ret method(args);
};
ret T::method(args) { /* ... */ }
Swap-by-pointer template: see 6.11.
9.7 Marking Scheme — "Complex class with operator+" (10 marks)¶
- 2 class skeleton (private/public).
- 2 constructor.
- 1 display.
- 2 add/multiply/modulus.
- 2
operator+overload (const Complex&). - 1 expected output.
10. Very Hard Questions¶
Beginner
- Compile hello.cpp. →
g++ hello.cpp -o hello. - Print "hi". →
std::cout<<"hi"<<std::endl; - Declare an int. →
int a=5; - Address of a. →
&a. - Pointer to a. →
int *p=&a;
Intermediate
- Loop over array length 5. →
for(int i=0;i<5;++i) ... - Pass int by reference. →
void f(int& a). - Class with public method display(). → see Complex.
- Constructor with init list. →
T():a(0){}. - Why is
using namespace stdbad in headers? → name pollution.
Hard
- Why does
f(int)not modify the caller's variable? → call-by-value copies. - What's wrong with
int *p; *p=5;? → uninitialised pointer (UB/segfault). - Difference between
deleteanddelete[]. → single object vs array. - Why is endl slow? → flushes the buffer.
- Write a function returning an object of class C. → return by value uses copy/move.
Very Hard
- Why do CFD solvers prefer
const Field&overField? → avoid expensive copies. - Difference between stack and heap allocation. → automatic vs
new/delete, lifetime. - What happens if you forget
;after class? → cascading errors at the next declaration.
Deep Integration
- How does pointer arithmetic underpin SIMD vectorisation? → contiguous memory, stride 1.
- Compare RAII to manual new/delete. → constructor acquires, destructor releases — exception-safe.
Coding/Command
- Implement
swap(int& a, int& b). → see harder version in §8. - Add
operator+to Complex. → see 9.7.
Debugging
*p=10;placed beforeint a; p=&a;— what happens? → p is dereferenced uninitialised; segfault/UB.class C{int x;}; C c; c.x=5;— error? → x is private by default; needpublic:or an accessor.
Long Written
- (250 words) Discuss why HPC kernels avoid copying data — call-by-reference vs pointer trade-offs.
11. Debugging and Mistake Analysis¶
| Mistake | Why wrong | Correct | Explanation |
|---|---|---|---|
if(a=5) |
assignment, not equality | if(a==5) |
classic |
int *p; *p=5; |
uninitialised | int a; int *p=&a; *p=5; |
dereference safe pointer |
swap(a,b) by value |
no-op | swap(&a,&b) (ptr) or int& version |
reference types |
Forgot ; after class |
cascade errors | class X { … }; |
semicolon |
private: accessed outside |
error | accessor or public: |
encapsulation |
Mismatched new/delete |
leak / UB | delete[] for arrays |
RAII / smart pointers |
using namespace std in .h |
symbol clashes | qualify std:: |
hygiene |
cout << endl in tight loop |
slow flushes | '\n' |
performance |
| int vs unsigned compare | warnings/UB-ish bugs | be consistent | -Wsign-compare |
Forgot return 0 |
UB pre-C++11 | always return | habit |
বাংলায়: পরীক্ষায় "find the bug" এলে আগে এই চারটা খোঁজো:
if(a=5)(একটা=), uninitialised pointer, value-passed swap, আর class-এর পরে;নেই। এই চারটাই সব exam-এর ৮০% bug।
12. Mini Project for Mastery¶
Goal: Implement a vector-of-Complex with sum and modulus.
#include <iostream>
#include <vector>
#include <cmath>
class Complex {
private:
float a, b;
public:
Complex(float x=0, float y=0) : a(x), b(y) {}
float real() const { return a; }
float img() const { return b; }
float modulus() const { return std::sqrt(a*a+b*b); }
Complex operator+(const Complex& o) const { return {a+o.a, b+o.b}; }
friend std::ostream& operator<<(std::ostream& os, const Complex& c){
return os << c.a << (c.b>=0?"+":"") << c.b << "i";
}
};
int main() {
std::vector<Complex> v = { {1,2}, {3,4}, {5,6} };
Complex sum;
for (const auto& c : v) sum = sum + c;
std::cout << "sum = " << sum << "\n";
std::cout << "modulus = " << sum.modulus() << "\n";
}
Compile: g++ -O2 -std=c++17 mini.cpp -o mini && ./mini
Output:
Hand-check: \((1+3+5) + (2+4+6)i = 9+12i\), \(|9+12i| = \sqrt{81+144} = \sqrt{225} = 15\).
Connection to exam: class, constructor with defaults, const-correctness, operator overload, friend, range-based for — all top-mark idioms.
13. Final Chapter Cheat Sheet¶
| Item | Memorise |
|---|---|
| Compile | g++ -O2 -std=c++17 -Wall src.cpp -o exe |
std::cout << x << '\n'; |
|
| Read | std::cin >> x; |
| Types | int 4B, char 1B, float 4B, double 8B, bool 1B, ptr 8B |
| Pointer | int *p = &a; *p = 7; |
| Reference | int& r = a; r = 7; |
| Array | int a[5]={…}; a[i] == *(a+i) |
| Pointer arithmetic | p+1 advances by sizeof(T) bytes |
| Function decl/def | ret name(args); / ret name(args){…} |
| Pass by val/ref/ptr | f(int), f(int&), f(int*) |
| Class | class C{ private:…; public:…; }; |
| Constructor | C(args):member(args){} |
| Member def | ret C::method(args){…} |
| Modulus | \(\sqrt{a^2+b^2}\) |
| Complex add | \((a+c)+(b+d)i\) |
| Complex mul | \((ac-bd)+(ad+bc)i\) |
| Optimisation | -O2 -march=native |
| Debug | -O0 -g, gdb |
| Trap | if(a=5) (assignment) |
| Top phrase | "References are aliases, pointers are addresses; both avoid copying — vital for HPC." |
14. Mock Exam — Four Levels¶
Level 1 — Basic (definitions & syntax)¶
Q1. Declare a pointer to double and make it point to variable t.
Solution: double *p = &t;
Q2. What is printed? int a=3; int& r=a; r=8; std::cout<<a;
Solution: 8 — r is an alias for a.
Q3. Give the g++ command compiling main.cpp with warnings and C++17.
Solution: g++ -std=c++17 -Wall main.cpp -o main
Q4. What are the sizes of char, int, double, and a pointer on x86-64?
Solution: 1, 4, 8, 8 bytes.
Q5. Write the signature of a function norm that takes a Complex by const reference and returns float.
Solution: float norm(const Complex& c);
Level 2 — Intuitive (predict the output / explain why)¶
Q1. Predict:
Solution: 30 20 — *(p+2) ≡ a[2]; p[1] ≡ a[1]. Identical notations.
Q2. Why does this NOT compile? int& r;
Solution: References must be bound at initialisation — there is no "null reference".
Q3. Predict:
Solution: 1 — f gets a copy; the 99 dies with the copy.
Q4. double* q = new double[1000]; — where do q and the 1000 doubles live?
Solution: q itself is a local variable on the STACK; the 8000-byte block lives on the HEAP until delete[] q.
Q5. For int a[5] at address 0x1000, what is the address of a[3]? Show the math.
Solution: \(0x1000 + 3 \times 4 = 0x100C\) — pointer arithmetic scales by sizeof(int)=4.
Level 3 — Hard (exam level)¶
Q1. (8 marks) Compute \((2-3i)(1+4i)\) by hand using the multiplication formula, then write the two lines of C++ using the Complex class of 6.12 that compute and print it.
Solution: \((ac-bd) = 2\cdot1-(-3)(4) = 2+12 = 14\); \((ad+bc) = 2\cdot4+(-3)(1) = 8-3 = 5\) → \(14+5i\).
বাংলা ইঙ্গিত: মাইনাস-চিহ্নগুলো সাবধানে: \(-bd = -(-3)(4) = +12\) — sign-এর ভুলেই বেশিরভাগ নম্বর যায়।Q2. (8 marks) This compiles but crashes. Find the bug and fix it:
Solution: arr is a stack array that dies when the function returns — the returned pointer dangles. Fix: heap-allocate int* arr = new int[n]; (caller must delete[]), or better return std::vector<int>.
বাংলা ইঙ্গিত: function-এর ভেতরের local array return করা মানে ভাড়া-বাড়ির চাবি ফেরত দিয়ে ঠিকানা বিলি করা — stack frame শেষ, মেমরিও শেষ।
Q3. (10 marks) Write a function scale(double* v, int n, double k) that multiplies an array in place, and explain why it must take a pointer (or reference) rather than by value. Then show the call for double f[3]={1,2,3} with k=2 and the resulting array.
Solution:
void scale(double* v, int n, double k){
for(int i=0;i<n;++i) v[i] *= k;
}
scale(f, 3, 2.0); // f becomes {2,4,6}
scale(f, …) লেখা যায়, &f[0] লিখতে হয় না।
Q4. (10 marks) Add to the Complex class: Complex conj() const and bool operator==(const Complex&) const. Write both, with one line of test code each.
Solution:
Complex conj() const { return Complex(a, -b); }
bool operator==(const Complex& o) const { return a==o.a && b==o.b; }
// tests:
Complex(3,4).conj().display(); // 3-4i
std::cout << (Complex(1,2)==Complex(1,2)); // 1
std::fabs(a-o.a)<1e-6 for bonus.)
বাংলা ইঙ্গিত: getter/conj-জাতীয় "পড়া-শুধু" method-এর শেষে const — না দিলে const object থেকে ডাকা যায় না; এই খুঁটিনাটিই top marks আলাদা করে।
Q5. (10 marks) Predict the exact output and justify each line:
int x = 5;
int* p = &x;
int& r = x;
*p = *p + 1;
r = r * 2;
std::cout << x << " " << *p << " " << r << "\n";
Solution: 12 12 12 — all three names refer to ONE box: *p+1 → x=6; r*2 → x=12; printing x, p, r reads the same 12.
বাংলা ইঙ্গিত:* pointer আর reference দুটোই থাকলে মাথায় একটাই ছবি আঁকো — বাক্স একটা, নাম তিনটা।
Level 4 — Beyond the lecture (transfer + coding)¶
Q1. Implement a minimal RAII wrapper DynArray holding double* with constructor (allocates n), destructor (frees), operator[], and a deleted copy constructor. Explain why copying must be forbidden (or deep-copied).
Solution:
class DynArray {
double* data; int n;
public:
explicit DynArray(int n_) : data(new double[n_]), n(n_) {}
~DynArray() { delete[] data; }
DynArray(const DynArray&) = delete; // no shallow copies!
DynArray& operator=(const DynArray&) = delete;
double& operator[](int i) { return data[i]; }
int size() const { return n; }
};
delete[] the same block → double free. Either delete copying or implement a deep copy.
বাংলা ইঙ্গিত: "rule of three"-র মর্ম: destructor-এ delete থাকলে copy constructor/assignment-ও সামলাতে হবে — নাহলে double free।
Q2. An OpenMP loop (Ch 14 preview) over std::vector<double> v computes a sum. Why does for(auto x : v) sum += x; copy each element, and what is the zero-copy form? Write the OpenMP version.
Solution: auto x deduces a value type → element copy each iteration. Zero-copy: for (const auto& x : v). OpenMP:
double sum = 0.0;
#pragma omp parallel for reduction(+:sum)
for (size_t i = 0; i < v.size(); ++i) sum += v[i];
& না দিলে প্রতি iteration-এ কপি — ছোট double-এ সস্তা, কিন্তু বড় class-এ মারাত্মক; অভ্যাসটা const auto&।
Q3. Memory math: a CFD field stores 3 velocity components + pressure as doubles for \(10^8\) cells. Compute the memory in GiB, and state whether passing this Field by value 4 times during one time step is feasible on a 64 GiB node.
Solution: \(4 \times 8\,\text{B} \times 10^8 = 3.2\times10^9\) B \(= 3.2/1.0737 \approx 2.98\) GiB per copy. Four by-value copies = ~11.9 GiB extra traffic + peak allocation — wasteful and possibly fatal alongside solver workspace; pass by const Field& (0 copies).
বাংলা ইঙ্গিত: GiB-এ ভাগ \(2^{30} = 1.0737\times10^9\) দিয়ে — আর সিদ্ধান্তের যুক্তি সবসময় "কপি × সাইজ বনাম RAM"।
Q4. Combine with bash (Ch 8): write a script-friendly main that reads n from argv[1], fills an array with squares, prints the sum, and returns 1 on bad input — then the one-line bash test that checks the program fails on "abc".
Solution:
#include <iostream>
#include <cstdlib>
int main(int argc, char** argv){
if (argc < 2) { std::cerr << "usage: prog n\n"; return 1; }
char* end;
long n = std::strtol(argv[1], &end, 10);
if (*end != '\0' || n <= 0) { std::cerr << "bad n\n"; return 1; }
long long sum = 0;
for (long i = 1; i <= n; ++i) sum += i*i;
std::cout << sum << "\n";
return 0;
}
./prog abc >/dev/null 2>&1 && echo BAD || echo "correctly failed"
বাংলা ইঙ্গিত: atoi চুপচাপ 0 দেয় — validation চাইলে strtol-এর end-pointer দেখতেই হবে; আর exit code-ই bash-এর সাথে C++-এর চুক্তিপত্র।
End of Chapter 11.