├── .gitignore ├── README.md ├── assets ├── CC.png ├── From NIC to Application.pdf ├── RAII.png ├── Vtables.pdf ├── binding.png ├── buffer_overflow.pdf ├── class.png ├── compilation_steps.png ├── concepts.png ├── constructor.png ├── copy.png ├── endian_1.png ├── endian_2.png ├── lambda-param.png ├── lambda.png ├── linking.png ├── meta1.png ├── meta2.png ├── no-rvo.png ├── pad.png ├── rvo.png ├── sections.png ├── sharedPtr.png ├── sharedPtr2.png ├── stack.png ├── stack.svg ├── store_buffer.png ├── syn.png └── vtables.png └── notes ├── CRTP.md ├── RAII.md ├── allocators.md ├── atomic_instructions.md ├── buffer_overflow.md ├── casting.md ├── cheat.md ├── const_constexpr.md ├── cpp_question_bank.md ├── exceptions.md ├── find.md ├── function_inlining.md ├── git-sheet.md ├── http.md ├── lambdas.md ├── latency_numbers.md ├── linux_tcp.md ├── memory_reordering.md ├── metaprogramming.md ├── move_semantics.md ├── os_booting.md ├── packet_handling.md ├── padding_packing.md ├── performance.md ├── placement_new.md ├── pre-post-increment.md ├── program_to_process.md ├── rvo.md ├── set_pq.md ├── smart_pointers.md ├── templates.md ├── tmux.md ├── vim.md └── virtual_functions.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shivam's Knowledgebase 2 | 3 | ## CS Fundamentals 4 | 5 | ### Operating Systems 6 | 7 | - Operating Systems: A Linux Kernel-Oriented Perspective by Prof. Smruti Sarangi: 8 | [Book Link](https://www.cse.iitd.ac.in/~srsarangi/osbook/index.html) 9 | - OS lectures by Prof. Sorav Bansal: [Youtube Link](https://www.youtube.com/playlist?list=PLf3ZkSCyj1tdCS2oCYACXO6x-VKpDIMB6) 10 | 11 | ### Caches 12 | 13 | - YouTube playlist by Prof. Harry Porter: [Link](https://www.youtube.com/playlist?list=PLbtzT1TYeoMgJ4NcWFuXpnF24fsiaOdGq) 14 | - Cache Coherence: See relevant videos [here](https://www.youtube.com/watch?v=ISaYWm8T8n4&list=PLUl4u3cNGP62WVs95MNq3dQBqY2vGOtQ2&index=170) 15 | - MIT Notes: [Intro](https://ocw.mit.edu/courses/6-004-computation-structures-spring-2017/pages/c14/c14s1/#17), [MESI](https://ocw.mit.edu/courses/6-004-computation-structures-spring-2017/pages/c21/c21s1/#18) 16 | 17 | ### Systems Programming 18 | 19 | - CS 361 by Prof. Chris Kanich: [YT Link](https://www.youtube.com/playlist?list=PLhy9gU5W1fvUND_5mdpbNVHC1WCIaABbP) 20 | - Some Latency Number: [Notes](notes/latency_numbers.md) 21 | - To be added 22 | 23 | ### OS 24 | 25 | - Atomic Instructions: [Notes](notes/atomic_instructions.md) 26 | - Memory Reordering: [Notes](notes/memory_reordering.md) 27 | - Padding and Packing (Aligned Memory Access): [Notes](notes/padding_packing.md) 28 | - Buffer Overflow Attacks: [Notes](notes/buffer_overflow.md) 29 | - OS Boot Process: [Notes](notes/os_booting.md) 30 | - From a Program to Process: [Notes](notes/program_to_process.md) 31 | - Stack Memory Management: [Link](https://organicprogrammer.com/2020/08/19/stack-frame/) 32 | - To be added 33 | 34 | ### Networking 35 | 36 | - From NIC to User Processes: [Notes](notes/packet_handling.md) 37 | - How The Kernel Handles A TCP Connection: [Notes](notes/linux_tcp.md) 38 | 39 | ### Some Interesting YT Videos 40 | 41 | - Revise OS Memory Management: [Link](https://www.youtube.com/watch?v=7aONIVSXiJ8&t=497s) 42 | - Why Composition better than Inheritance: [Link](https://www.youtube.com/watch?v=tXFqS31ZOFM&list=PLE28375D4AC946CC3&index=24) 43 | - `mmap` in database system: [Link](https://www.youtube.com/watch?v=1BRGU_AS25c) 44 | - How processes get more memory: [Link](https://www.youtube.com/watch?v=XV5sRaSVtXQ) 45 | - `mmap` for File Mapping: [Link](https://www.youtube.com/watch?v=m7E9piHcfr4) 46 | - `mmap` for IPC: [Link](https://www.youtube.com/watch?v=rPV6b8BUwxM) 47 | - From Silicon to Applications: [Link](https://youtu.be/5f3NJnvnk7k?si=zVW5JZbXZz8X74XI) 48 | 49 | ## C++ 50 | 51 | ### General Concepts 52 | 53 | - Move Semantics: [Notes](notes/move_semantics.md) 54 | - Casting: [Notes](notes/casting.md) 55 | - Const, const_cast<>, constexpr, consteval: [Notes](notes/const_constexpr.md) 56 | - C++ Coding Practices: [Link](https://micro-os-plus.github.io/develop/sutter-101/) 57 | - Lambdas: [Notes](notes/lambdas.md) 58 | - Smart Pointers: [Notes](notes/smart_pointers.md) 59 | - Classes and RAII: [Notes](notes/RAII.md) 60 | - Templates: [Notes](notes/templates.md) 61 | - Virtual Functions and vTables: [Notes](notes/virtual_functions.md) 62 | - HTTP using libcurl: [Notes](notes/http.md) 63 | - More C++ Study Notes: [Link](https://encelo.github.io/notes.html) 64 | - Allocators: [Notes](notes/allocators.md) 65 | - Placement New: [Notes](notes/placement_new.md) 66 | - Exceptions (throw, try/catch): [Notes](notes/exceptions.md) 67 | - `i++` vs `++i` overloading: [Notes](notes/pre-post-increment.md) 68 | - CRTP: [Notes](notes/CRTP.md) 69 | - To be added 70 | 71 | ### C++ Performance 72 | 73 | - Keep in Mind: [Notes](notes/performance.md) 74 | - Return Value Optimisation: [Notes](notes/rvo.md) 75 | - Template Metaprogramming: [Notes](notes/metaprogramming.md) 76 | - Set vs PQ: [Notes](notes/set_pq.md) 77 | - Function Inlining: [Notes](notes/function_inlining.md) 78 | 79 | ### C++ Implementation 80 | 81 | - Unique Pointer: [Code](https://github.com/Shivam5022/CPP-Internals/blob/main/includes/unique_pointer.hpp) 82 | - Shared Pointer: [Code](https://github.com/Shivam5022/CPP-Internals/blob/main/includes/shared_pointer.hpp) 83 | - Thread Pool: [Code](https://github.com/Shivam5022/CPP-Internals/blob/main/includes/thread_pool.hpp) 84 | - HashMap: [Code](https://github.com/Shivam5022/CPP-Internals/blob/main/includes/hashmap.hpp) 85 | - LRU Cache: [Code](https://github.com/Shivam5022/CPP-Internals/blob/main/includes/LRU_cache.hpp) 86 | - Scope Timer: [Code](https://github.com/Shivam5022/CPP-Internals/blob/main/includes/timer.hpp) 87 | - Memory Pool: [Code](https://github.com/Shivam5022/CPP-Internals/blob/main/includes/memory_pool.hpp) 88 | 89 | ## Development 90 | 91 | ### Work Environment 92 | 93 | - Get started with Vim/Nvim (Helix actually): [Notes](notes/vim.md) 94 | - Git command sheet: [Notes](notes/git-sheet.md) 95 | - Tmux cheat sheet: [Notes](notes/tmux.md) 96 | 97 | ### Docker 98 | 99 | - Basic Introduction by Learn Linux TV: [YT Link](https://www.youtube.com/playlist?list=PLT98CRl2KxKECHltRib03tG8pyKEzwf9t) 100 | 101 | ### CLI utilities 102 | 103 | - Cheat.sh: [Notes](/notes/cheat.md) 104 | - Find command linux: [Notes](/notes/find.md) 105 | -------------------------------------------------------------------------------- /assets/CC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/CC.png -------------------------------------------------------------------------------- /assets/From NIC to Application.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/From NIC to Application.pdf -------------------------------------------------------------------------------- /assets/RAII.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/RAII.png -------------------------------------------------------------------------------- /assets/Vtables.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/Vtables.pdf -------------------------------------------------------------------------------- /assets/binding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/binding.png -------------------------------------------------------------------------------- /assets/buffer_overflow.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/buffer_overflow.pdf -------------------------------------------------------------------------------- /assets/class.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/class.png -------------------------------------------------------------------------------- /assets/compilation_steps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/compilation_steps.png -------------------------------------------------------------------------------- /assets/concepts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/concepts.png -------------------------------------------------------------------------------- /assets/constructor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/constructor.png -------------------------------------------------------------------------------- /assets/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/copy.png -------------------------------------------------------------------------------- /assets/endian_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/endian_1.png -------------------------------------------------------------------------------- /assets/endian_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/endian_2.png -------------------------------------------------------------------------------- /assets/lambda-param.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/lambda-param.png -------------------------------------------------------------------------------- /assets/lambda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/lambda.png -------------------------------------------------------------------------------- /assets/linking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/linking.png -------------------------------------------------------------------------------- /assets/meta1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/meta1.png -------------------------------------------------------------------------------- /assets/meta2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/meta2.png -------------------------------------------------------------------------------- /assets/no-rvo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/no-rvo.png -------------------------------------------------------------------------------- /assets/pad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/pad.png -------------------------------------------------------------------------------- /assets/rvo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/rvo.png -------------------------------------------------------------------------------- /assets/sections.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/sections.png -------------------------------------------------------------------------------- /assets/sharedPtr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/sharedPtr.png -------------------------------------------------------------------------------- /assets/sharedPtr2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/sharedPtr2.png -------------------------------------------------------------------------------- /assets/stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/stack.png -------------------------------------------------------------------------------- /assets/stack.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/store_buffer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/store_buffer.png -------------------------------------------------------------------------------- /assets/syn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/syn.png -------------------------------------------------------------------------------- /assets/vtables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivam5022/Knowledgebase-SV/f2897c4b12224733de22d7ceb0383800451719d9/assets/vtables.png -------------------------------------------------------------------------------- /notes/CRTP.md: -------------------------------------------------------------------------------- 1 | ## Static Polymorphism Using CRTP 2 | 3 | Static polymorphism is a type of polymorphism that is resolved at 4 | compile time. It is primarily achieved through templates and CRTP 5 | (Curiously Recurring Template Pattern) in C++. In contrast to 6 | dynamic polymorphism (which uses virtual functions and 7 | inheritance), static polymorphism doesn’t rely on runtime checks 8 | like vtables but instead utilizes compile-time mechanisms. 9 | 10 | ```cpp 11 | #include 12 | 13 | // Base class template that uses CRTP 14 | template 15 | class Shape { 16 | public: 17 | // Static polymorphism: Calls the derived class's area() method at compile-time 18 | void draw() const { 19 | // Calling the derived class's area() method 20 | static_cast(this)->draw(); 21 | } 22 | 23 | // Common interface for all derived classes 24 | double getArea() const { 25 | // Calling the derived class's area() method using CRTP 26 | return static_cast(this)->area(); 27 | } 28 | }; 29 | 30 | // Derived class for Circle 31 | class Circle : public Shape { 32 | public: 33 | Circle(double radius) : radius_(radius) {} 34 | 35 | // Method specific to Circle 36 | void draw() const { 37 | std::cout << "Drawing Circle with radius: " << radius_ << std::endl; 38 | } 39 | 40 | // Implementation of area() for Circle 41 | double area() const { 42 | return 3.14159 * radius_ * radius_; 43 | } 44 | 45 | private: 46 | double radius_; 47 | }; 48 | 49 | // Derived class for Square 50 | class Square : public Shape { 51 | public: 52 | Square(double side) : side_(side) {} 53 | 54 | // Method specific to Square 55 | void draw() const { 56 | std::cout << "Drawing Square with side: " << side_ << std::endl; 57 | } 58 | 59 | // Implementation of area() for Square 60 | double area() const { 61 | return side_ * side_; 62 | } 63 | 64 | private: 65 | double side_; 66 | }; 67 | 68 | int main() { 69 | Circle circle(5.0); 70 | Square square(4.0); 71 | 72 | // Static polymorphism: Compile-time resolution 73 | circle.draw(); // Calls Circle's draw() 74 | square.draw(); // Calls Square's draw() 75 | 76 | std::cout << "Circle Area: " << circle.getArea() << std::endl; 77 | std::cout << "Square Area: " << square.getArea() << std::endl; 78 | 79 | return 0; 80 | } 81 | ``` 82 | > The Shape class in this static polymorphism example acts as an 83 | interface-like structure, but at compile-time. Unlike traditional 84 | dynamic polymorphism (with a virtual base class), Shape is not used 85 | for runtime polymorphism or to manage different types via base 86 | class pointers. Instead, it ensures that each derived class (like 87 | Circle or Square) implements certain methods such as draw() and area(). 88 | 89 | > It provides a common template for other shapes (like Circle or Square) to 90 | follow, forcing them to implement specific methods (draw() and area()). 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /notes/RAII.md: -------------------------------------------------------------------------------- 1 | ## More on Classes 2 | 3 | Compiler generates default functions: Constructor, Copy Constructor, Copy 4 | Assignment `only if any variant of them are not present`. 5 | 6 | ### Disallowing Functions 7 | `f() = delete` : It will prevent the compiler from generating it. 8 | 9 | ### Private Destructor 10 | It means the object cannot be stored in stack. Because when the stack unwinds, 11 | the destructor of the objects are called but this destuctor is private. 12 | 13 | Also it can only be destroyed by a factory member function or a friend function. 14 | *Yes, friends are worse than enemies*. 15 | 16 | ```cpp 17 | class MyClass { 18 | private: 19 | ~MyClass() { 20 | std::cout << "Private Destructor Called" << std::endl; 21 | } 22 | }; 23 | 24 | int main() { 25 | MyClass obj; // Error: Destructor is private, stack object can't be destroyed 26 | MyClass* ptr = new MyClass(); // Yes can be created like this 27 | delete ptr; // Error: Destructor is private, can't delete heap object 28 | } 29 | ``` 30 | 31 | **How to destroy it:** 32 | 33 | 1. Private Constuctor 34 | ```cpp 35 | class HeapOnly { 36 | public: 37 | static HeapOnly* createInstance() { 38 | return new HeapOnly(); 39 | } 40 | 41 | void destroyInstance() { 42 | delete this; // Allows deletion, but only through this function 43 | } 44 | 45 | private: 46 | HeapOnly() { std::cout << "HeapOnly Constructor" << std::endl; } 47 | ~HeapOnly() { std::cout << "HeapOnly Destructor" << std::endl; } 48 | }; 49 | 50 | int main() { 51 | // HeapOnly obj; // Error: Constructor is private (can't allocate on stack) 52 | HeapOnly* obj = HeapOnly::createInstance(); 53 | obj->destroyInstance(); // Properly deletes the object 54 | } 55 | ``` 56 | 57 | 2. Public Constructor: 58 | 59 | ```cpp 60 | #include 61 | 62 | class MyClass { 63 | public: 64 | // Public constructor 65 | MyClass() { 66 | std::cout << "Constructor: MyClass object created!" << std::endl; 67 | } 68 | 69 | // Method to safely delete the object 70 | void destroyInstance() { 71 | delete this; // Allows controlled deletion of the object 72 | } 73 | 74 | private: 75 | // Private destructor 76 | ~MyClass() { 77 | std::cout << "Destructor: MyClass object destroyed!" << std::endl; 78 | } 79 | }; 80 | 81 | int main() { 82 | // MyClass obj; // ERROR: destructor is private 83 | // Creating the object dynamically on the heap 84 | MyClass* obj = new MyClass(); 85 | 86 | // Deleting the object through the controlled method 87 | obj->destroyInstance(); 88 | 89 | // obj->~MyClass(); // ERROR: Destructor is private and cannot be called directly 90 | // delete obj; // ERROR: Cannot delete directly, destructor is private 91 | 92 | return 0; 93 | } 94 | ``` 95 | 96 | 97 | ## RAII: Resource Acquisition is Initialisation 98 | 99 | C++ program can have different type of resources: 100 | - Allocated memory on heap 101 | - FILE handles (fopen, fclose) 102 | - Mutex Locks 103 | - C++ threads 104 | 105 | Some of these resources are `unique` like mutex lock and some can be duplicated 106 | like heap allocations and file handlers (they can be `duped`). 107 | > Some actions needs to be taken by the program in order to free these resources. 108 | 109 | Try to do cleanups in the destructor of the object. Since destructor is always 110 | called whenever the object goes out of scope: we don't need to release resources 111 | explicitly. 112 | 113 | ```cpp 114 | class NaiveVector { 115 | int* arr; 116 | size_t size; 117 | 118 | // assume we have released resource in destructor 119 | } 120 | 121 | { 122 | NaiveVector v; 123 | v.push_back(1); 124 | { 125 | NaiveVector w = v; // this would also copy the pointer int* arr 126 | } // here int * arr would be released since w is now out of scope 127 | 128 | std::cout << v[0] << '\n'; // this is invalid now. since arr is deleted 129 | 130 | } // double delete here. arr is already freed, we will free it again. 131 | 132 | // the problem above was, NaiveVector w = v, will copy all the member variables 133 | // as it is, if we don't define our custom copy constructor. 134 | ``` 135 | #### Adding copy constructor 136 | The destructor was responsible for freeing resources to avoid any leaks. The 137 | copy constructor is responsible for duplicating resources to avoid double frees. 138 | 139 | ![](../assets/CC.png) 140 | 141 | **Initialisation vs Assignment** 142 | ```cpp 143 | // 1. This is initialisation (construction). Calls copy constructor 144 | NaiveVector w = v; 145 | 146 | // 2. This is assignment to existing object w. Calls assignment operator 147 | NaiveVector w; 148 | w = v; 149 | ``` 150 | 151 | ![](../assets/RAII.png) 152 | 153 | In C++, the handling of `try-catch` blocks during an exception involves manipulating the **call stack**. Here’s how it works step by step: 154 | 155 | ### 1. **Normal Execution and Call Stack Behavior** 156 | - Under normal execution, each function call pushes a new stack frame onto the call stack. 157 | - This stack frame holds local variables, return addresses, and other function context. 158 | - When a function completes, its stack frame is popped off, and control returns to the calling function. 159 | 160 | ### 2. **When an Exception is Thrown** 161 | When an exception is thrown inside a `try` block: 162 | - The program immediately **stops executing** the normal flow of code and begins **unwinding the call stack**. 163 | - This process is known as **stack unwinding**. 164 | 165 | ### 3. **Stack Unwinding** 166 | During stack unwinding: 167 | - The function that threw the exception doesn’t return normally. Instead, the runtime looks for a `catch` block that can handle the exception. 168 | - As the runtime searches for the appropriate `catch`, it starts **popping stack frames** off the call stack, effectively **exiting functions** in reverse order until a suitable handler is found. 169 | - If any objects are going out of scope as part of this unwinding (i.e., objects with automatic storage duration in the stack frames), their destructors are called to properly clean up resources. This ensures that **RAII** (Resource Acquisition Is Initialization) is respected, and resources such as memory or file handles are properly released. 170 | 171 | ### 4. **Finding the Appropriate `catch` Block** 172 | - The runtime checks each function in the call stack, starting with the function where the exception was thrown, to see if there is a `catch` block that matches the exception type. 173 | - If a matching `catch` block is found, control is transferred to it, and the stack unwinding stops. 174 | - If no matching `catch` block is found in the current function, the stack unwinding continues to the next function in the call stack. 175 | 176 | ### 5. **Uncaught Exceptions** 177 | - If the runtime unwinds all the way through the call stack without finding a matching `catch` block, the program terminates. 178 | - In this case, the runtime will call `std::terminate`, which by default ends the program, often producing an error message like "terminate called after throwing an instance of...". 179 | 180 | ### Example: 181 | 182 | ```cpp 183 | #include 184 | #include 185 | 186 | void funcC() { 187 | std::cout << "In funcC\n"; 188 | throw std::runtime_error("Exception in funcC"); 189 | } 190 | 191 | void funcB() { 192 | std::cout << "In funcB\n"; 193 | funcC(); // Call to funcC, which will throw an exception 194 | std::cout << "In funcB after exception\n"; // won't be printed 195 | } 196 | 197 | void funcA() { 198 | std::cout << "In funcA\n"; 199 | try { 200 | funcB(); // Call to funcB, which will call funcC and eventually throw an exception 201 | std::cout << "In func A return\n"; // won't be printed 202 | } catch (const std::exception& e) { 203 | std::cout << "Caught exception: " << e.what() << '\n'; 204 | } 205 | std::cout << "Handling Done\n"; 206 | } 207 | 208 | int main() { 209 | funcA(); // Start the chain of function calls 210 | return 0; 211 | } 212 | ``` 213 | 214 | ### Output: 215 | ```plaintext 216 | In funcA 217 | In funcB 218 | In funcC 219 | Caught exception: Exception in funcC 220 | Handling Done 221 | ``` 222 | 223 | ### What Happens in the Call Stack: 224 | 1. **`main`** calls **`funcA`**, which adds a stack frame for `funcA` to the call stack. 225 | 2. **`funcA`** calls **`funcB`**, which adds another stack frame for `funcB` to the call stack. 226 | 3. **`funcB`** calls **`funcC`**, which adds yet another stack frame for `funcC` to the call stack. 227 | 4. **`funcC`** throws a `std::runtime_error`. The runtime starts stack unwinding. 228 | - The stack frame for `funcC` is popped off the stack, and the destructor of any local variables in `funcC` (if any) are called. 229 | 5. **`funcB`** doesn’t have a `catch` block, so its stack frame is also popped off the stack, and local objects (if any) are destroyed. 230 | 6. Control reaches **`funcA`**, which has a matching `catch` block for `std::exception`. The exception is caught, and stack unwinding stops. 231 | 7. The program continues execution in the `catch` block of `funcA`. 232 | 233 | 234 | - **RAII**: Objects are properly destroyed even during stack unwinding, as destructors are automatically invoked. 235 | 236 | - If a matching `catch` block is found, the exception is handled; otherwise, the program terminates. 237 | 238 | 239 | ### The Rule of Zero 240 | If your class does not directly manage any resource, but merely use library 241 | components such as vector and string, then write NO special member function. 242 | 243 | Let the compiler generate all of them default: 244 | - Default destructor 245 | - Default copy constructor 246 | - Default copy assignment operator 247 | 248 | ```cpp 249 | #include 250 | #include 251 | 252 | class MyString { 253 | private: 254 | char* data; // Dynamically allocated memory to hold a string 255 | public: 256 | // 1. Default Constructor 257 | MyString(const char* str = "") { 258 | data = new char[std::strlen(str) + 1]; 259 | std::strcpy(data, str); 260 | std::cout << "Constructor called\n"; 261 | } 262 | 263 | // 2. Destructor 264 | ~MyString() { 265 | delete[] data; 266 | std::cout << "Destructor called\n"; 267 | } 268 | 269 | // 3. Copy Constructor 270 | MyString(const MyString& other) { 271 | data = new char[std::strlen(other.data) + 1]; 272 | std::strcpy(data, other.data); 273 | std::cout << "Copy Constructor called\n"; 274 | } 275 | 276 | // 4. Copy Assignment Operator 277 | MyString& operator=(const MyString& other) { 278 | if (this == &other) return *this; // Self-assignment check 279 | 280 | delete[] data; // Release old memory 281 | data = new char[std::strlen(other.data) + 1]; // Allocate new memory 282 | std::strcpy(data, other.data); // Copy the data 283 | std::cout << "Copy Assignment Operator called\n"; 284 | return *this; 285 | } 286 | 287 | // 5. Move Constructor 288 | MyString(MyString&& other) noexcept : data(other.data) { 289 | other.data = nullptr; // Release ownership of the moved-from object 290 | std::cout << "Move Constructor called\n"; 291 | } 292 | 293 | // 6. Move Assignment Operator 294 | MyString& operator=(MyString&& other) noexcept { 295 | if (this == &other) return *this; // Self-assignment check 296 | 297 | delete[] data; // Release old memory 298 | data = other.data; // Steal the data pointer 299 | other.data = nullptr; // Release ownership of the moved-from object 300 | std::cout << "Move Assignment Operator called\n"; 301 | return *this; 302 | } 303 | 304 | // Helper method to print the string 305 | void print() const { 306 | std::cout << "String: " << (data ? data : "null") << '\n'; 307 | } 308 | }; 309 | 310 | int main() { 311 | MyString s1("Hello"); 312 | MyString s2 = s1; // Invokes Copy Constructor 313 | MyString s3; 314 | s3 = s1; // Invokes Copy Assignment Operator 315 | 316 | MyString s4 = std::move(s1); // Invokes Move Constructor 317 | MyString s5; 318 | s5 = std::move(s2); // Invokes Move Assignment Operator 319 | 320 | s4.print(); 321 | s5.print(); 322 | 323 | return 0; 324 | } 325 | ``` -------------------------------------------------------------------------------- /notes/allocators.md: -------------------------------------------------------------------------------- 1 | ## An Allocator is a Handle to a Heap 2 | 3 | CppNow Link: [Here](https://www.youtube.com/watch?v=0MdSJsCTRkY) 4 | 5 | (Wrong) An allocator object represents a source of memory. 6 | 7 | (Correct) An allocator represents a handle to the source of memory. 8 | 9 | *Incomplete, haven't covered fully* -------------------------------------------------------------------------------- /notes/atomic_instructions.md: -------------------------------------------------------------------------------- 1 | ## Atomics 2 | 3 | To implement locks, we need hardware support for atomic instructions. This can't 4 | alone be done by software. 5 | 6 | ### CMPXCHG (Compare-And-Exchange/Swap) 7 | Atomically compares the value in memory to a register. If the values are equal, 8 | it writes a new value; otherwise, it leaves the memory unchanged and sets flags 9 | indicating failure. 10 | 11 | `CMPXCHG [mem], reg` 12 | 13 | ```cpp 14 | bool CAS(int* addr, int expected, int new_val) { 15 | if (*addr == expected) { 16 | *addr = new_val; // update the memory with new_val 17 | return true; // return success 18 | } else { 19 | return false; // return failure 20 | } 21 | } 22 | ``` 23 | 24 | ### XCHG (Exchange) 25 | Atomically swaps the contents of a register and a memory location. 26 | 27 | `XCHG reg, [mem]` 28 | 29 | ### LOCK Prefix 30 | In x86, certain instructions can be prefixed with the LOCK instruction to make 31 | them atomic, meaning the instruction will operate atomically on the memory location. 32 | 33 | Applies to instructions like ADD, SUB, INC, DEC, XOR, OR, AND, etc. 34 | These operations can then modify memory atomically. 35 | 36 | `LOCK ADD [mem], reg` 37 | 38 | ### Simple spinlock using CAS 39 | ```cpp 40 | typedef int spinlock_t; // 0 = unlocked, 1 = locked 41 | 42 | void spinlock_init(spinlock_t* lock) { 43 | *lock = 0; // Initialize the lock as unlocked 44 | } 45 | 46 | void spinlock_acquire(spinlock_t* lock) { 47 | while (!CAS(lock, 0, 1)) { 48 | // Spin until we successfully acquire the lock 49 | } 50 | } 51 | 52 | void spinlock_release(spinlock_t* lock) { 53 | *lock = 0; // Release the lock by setting it to unlocked 54 | } 55 | ``` 56 | ```assembly 57 | spinlock_acquire: 58 | mov eax, 0 ; expected value (unlocked) 59 | acquire_retry: 60 | mov ebx, 1 ; new value (locked) 61 | lock cmpxchg [lock], ebx ; compare and swap 62 | jne acquire_retry ; if lock was not acquired, retry 63 | ret 64 | 65 | spinlock_release: 66 | mov [lock], 0 ; set lock to unlocked 67 | ret 68 | ``` 69 | 70 | More on different types of spinlocks can be found 71 | [here](https://github.com/Shivam5022/Spin-Locks-and-Contention) in my second 72 | assignment of COL818. 73 | 74 | ### How hardware supports atomic instructions: 75 | 76 | The simplest CAS implementations (and the easiest mental model) will simply freeze the local cache coherence protocol state machine after the load part of the CAS brings the relevant cache line into the nearest (e.g. L1) cache in exclusive mode, and will unfreeze it after the (optional) store completes. This, by definition, makes the CAS operation as a whole atomic with relation to any other participant in the cache coherence protocol. 77 | 78 | -------------------------------------------------------------------------------- /notes/buffer_overflow.md: -------------------------------------------------------------------------------- 1 | ## Buffer Overflow 2 | 3 | [Video 1](https://www.youtube.com/watch?v=scaz_pofc7A&list=PLEJxKK7AcSEGPOCFtQTJhOElU44J_JAun&index=33) 4 | 5 | [Video 2](https://www.youtube.com/watch?v=o3pcY-bRRgs&list=PLEJxKK7AcSEGPOCFtQTJhOElU44J_JAun&index=34&pp=iAQB) 6 | 7 | 8 | ![](../assets/stack.svg) 9 | 10 | [Pdf Notes Here](../assets/buffer_overflow.pdf) 11 | -------------------------------------------------------------------------------- /notes/casting.md: -------------------------------------------------------------------------------- 1 | 2 | ### Explicit Keyword 3 | 4 | By default, C++ allows implicit conversions for single-argument constructors. 5 | This means that if you have a constructor with one parameter, the compiler 6 | will automatically convert objects of that parameter’s type into objects of 7 | your class if needed. 8 | 9 | - The explicit keyword prevents these implicit conversions. 10 | 11 | ```cpp 12 | #include 13 | 14 | class MyClass { 15 | public: 16 | // Constructor with 'explicit' 17 | explicit MyClass(int x) { 18 | std::cout << "MyClass constructor called with value: " << x << std::endl; 19 | } 20 | }; 21 | 22 | void func(MyClass obj) { 23 | std::cout << "In func()" << std::endl; 24 | } 25 | 26 | int main() { 27 | // func(5); // Error: implicit conversion from int to MyClass is not allowed! 28 | func(MyClass(5)); // This works because we explicitly create a MyClass object 29 | return 0; 30 | } 31 | ``` 32 | 33 | ### Casting 34 | 35 | #### Static Casting 36 | 37 | `static_cast` is used for compile-time type conversions between compatible 38 | types. It performs the conversion at compile time, ensuring type safety in 39 | most cases, *but it does not perform runtime checks (unlike dynamic_cast).* 40 | 41 | ```cpp 42 | #include 43 | 44 | int main() { 45 | // Basic type conversion 46 | float f = 9.5; 47 | int i = static_cast(f); // Converts float to int 48 | std::cout << "int i: " << i << std::endl; 49 | 50 | // Upcasting (Derived to Base) 51 | class Base {}; 52 | class Derived : public Base {}; 53 | Derived d; 54 | Base* basePtr = static_cast(&d); // Safe upcast (Derived* -> Base*) 55 | 56 | return 0; 57 | } 58 | ``` 59 | *static_cast performs no runtime checks, so downcasting (casting from base to 60 | derived) is unsafe unless you’re sure of the object type.* 61 | 62 | #### Dynamic Casting 63 | 64 | `dynamic_cast` is used for runtime type checking and safe downcasting in 65 | inheritance hierarchies. It is primarily used for casting between base and 66 | derived class pointers or references when polymorphism is involved (i.e., 67 | when you have a virtual function in the base class). 68 | 69 | ```cpp 70 | #include 71 | 72 | class Base { 73 | public: 74 | virtual ~Base() = default; // Must have at least one virtual function 75 | }; 76 | 77 | class Derived : public Base {}; 78 | 79 | int main() { 80 | Base* basePtr = new Derived(); // Pointer to Base, but actually a Derived object 81 | 82 | // Safe downcast: checks at runtime if basePtr actually points to a Derived object 83 | Derived* derivedPtr = dynamic_cast(basePtr); 84 | if (derivedPtr) { 85 | std::cout << "Successfully casted to Derived" << std::endl; 86 | } else { 87 | std::cout << "Failed to cast to Derived" << std::endl; 88 | } 89 | 90 | delete basePtr; 91 | return 0; 92 | } 93 | ``` 94 | 95 | *dynamic_cast only works with pointers or references to polymorphic types (classes with at least one virtual function).* 96 | 97 | #### Re-interpret Casting 98 | 99 | `reinterpret_cast` is the most dangerous cast, used for low-level type 100 | reinterpretation. It allows you to treat a block of memory as if it were a 101 | different type entirely. This is often used for pointer conversions or 102 | type-punning. 103 | 104 | This example shows how to use reinterpret_cast to interpret a 32-bit integer 105 | (std::uint32_t) as an array of bytes. This kind of operation can be useful in 106 | networking or binary file I/O, where you need to break a larger value into 107 | its individual bytes (little-endian or big-endian conversion). 108 | 109 | ```cpp 110 | #include 111 | #include // For uint32_t 112 | 113 | void printBytes(const std::uint8_t* byteArray, std::size_t size) { 114 | for (std::size_t i = 0; i < size; ++i) { 115 | std::cout << "Byte " << i << ": 0x" << std::hex << static_cast(byteArray[i]) << std::endl; 116 | } 117 | } 118 | 119 | int main() { 120 | std::uint32_t value = 0x12345678; // A 32-bit integer (hexadecimal representation) 121 | 122 | // Reinterpret the 32-bit integer as a byte array 123 | const std::uint8_t* byteArray = reinterpret_cast(&value); 124 | 125 | // Print the individual bytes 126 | std::cout << "Value as bytes:" << std::endl; 127 | printBytes(byteArray, sizeof(value)); // Should print the 4 bytes of the integer 128 | 129 | return 0; 130 | } 131 | ``` 132 | **Output in Little Endian Machine:** 133 | ``` 134 | Value as bytes: 135 | Byte 0: 0x78 136 | Byte 1: 0x56 137 | Byte 2: 0x34 138 | Byte 3: 0x12 139 | ``` 140 | 141 | #### Const Casting 142 | Refer [these](./const_constexpr.md) notes. -------------------------------------------------------------------------------- /notes/cheat.md: -------------------------------------------------------------------------------- 1 | ## Cheat.sh 2 | 3 | You can find important usage info about command line tools like grep, find, 4 | curl etc at [cheat.sh](https://cheat.sh). 5 | 6 | Here is a very minimal script for getting documentation about tools from 7 | command line: 8 | 9 | ```sh 10 | # Function to query cheat.sh and display results with less 11 | cheat() { 12 | if [ -z "$1" ]; then 13 | echo "Usage: cheat " 14 | echo "Example: cheat grep" 15 | return 1 16 | fi 17 | 18 | local topic="$1" 19 | 20 | # Fetch the cheat sheet from cheat.sh 21 | echo "https://cheat.sh/${topic}" 22 | curl -s "https://cheat.sh/${topic}" | less -R 23 | } 24 | ``` 25 | 26 | Add it in `.zshrc` and use like: `cheat grep` 27 | -------------------------------------------------------------------------------- /notes/const_constexpr.md: -------------------------------------------------------------------------------- 1 | ## Resources: 2 | 1. The below notes are from the CppCon 2021 talk by Rainer Grimm: [Link](https://www.youtube.com/watch?v=tA6LbPyYdco) 3 | 4 | 5 | ### const 6 | - Declare a variable const: means you cannot modify it afterwards. 7 | 8 | - const objects: 9 | 10 | - must be initialised. 11 | - cannot be modified. 12 | - they cant be victim of data races. since they are read only. 13 | - can only invoke const member functions. 14 | 15 | - `const member functions` of a class cannot change the state of the object. 16 | That is, they can't change the value of member variables. 17 | Although they can change the value of objects which dont belong to this class. 18 | 19 | - use `mutable` keyword, in case you want a member variable to get modified inside a `const` member function. 20 | 21 | ```cpp 22 | int f = 100; 23 | struct Widget { 24 | int a; 25 | mutable int c = 0; 26 | Widget(int init) : a(init) {} 27 | void test(int& p) const { 28 | p++; // valid since `p` doesnt belong to this class 29 | f++; // valid since `f` doesnt belong to this class 30 | c++; // valid since `c` is mutable 31 | const_cast(this)->c++; // another way to increment c, without mutable. 32 | // a++; // not valid 33 | std::cout << a << '\n'; 34 | } 35 | }; 36 | ``` 37 | 38 | - `const char* const a`: means a is a const pointer to a const char. which means the value of neither the pointer nor the pointee can be altered. 39 | 40 | 41 | ### const_cast 42 | - used to remove `const` or `volatile` from a variable. 43 | 44 | - modifying the value of a `const` object by removing its constness is undefined behaviour. 45 | 46 | ```cpp 47 | #include 48 | 49 | 50 | int main() { 51 | const int a = 10; 52 | int* b = const_cast (&a); 53 | *b = 11; 54 | std::cout << *b << '\n'; // prints 11 55 | std::cout << a << '\n'; // prints 10 56 | 57 | } 58 | ``` 59 | 60 | Modifying an object declared as const after casting away its 61 | constness using const_cast results in undefined behavior if the 62 | object was truly defined as const in its original context. However, 63 | it’s safe if the object was not originally constant but passed 64 | around as a const reference or pointer. 65 | 66 | 67 | ```cpp 68 | #include 69 | #include 70 | #include 71 | #include 72 | 73 | int main () { 74 | // here i is implicitly constant 75 | // hence, using const_cast is undefined 76 | const int i = 5; 77 | // int& j = i; // error 78 | int& j = const_cast(i); 79 | j = 7; 80 | std::cout << i << '\n'; // prints 5 81 | std::cout << j << '\n'; // prints 7 82 | 83 | // int* ptr = &i; // error 84 | int* ptr = const_cast (&i); 85 | *ptr = 10; 86 | std::cout << i << '\n'; // still prints 5 87 | std::cout << *ptr << '\n'; // prints 10 88 | 89 | // although same address :) 90 | std::cout << ptr << ' ' << &i << '\n'; 91 | 92 | 93 | // safe usage 94 | 95 | int original = 11; // not inherently constant 96 | 97 | auto change = [](const int& ref) { 98 | // ref = 15; // error 99 | int& nonconst = const_cast (ref); 100 | nonconst = 15; // this is applicable 101 | }; 102 | 103 | change(original); 104 | 105 | std::cout << original << '\n'; // prints 15 now :) 106 | 107 | // Modifying an object declared as const after casting 108 | // away its const-ness using const_cast results in 109 | // undefined behavior if the object was truly defined 110 | // as const in its original context. 111 | // However, it’s safe if the object was not originally 112 | // constant but passed around as a const reference 113 | // or pointer. 114 | 115 | } 116 | ``` 117 | 118 | ### constexpr 119 | - These expressions can be: 120 | - evaluated at the compile time (good optimisation). 121 | - they are thread safe. 122 | - `const` variables are implicitly `constexpr` when initialised with some constant expression. 123 | - they have potential to run at compile time (*not the guarantee*). 124 | 125 | ```cpp 126 | #include 127 | 128 | // here this gcd function is evaluated at compile time only 129 | constexpr int gcd(int a, int b) { 130 | return (b == 0) ? a : gcd(b, a % b); 131 | } 132 | 133 | int main() { 134 | // all the arguments must be known at compile time 135 | constexpr int result = gcd(48, 18); // Compile-time GCD calculation 136 | std::cout << "GCD of 48 and 18 is: " << result << std::endl; 137 | return 0; 138 | } 139 | ``` 140 | - C++20 supports the `constexpr` containers: std::vectors and std::string. 141 | - Meaning, the memory is allocated and released at compile time (Transient Allocation). 142 | - ```cpp 143 | #include 144 | #include 145 | #include 146 | 147 | constexpr int maxElement() { 148 | std::vector a {1, 22, 333, 44, 55}; 149 | a.push_back(412); 150 | std::sort(a.begin(), a.end()); 151 | return a.back(); 152 | } 153 | 154 | int main() { 155 | // compile time (check godbolt assembly with --std=c++20) 156 | constexpr int m = maxElement(); 157 | std::cout << m << '\n'; 158 | } 159 | ``` 160 | 161 | ### consteval 162 | - must run at compile time (*strong guarantee*) 163 | 164 | ```cpp 165 | #include 166 | #include 167 | #include 168 | 169 | int runTime(int a) { 170 | return a + 1; 171 | } 172 | 173 | constexpr int runOrCompileTime(int a) { 174 | return a + 1; 175 | } 176 | 177 | consteval int compileTime(int a) { 178 | return a + 1; 179 | } 180 | 181 | int main() { 182 | // constexpr int ans1 = runTime(100); // ERROR 183 | constexpr int ans2 = runOrCompileTime(100); 184 | constexpr int ans3 = compileTime(100); 185 | 186 | int f = 100; 187 | int ans4 = runOrCompileTime(f); // Fine: Because it can be evaluated at runtime too! 188 | // int ans5 = compileTime(f); // ERROR: consteval must be evaluated at compile time, but `f' is not const 189 | // making `f' const solves the above problem 190 | } 191 | ``` 192 | 193 | -------------------------------------------------------------------------------- /notes/cpp_question_bank.md: -------------------------------------------------------------------------------- 1 | ## C++ Questions for HFT SWE 2 | -------------------------------------------------------------------------------- /notes/exceptions.md: -------------------------------------------------------------------------------- 1 | ## Exceptions in C++ 2 | 3 | - Ignoring exception: Leads to core dump 4 | - What is exception: 5 | - Something that gets thrown 6 | - Something that gets caught (hopefully) 7 | - Throwing and catching exceptions is `expensive`. 8 | 9 | - If an exception is thrown: 10 | - During stack unwinding the program is terminated 11 | - Caught by a matching handler 12 | - All the intermediate objects in stack are destructed 13 | - If no matching handler is found, the functions `std::terminate` is called 14 | 15 | - Desctructor throwing exception: 16 | - Bad situation 17 | - If an exception is thrown while another exception is already being 18 | handled, this causes terminate to be called, which ends the program 19 | abruptly. This occurs because C++ cannot handle two simultaneous exceptions. 20 | - For example, if an exception is thrown and the stack unwinds to destroy 21 | objects in scope, any destructor that throws an exception will clash with 22 | the already active exception. 23 | 24 | - Handle the exception there itself (for Destructors) 25 | ```cpp 26 | struct ResourceHandler { 27 | ~ResourceHandler() { 28 | try { 29 | // Code that may throw an exception 30 | cleanup(); 31 | } catch (const std::exception& e) { 32 | // Handle or log the exception, but do not rethrow 33 | std::cerr << "Exception in destructor: " << e.what() << std::endl; 34 | } catch (...) { 35 | std::cerr << "Unknown exception in destructor" << std::endl; 36 | } 37 | } 38 | 39 | void cleanup() { 40 | // Code that might throw an exception 41 | } 42 | }; 43 | ``` 44 | 45 | - Exception Hygiene 46 | - Throw by value [*This memory is allocated on heap 47 | actually, therefore expensive*] 48 | - Catch by (const) reference 49 | 50 | 51 | **Custom Exception in C++:** 52 | 53 | ```cpp 54 | #include 55 | #include 56 | #include 57 | 58 | // Step 1: Define the custom exception class 59 | class MyCustomException : public std::exception { 60 | private: 61 | std::string message; // Custom error message 62 | 63 | public: 64 | // Constructor to initialize the error message 65 | MyCustomException(const std::string& msg) : message(msg) {} 66 | 67 | // Override the what() function 68 | virtual const char* what() const noexcept override { 69 | return message.c_str(); 70 | } 71 | }; 72 | 73 | // Function that may throw MyCustomException 74 | void riskyFunction(bool triggerError) { 75 | if (triggerError) { 76 | throw MyCustomException("Something went wrong in riskyFunction!"); 77 | } 78 | std::cout << "Function executed successfully." << std::endl; 79 | } 80 | 81 | int main() { 82 | try { 83 | // Step 2: Call the function and trigger the custom exception 84 | riskyFunction(true); // Pass true to trigger the exception 85 | } 86 | catch (const MyCustomException& e) { // Step 3: Catch the custom exception 87 | std::cerr << "Caught custom exception: " << e.what() << std::endl; 88 | } 89 | return 0; 90 | } 91 | ``` 92 | **Re-throwing:** 93 | 94 | Inside a catch block, using throw; without any argument will 95 | rethrow the currently caught exception. This is often done to 96 | perform some actions (like logging) and then pass the 97 | exception up the stack without changing it. 98 | 99 | ```cpp 100 | #include 101 | #include 102 | 103 | void innerFunction() { 104 | throw std::runtime_error("Error in innerFunction"); // Throwing an exception 105 | } 106 | 107 | void outerFunction() { 108 | try { 109 | innerFunction(); 110 | } 111 | catch (const std::exception& e) { 112 | std::cerr << "Caught in outerFunction: " << e.what() << std::endl; 113 | throw; // Rethrow the same exception to propagate it further 114 | } 115 | } 116 | 117 | int main() { 118 | try { 119 | outerFunction(); 120 | } 121 | catch (const std::exception& e) { 122 | std::cerr << "Caught in main: " << e.what() << std::endl; 123 | } 124 | return 0; 125 | } 126 | ``` 127 | 128 | > Prefer to keep exceptions as "Rare" as you can, meaning for 129 | serious, uncommon errors 130 | 131 | > Resource management should always use RAII 132 | -------------------------------------------------------------------------------- /notes/find.md: -------------------------------------------------------------------------------- 1 | ## Find command in linux 2 | 3 | - search for files and directories (recursively) based on various criterias 4 | 5 | - `find [path] [expression]` 6 | 7 | - [path]: The directory where the search will be started 8 | 9 | - [expression]: Filter criteria 10 | 11 | - when using `-name -iname` as criteria * is supported for wildcard 12 | 13 | - `find /home/user -name "example.txt"` find a file with following name in /home/user directory 14 | 15 | - `find /home/user -iname "example.txt"`: case insensitive search `iname` 16 | 17 | - `find /path -type f`: find only files not directories 18 | 19 | - `find /path -type d`: find only directories 20 | 21 | - `find /path -type l`: find symbolic links 22 | 23 | - `find / -size +100M`: find files greater than 100MB 24 | 25 | - `find / -size -5k`: find files smaller than 5KB 26 | 27 | - `find /home -type f -perm 644`: find files with permission mode 28 | 29 | - `find /home -type f -mtime -7`: find files modified in last 7 days 30 | 31 | - `find /home -type f -atime -2`: find files accessed in last 2 days 32 | 33 | - `find /path -type f -name "*.log" -exec rm {} +` 34 | 35 | - `-exec`: executes the given command 36 | 37 | - `{}`: placeholder for the attributes 38 | 39 | - `+`: end of the command 40 | 41 | - `find /path -type f -empty`: find empty files (use d for directories) 42 | 43 | - `find /home -type f -name "*.log" -exec grep -i "error" {} +`: searching inside files (find all the log files which contain "error") 44 | 45 | - `find /var/log -type f -name "*.log" | xargs rm`: xargs takes a list and passes each element as arguments to another command 46 | -------------------------------------------------------------------------------- /notes/function_inlining.md: -------------------------------------------------------------------------------- 1 | ## Function Inlining 2 | 3 | C++ FAQs: [Link](https://isocpp.org/wiki/faq/inline-functions) 4 | 5 | Assuming that we already know about the ODR (One Definition Rule) and how 6 | marking a function inline helps that, lets discuss function inling from 7 | performance POV. 8 | 9 | ### Why Inlining 10 | 11 | When the compiler inline-expands a function call, the function’s code gets 12 | inserted into the caller’s code stream. 13 | 14 | When a program makes a function call, the instruction pointer (IP) jumps to a 15 | different memory address, executes the instructions at that location, and then 16 | jumps back to the original location. 17 | 18 | This jumping to a new address can be inefficient because the next instruction 19 | to be executed may not be cached in the L1-I cache. 20 | 21 | If the function is small, it often makes more sense for it to be inlined in the 22 | caller’s code stream. In such cases, there is no jump to an arbitrary location, 23 | and the L1-I cache remains warm. 24 | 25 | Additionally, compilers are generally better suited to apply optimizations when 26 | the code is inlined, compared to optimizing across multiple distinct functions. 27 | 28 | 29 | ### Why not always inline 30 | Inlining all function calls can lead to code bloat, increasing the size of the 31 | executable and potentially causing cache thrashing. 32 | 33 | Consider a scenario in the hot path: before sending an order to the exchange, we 34 | perform a sanity check. If there is an error, we call the function logAndDebug, 35 | which handles some bookkeeping internally. In the typical case (the happy path), 36 | the order is sent to the exchange. 37 | 38 | ```cpp 39 | bool isError = checkOrder(order); 40 | 41 | if (isError) { 42 | logAndDebug(order); 43 | } else { 44 | sendOrderToExchange(order); 45 | } 46 | 47 | ``` 48 | 49 | Here, `isError` is rarely true, and the happy path is executed most of the time. 50 | 51 | If the function `logAndDebug` were inlined, unnecessary instructions—executed only 52 | in rare cases—would occupy space in the instruction cache, potentially polluting 53 | it. This could slow down the program instead of improving performance. -------------------------------------------------------------------------------- /notes/git-sheet.md: -------------------------------------------------------------------------------- 1 | ## Git Command Sheet 2 | 3 | - Complete documentation can be found here: [Pro Git Book](https://git-scm.com/book/en/v2) 4 | - Install `lazygit` for nice TUI experience. 5 | 6 | ## Basics 7 | 8 | 1. `git clone url [new repo name]` 9 | 2. Created a alias `git config --global alias.glog "log --graph --pretty=format:'%C(yellow)%h%C(reset) - %C(cyan)%an%C(reset) - %C(blue)%ad%C(reset) - %s' --date=short"`. 10 | 11 | Use `git glog` now 12 | 13 | 3. To check the remote servers (eg GitHub): `git remote -v`. 14 | 15 | If you clone a repository, the command automatically adds that remote repository under the name “origin”. If your current branch is set up to track a remote branch, you can use the `git pull` command to automatically fetch and then merge that remote branch into your current branch. By default, the `git clone` command automatically sets up your local master branch to track the remote master branch (or whatever the default branch is called) on the server you cloned from. Running `git pull` generally fetches data from the server you originally cloned from and automatically tries to merge it into the code you’re currently working on. 16 | 17 | 4. Pushing branch to remote: `git push ` 18 | 19 | 5. Inspecting a remote in detail: `git remote show ` 20 | 21 | ## Branches 22 | 23 | 1. Create a new branch and checkout to it: `git checkout -b ` 24 | 25 | 2. Merge a branch to master: `git checkout master && git merge ` 26 | 27 | 3. See all branches: `git branch --all -vv` 28 | 29 | 4. Pushing to new remote branch name: you could run `git push origin serverfix:awesomebranch` to push your local serverfix branch to the awesomebranch branch on the remote project. 30 | 31 | 5. `git checkout -b /` : Create a local tracking branch for a remote branch 32 | 33 | 6. For pt 5, shortcut is `git checkout ` (If the branch name you’re trying to checkout (a) doesn’t exist and (b) exactly matches a name on only one remote, Git will create a tracking branch for you) 34 | 35 | ## Stashing 36 | 37 | 1. `git stash` 38 | 39 | 2. `git stash list` 40 | 41 | 3. `git stash apply / pop` 42 | -------------------------------------------------------------------------------- /notes/http.md: -------------------------------------------------------------------------------- 1 | ## HTTP 2 | - Hyper Text Transfer Protocol 3 | - Communication between web servers and clients 4 | - HTTP Requests / Response 5 | - Its stateless (each request is independent) 6 | 7 | `GET` 8 | Retrieves data from the server 9 | 10 | `POST` 11 | Submit data to the server 12 | 13 | `PUT` 14 | Update data already on the server 15 | 16 | `DELETE` 17 | Deletes data from the server 18 | 19 | ``` 20 | 200 - OK 21 | 201 - OK created 22 | 301 - Moved to new URL 23 | 304 - Not modified (Cached version) 24 | 400 - Bad request 25 | 401 - Unauthorized 26 | 404 - Not found 27 | 500 - Internal server error 28 | ``` 29 | 30 | ### HTTP in C++ using libcurl 31 | 32 | For `json` parsing, I have used this amazing library [nlohmann](https://github.com/nlohmann/json). 33 | 34 | ```cpp 35 | #include 36 | #include 37 | #include "json.hpp" 38 | #include 39 | 40 | using json = nlohmann::json; 41 | ``` 42 | This callback is called everytime we receive a response from the server. We pass 43 | into it, a pointer to user string and append the response in this string. 44 | ```cpp 45 | // Helper function to capture server responses into a string 46 | static size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* out) { 47 | size_t totalSize = size * nmemb; 48 | out->append((char*)contents, totalSize); 49 | return totalSize; 50 | } 51 | ``` 52 | Sending a `GET` request (it takes no argument). 53 | We have stored the response in a JSON object: 54 | ```cpp 55 | json httpGet(const std::string& url) { 56 | CURL* curl; 57 | CURLcode res; 58 | std::string readBuffer; 59 | 60 | curl = curl_easy_init(); 61 | if(curl) { 62 | curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); 63 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); 64 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); 65 | 66 | res = curl_easy_perform(curl); 67 | curl_easy_cleanup(curl); 68 | 69 | if(res != CURLE_OK) { 70 | std::cerr << "GET request failed: " << curl_easy_strerror(res) << std::endl; 71 | } 72 | } 73 | // Do this only when the response type is JSON 74 | return json::parse(readBuffer); 75 | } 76 | ``` 77 | 78 | Headers are appended like this: 79 | ```cpp 80 | struct curl_slist* headers = NULL; 81 | headers = curl_slist_append(headers, "Content-Type: application/json"); 82 | // headers = curl_slist_append(headers, ""); 83 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); 84 | ``` 85 | 86 | Sending a `POST` request. It takes the data we want to post as 87 | argument in JSON format. 88 | Inside the function, we dump this JSON object into a c-style string. 89 | ```cpp 90 | json httpPost(const std::string& url, const json& data) { 91 | CURL* curl; 92 | CURLcode res; 93 | std::string readBuffer; 94 | 95 | curl = curl_easy_init(); 96 | if(curl) { 97 | curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); 98 | curl_easy_setopt(curl, CURLOPT_POST, 1L); 99 | 100 | // JSON data 101 | std::string jsonData = data.dump(); 102 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonData.c_str()); 103 | 104 | struct curl_slist* headers = NULL; 105 | headers = curl_slist_append(headers, "Content-Type: application/json"); 106 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); 107 | 108 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); 109 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); 110 | 111 | res = curl_easy_perform(curl); 112 | curl_easy_cleanup(curl); 113 | 114 | if(res != CURLE_OK) { 115 | std::cerr << "POST request failed: " << curl_easy_strerror(res) << std::endl; 116 | } 117 | long response_code; 118 | curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); 119 | std::cout << "Got response code: " << response_code << std::endl; 120 | } 121 | 122 | return json::parse(readBuffer); 123 | } 124 | ``` 125 | 126 | Function to send PUT request: 127 | ```cpp 128 | json httpPut(const std::string& url, const json& data) { 129 | CURL* curl; 130 | CURLcode res; 131 | std::string readBuffer; 132 | 133 | curl = curl_easy_init(); 134 | if(curl) { 135 | curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); 136 | curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); 137 | 138 | std::string jsonData = data.dump(); 139 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonData.c_str()); 140 | 141 | struct curl_slist* headers = NULL; 142 | headers = curl_slist_append(headers, "Content-Type: application/json"); 143 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); 144 | 145 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); 146 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); 147 | 148 | res = curl_easy_perform(curl); 149 | curl_easy_cleanup(curl); 150 | 151 | if(res != CURLE_OK) { 152 | std::cerr << "PUT request failed: " << curl_easy_strerror(res) << std::endl; 153 | } 154 | } 155 | 156 | return json::parse(readBuffer); 157 | } 158 | ``` 159 | 160 | Function to send PATCH request: 161 | ```cpp 162 | json httpPatch(const std::string& url, const json& data) { 163 | CURL* curl; 164 | CURLcode res; 165 | std::string readBuffer; 166 | 167 | curl = curl_easy_init(); 168 | if(curl) { 169 | curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); 170 | curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); 171 | 172 | std::string jsonData = data.dump(); 173 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonData.c_str()); 174 | 175 | struct curl_slist* headers = NULL; 176 | headers = curl_slist_append(headers, "Content-Type: application/json"); 177 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); 178 | 179 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); 180 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); 181 | 182 | res = curl_easy_perform(curl); 183 | curl_easy_cleanup(curl); 184 | 185 | if(res != CURLE_OK) { 186 | std::cerr << "PATCH request failed: " << curl_easy_strerror(res) << std::endl; 187 | } 188 | } 189 | 190 | return json::parse(readBuffer); 191 | } 192 | ``` 193 | Function to send DELETE request: 194 | ```cpp 195 | void httpDelete(const std::string& url) { 196 | CURL* curl; 197 | CURLcode res; 198 | 199 | curl = curl_easy_init(); 200 | if(curl) { 201 | curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); 202 | curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); 203 | 204 | res = curl_easy_perform(curl); 205 | curl_easy_cleanup(curl); 206 | 207 | if(res != CURLE_OK) { 208 | std::cerr << "DELETE request failed: " << curl_easy_strerror(res) << std::endl; 209 | } 210 | } 211 | } 212 | ``` 213 | #### Putting everything together: 214 | 215 | ```cpp 216 | #include 217 | #include 218 | #include "json.hpp" 219 | #include 220 | 221 | using json = nlohmann::json; 222 | 223 | // Helper function to capture server responses into a string 224 | static size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* out) { 225 | size_t totalSize = size * nmemb; 226 | out->append((char*)contents, totalSize); 227 | return totalSize; 228 | } 229 | 230 | // Function to send GET request 231 | json httpGet(const std::string& url) { 232 | CURL* curl; 233 | CURLcode res; 234 | std::string readBuffer; 235 | 236 | curl = curl_easy_init(); 237 | if(curl) { 238 | curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); 239 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); 240 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); 241 | 242 | res = curl_easy_perform(curl); 243 | curl_easy_cleanup(curl); 244 | 245 | if(res != CURLE_OK) { 246 | std::cerr << "GET request failed: " << curl_easy_strerror(res) << std::endl; 247 | } 248 | } 249 | 250 | return json::parse(readBuffer); 251 | } 252 | 253 | // Function to send POST request 254 | json httpPost(const std::string& url, const json& data) { 255 | CURL* curl; 256 | CURLcode res; 257 | std::string readBuffer; 258 | 259 | curl = curl_easy_init(); 260 | if(curl) { 261 | curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); 262 | curl_easy_setopt(curl, CURLOPT_POST, 1L); 263 | 264 | // JSON data 265 | std::string jsonData = data.dump(); 266 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonData.c_str()); 267 | 268 | struct curl_slist* headers = NULL; 269 | headers = curl_slist_append(headers, "Content-Type: application/json"); 270 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); 271 | 272 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); 273 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); 274 | 275 | res = curl_easy_perform(curl); 276 | curl_easy_cleanup(curl); 277 | 278 | if(res != CURLE_OK) { 279 | std::cerr << "POST request failed: " << curl_easy_strerror(res) << std::endl; 280 | } 281 | } 282 | 283 | return json::parse(readBuffer); 284 | } 285 | 286 | // Function to send PUT request 287 | json httpPut(const std::string& url, const json& data) { 288 | CURL* curl; 289 | CURLcode res; 290 | std::string readBuffer; 291 | 292 | curl = curl_easy_init(); 293 | if(curl) { 294 | curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); 295 | curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); 296 | 297 | std::string jsonData = data.dump(); 298 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonData.c_str()); 299 | 300 | struct curl_slist* headers = NULL; 301 | headers = curl_slist_append(headers, "Content-Type: application/json"); 302 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); 303 | 304 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); 305 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); 306 | 307 | res = curl_easy_perform(curl); 308 | curl_easy_cleanup(curl); 309 | 310 | if(res != CURLE_OK) { 311 | std::cerr << "PUT request failed: " << curl_easy_strerror(res) << std::endl; 312 | } 313 | } 314 | 315 | return json::parse(readBuffer); 316 | } 317 | 318 | // Function to send PATCH request 319 | json httpPatch(const std::string& url, const json& data) { 320 | CURL* curl; 321 | CURLcode res; 322 | std::string readBuffer; 323 | 324 | curl = curl_easy_init(); 325 | if(curl) { 326 | curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); 327 | curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); 328 | 329 | std::string jsonData = data.dump(); 330 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonData.c_str()); 331 | 332 | struct curl_slist* headers = NULL; 333 | headers = curl_slist_append(headers, "Content-Type: application/json"); 334 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); 335 | 336 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); 337 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); 338 | 339 | res = curl_easy_perform(curl); 340 | curl_easy_cleanup(curl); 341 | 342 | if(res != CURLE_OK) { 343 | std::cerr << "PATCH request failed: " << curl_easy_strerror(res) << std::endl; 344 | } 345 | } 346 | 347 | return json::parse(readBuffer); 348 | } 349 | 350 | // Function to send DELETE request 351 | void httpDelete(const std::string& url) { 352 | CURL* curl; 353 | CURLcode res; 354 | 355 | curl = curl_easy_init(); 356 | if(curl) { 357 | curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); 358 | curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); 359 | 360 | res = curl_easy_perform(curl); 361 | curl_easy_cleanup(curl); 362 | 363 | if(res != CURLE_OK) { 364 | std::cerr << "DELETE request failed: " << curl_easy_strerror(res) << std::endl; 365 | } 366 | } 367 | } 368 | 369 | int main() { 370 | std::string baseUrl = "https://jsonplaceholder.typicode.com/users"; 371 | 372 | // 1. GET Request - Retrieve a list of users 373 | std::cout << "GET Request: Fetching users..." << std::endl; 374 | json users = httpGet(baseUrl); 375 | std::ofstream file("../key.json"); 376 | file << users.dump(4); 377 | std::cout << users[0]["address"].dump(4) << std::endl; // Pretty print the JSON 378 | 379 | // 2. Modify user data (update name) 380 | json user = users[0]; 381 | user["name"] = "John Doe Updated"; 382 | 383 | // 3. POST Request - Send the modified user back to the server 384 | std::cout << "\nPOST Request: Creating a new user..." << std::endl; 385 | json newUser = httpPost(baseUrl, user); 386 | std::cout << newUser.dump(4) << std::endl; 387 | 388 | // // 4. PUT Request - Update the entire user 389 | std::string putUrl = baseUrl + "/1"; // Assuming user ID is 1 390 | std::cout << "\nPUT Request: Updating user 1..." << std::endl; 391 | json updatedUser = httpPut(putUrl, user); 392 | std::cout << updatedUser.dump(4) << std::endl; 393 | 394 | // 5. PATCH Request - Update a single field of the user 395 | json patchData; 396 | patchData["email"] = "updated.email@example.com"; 397 | std::cout << "\nPATCH Request: Updating user's email..." << std::endl; 398 | json patchedUser = httpPatch(putUrl, patchData); 399 | std::cout << patchedUser.dump(4) << std::endl; 400 | 401 | // 6. DELETE Request - Remove the user 402 | std::cout << "\nDELETE Request: Deleting user 1..." << std::endl; 403 | httpDelete(putUrl); 404 | std::cout << "User 1 deleted." << std::endl; 405 | 406 | return 0; 407 | } 408 | ``` -------------------------------------------------------------------------------- /notes/lambdas.md: -------------------------------------------------------------------------------- 1 | ## Lambdas in C++ 2 | 3 | ```cpp 4 | class Plus { 5 | int value; 6 | public: 7 | Plus(int v): value(v) {} 8 | int operator() (int x) const { 9 | return x + value; 10 | } 11 | }; 12 | 13 | // The above thing can be achived as (lambdas reduce boilercode): 14 | 15 | auto plus = [value = 1] (int x) { 16 | return x + value; 17 | }; 18 | 19 | // this plus is basically an object of some class whose name I can't spell. 20 | // it has a data member `value` (captures) 21 | ``` 22 | 23 | `lambda` is object of some anonymous class, but the `this` keyword inside this 24 | lambda won't work as usual. It wouldn't point to this object. Rather `this` 25 | inside a lambda refers to the `this` of outer context (wherever this lambda is 26 | being declared)! 27 | 28 | ![](../assets/lambda.png) 29 | Here `kitten` makes a copy when it needs. 30 | 31 | `cat` makes a copy when initialised. 32 | 33 | > By default, lambdas that capture variables by value are const (meaning you 34 | cannot modify the captured variables inside the lambda). If you want to modify 35 | the captured value, you can use the mutable keyword. 36 | 37 | Sending lambda as a parameter to the function: 38 | ![](../assets/lambda-param.png) 39 | 40 | Alternatively we can use `std::function` in the parameter type and 41 | pass a lambda to it. 42 | 43 | Copying of lambdas: 44 | 45 | ![](../assets/copy.png) -------------------------------------------------------------------------------- /notes/latency_numbers.md: -------------------------------------------------------------------------------- 1 | ## Interesting Latency Numbers 2 | 3 | The notes are taken from this [link](https://gist.github.com/hellerbarde/2843375). 4 | 5 | ### CPU Frequency (Clock Speed): 6 | - The CPU frequency refers to the number of cycles the CPU can execute per second. It’s typically measured in Hertz (Hz), with modern CPUs operating in the range of Gigahertz (GHz) (1 GHz = 1 billion cycles per second). 7 | - Higher frequency generally means more cycles per second, but that doesn’t directly translate to faster execution if other bottlenecks, like memory access or instruction complexity, exist. 8 | - Example: A CPU with a frequency of 3 GHz can execute up to 3 billion cycles per second. 9 | 10 | `On Average, 1 Clock Cycle = 0.3 ns` 11 | 12 | ```cpp 13 | L1 cache reference ......................... 0.5 ns 14 | Branch mispredict ............................ 5 ns 15 | L2 cache reference ........................... 7 ns 16 | Mutex lock/unlock ........................... 25 ns 17 | Main memory reference ...................... 100 ns 18 | Compress 1K bytes with Zippy ............. 3,000 ns = 3 µs 19 | Send 2K bytes over 1 Gbps network ....... 20,000 ns = 20 µs 20 | SSD random read ........................ 150,000 ns = 150 µs 21 | Read 1 MB sequentially from memory ..... 250,000 ns = 250 µs 22 | Round trip within same datacenter ...... 500,000 ns = 0.5 ms 23 | Read 1 MB sequentially from SSD* ..... 1,000,000 ns = 1 ms 24 | Disk seek ........................... 10,000,000 ns = 10 ms 25 | Read 1 MB sequentially from disk .... 20,000,000 ns = 20 ms 26 | Send packet CA->Netherlands->CA .... 150,000,000 ns = 150 ms 27 | ``` 28 | 29 | ### Lets multiply all these durations by a billion: 30 | 31 | Magnitudes: 32 | 33 | #### Minute: 34 | L1 cache reference 0.5 s Blink of eye (0.5 s) 35 | Branch mispredict 5 s Sip of water 36 | L2 cache reference 7 s Long yawn 37 | Mutex lock/unlock 25 s Making a coffee 38 | 39 | ### Hour: 40 | Main memory reference 100 s Brushing your teeth 41 | Compress 1K bytes with Zippy 50 min One episode of a TV show (including ad breaks) 42 | 43 | ### Day: 44 | Send 2K bytes over 1 Gbps network 5.5 hr From lunch to end of work day 45 | 46 | ### Week 47 | SSD random read 1.7 days A normal weekend 48 | Read 1 MB sequentially from memory 2.9 days A long weekend 49 | Round trip within same datacenter 5.8 days A medium vacation 50 | Read 1 MB sequentially from SSD 11.6 days Waiting for almost 2 weeks for a delivery 51 | 52 | ### Year 53 | Disk seek 16.5 weeks A semester in university 54 | Read 1 MB sequentially from disk 7.8 months Almost producing a new human being 55 | The above 2 together 1 year 56 | 57 | ### Decade 58 | Send packet CA->Netherlands->CA 4.8 years CS5 degree at IITD -------------------------------------------------------------------------------- /notes/linux_tcp.md: -------------------------------------------------------------------------------- 1 | # LINUX Networking 2 | 3 | ## High Level Socket API 4 | 5 | 1. [CS361 Video](https://youtu.be/XXfdzwEsxFk?si=VGb5lymk8Fkaglqk) 6 | 2. [Video on Protocol Stack](https://www.youtube.com/watch?v=3b_TAYtzuho) 7 | 8 | `Server`: This is the component which listens for connections. 9 | 10 | `Client`: It sends a connect request to the server. This is the component, which 11 | initiates a connection. 12 | 13 | ```mermaid 14 | sequenceDiagram 15 | participant Client 16 | participant Server 17 | participant SocketServer as Server Socket 18 | participant SocketClient as Client Socket 19 | 20 | Server->>SocketServer: socket() 21 | Server->>SocketServer: bind() 22 | Server->>SocketServer: listen() 23 | Server-->>Client: Waiting for connection 24 | 25 | Client->>SocketClient: socket() 26 | Client->>SocketServer: connect() 27 | 28 | Server->>SocketServer: accept() 29 | Server-->>Client: Connection accepted 30 | 31 | Client->>SocketServer: send("Hello, Server!") 32 | SocketServer->>Server: recv() 33 | Server-->>Client: send("Hello, Client!") 34 | Client->>SocketClient: recv() 35 | 36 | Client->>SocketClient: close() 37 | Server->>SocketServer: close() 38 | ``` 39 | 40 | ### Client Side 41 | 42 | `socket`: It just creates a file descriptor, but doesn't do anything more! 43 | 44 | `connect`: Takes a file descriptor and server's address & sends a connection 45 | request to the server. 46 | 47 | `send & recv`: Given a connected file descriptor, submit bytes to the OS for 48 | delivery and ask OS to deliver the bytes. (Similar to read/write.) 49 | - These functions don't do the actual transfer of bytes, they are just asking the 50 | OS to do these things. 51 | 52 | `close`: Given a connected file descriptor, it tells the OS that this connection 53 | can be terminated. 54 | - Kernel continues sending the buffered bytes. 55 | - At the end of buffered bytes, sends special 'EOF' message: which tells the receiver 56 | that I am done sending, and will close the connection, you can close too! 57 | 58 | ### Server Side 59 | 60 | `bind`: Given a file descriptor, tells the kernel to associate it with the 61 | given IP and port (Making a reservation at this address.) 62 | 63 | `listen`: Given a file descriptor that has been binded to a IP/Port, it now asks 64 | the OS that it wishes to start receiving connections. 65 | 66 | `accept`: Given a fd which is listening, it creates a `new fd` that can be used to 67 | communicate with individual client. This call is blocking by default, until a 68 | client shows up. 69 | - Note that here a new fd has been created to talk to this client. The old fd is 70 | not for sending/receiving messages from the client. It is just used for accepting 71 | connections from the client. 72 | 73 | 74 | *Keeping track of the return values of send/recv are very crucial. The returned 75 | bytes may be less than what you asked for. So keep trying until you have received 76 | what you wanted. 77 | You are only submitting the bytes to the operating system, not actually sending 78 | them to the server.* 79 | 80 | 81 | ## How The Kernel Handles A TCP Connection 82 | 83 | Packets and Syscall Analysis: [Video](https://www.youtube.com/watch?v=ck4WvYM9V4c) 84 | 85 | ### TCP Three-Way Handshake Overview 86 | 87 | ```mermaid 88 | sequenceDiagram 89 | participant cc as Client[192.168.1.105:1234] 90 | participant ss as Server[192.168.1.102:5000] 91 | 92 | cc->>ss: SYN (SEQ = X) 93 | ss-->>cc: SYN-ACK (SEQ = Y, ACK = X + 1) 94 | cc->>ss: ACK (ACK = Y + 1) 95 | Note over cc: 3 Way Handshaking done 96 | ss->>cc: Hello World 97 | ``` 98 | ### Using `ncat` 99 | 100 | - Creating server: `ncat -l 1234` 101 | 102 | - `-l`: Tells Ncat to listen for incoming connections. 103 | - `1234`: The port where Ncat is listening for connections. 104 | - Connect to a server: `ncat ` 105 | 106 | - Create UDP server: `ncat -l -u 1234` 107 | - Connect to UDP server: `ncat -u ` 108 | 109 | ### Server Side Analysis 110 | 111 | The following events happen while running `ncat` on the server side: 112 | 113 | 1. `socket` system call: It requests an ipv4 socket to be created. 114 | On success, it returns a file descriptor for the newly created 115 | socket (let fd = 3). 116 | 117 | 2. `bind` system call: It takes a file descriptor (fd 3) (returned by 118 | socket), and binds it to an ip address and port. Socket itself is 119 | just a data structure with a buffer. We need to bind it. 120 | 121 | 3. `listen` system call: Listens on this socket for any `TCP SYN` 122 | request. 123 | 124 | 4. Then we make a `select` system call. This call waits on a file descriptor 125 | (fd 3), 126 | until its available for read or write. This is generally used in IO multiplexing. 127 | In our case, we wait on the socket's fd. Here are waiting for someone to send 128 | connection requests, since we are listening for connection. The `ncat` process 129 | is wait-blocked at this moment. It is unblocked, when some desirable event occurs. 130 | 131 | 5. When it is unblocked, it means a `syn` request has arrived, and kernel has 132 | completed 3-way handshake with the client. 133 | Thus `accept` system call is ran on this fd 3. The accept system call on success 134 | returns a new data socket for that connection. A `new fd (let fd 4)` is now 135 | created to talk to this client. 136 | 137 | 6. `close` system call is called on fd 3 (this is specific to ncat, since it only 138 | accepts on client connection). 139 | 140 | 7. We then make `select` system call on fd = 0, 4. `fd 0` is for stdin, incase we 141 | want to send some data to client and `fd 4` is the data socket, which will tell 142 | if we have something to read. 143 | 144 | Suppose we receive a packet over wifi; an interrupt will be raised and 145 | corresponding interrupt handler will be called. 146 | Inside the kernel thread running the interrupt handler, we consume the packet 147 | from the buffer, create `skb` struct (socket kernel buffer). 148 | After consuming, we copy the packet into the socket buffer of the `ncat`. At 149 | this point the `select` syscall (waiting on this fd) of the user process returns. 150 | 151 | ### Handshaking from Process' POV 152 | The protocol stack of Linux does the handshaking without involving the application. 153 | 154 | The kernel unblocks the process waiting on `select` syscall (while listening 155 | for new connection), only when the handshake is completed. The user process is not 156 | involved in the handshake. 157 | It only receives the last `ack` of 3-way handshake. On receiving, it can accept 158 | the new connection and can start to `send/recv` data. 159 | 160 | ![](../assets/syn.png) 161 | *Note that size of accept queue is bounded. If we don't accept the ready connections, new connections would be dropped* 162 | 163 | ### ACKs 164 | Even the `acks/retransmissions` are handled by the kernel's protocol stack. 165 | The application is not notified about this. The TCP header contain flags, which 166 | tell if it contains some data or ack. Based on the flag, the protocol stack takes 167 | appropriate decisions. 168 | 169 | Generally the user process if only unblocked (from `select` syscall), when the 170 | kernel copies some useful data into the fd's read buffer, which the process can 171 | consume. 172 | 173 | ### Some benchmarks 174 | 175 | *take it with pinch of salt* 176 | 177 | - From receiving interrupt for SYN to accepting the connection: 1.7 ms 178 | (*It actually depends between the distance between client and server, as 2 more 179 | exchanges are involved in between. This data is from localhost I think*) 180 | 181 | - Processing a packet interrupt: 200 microseconds 182 | -------------------------------------------------------------------------------- /notes/memory_reordering.md: -------------------------------------------------------------------------------- 1 | # Memory Reordering 2 | 3 | ### Some Background 4 | Modern CPUs employ lots of techniques to counteract the latency cost of going 5 | to main memory. These days CPUs can process hundreds of instructions in the 6 | time it takes to read or write data to the DRAM memory banks. 7 | 8 | Memory access is generally the bottlenack. 9 | 10 | Hardware caches are the most common tools used to hide this latency. 11 | Unfortunately CPUs are now so fast that even these caches cannot keep up at 12 | times. So to further hide this latency a number of less well known buffers 13 | are used. 14 | 15 | #### Lets understand about `store buffers` 16 | 17 | When a CPU executes a store operation it will try to write the data to the `L1 18 | cache` nearest to the CPU. If a cache miss occurs at this stage the CPU goes 19 | out to the next layer of cache. At this point on an Intel, and many other, 20 | CPUs a technique known as `write combining` comes into play. 21 | 22 | While the request for ownership of the L2 cache line is outstanding the data to 23 | be stored is written to one of a number of cache line sized buffers on the 24 | processor itself, known as `store buffers` on Intel CPUs. These on chip 25 | buffers allow the CPU to continue processing instructions while the cache 26 | sub-system gets ready to receive and process the data. The biggest advantage 27 | comes when the data is not present in any of the other cache layers. 28 | 29 | These buffers become very interesting when subsequent writes happen to require 30 | the same cache line. The subsequent writes can be combined into the buffer 31 | before it is committed down the cache hierarchy. 32 | 33 | > What happens if the program wants to read some of the data that has been 34 | written to a buffer? Well our hardware friends have thought of that and they 35 | will snoop the buffers before they read the caches. 36 | 37 | ![](../assets/store_buffer.png) 38 | 39 | *Loads and stores to the caches and main memory are buffered and re-ordered 40 | using the load, store, and write-combining buffers. These buffers are 41 | associative queues that allow fast lookup. This lookup is necessary when a 42 | later load needs to read the value of a previous store that has not yet reached 43 | the cache.* 44 | 45 | ### Fencing 46 | 47 | When a program is executed it does not matter if its instructions are 48 | re-ordered provided the same end result is achieved. For example, within a loop 49 | it does not matter when the loop counter is updated if no operation within the 50 | loop uses it. 51 | The compiler and CPU are free to re-order the instructions to best utilise the 52 | CPU provided it is updated by the time the next iteration is about to 53 | commence. 54 | Also over the execution of a loop this variable may be stored in a register and 55 | never pushed out to cache or main memory, thus it is never visible to another 56 | CPU. 57 | 58 | > We use `volatile` keyword to tell the compiler that don't store this variable 59 | in register. Always push down the changes to memory. So that other CPU can 60 | always see the latest value. 61 | 62 | Provided “program order” is preserved the CPU, and compiler, are free to do 63 | whatever they see fit to improve performance. 64 | 65 | 66 | 67 | 68 | ### Hardware Memory Ordering in x86 Processors 69 | 70 | The term memory ordering refers to the order in which the processor issues 71 | reads (loads) and writes (stores) through the system bus to system memory. 72 | 73 | For example, the Intel386 processor enforces program ordering 74 | (generally referred to as strong ordering), where reads and writes are issued 75 | on the system in the order they occur in the instruction stream. 76 | 77 | But the hardware may reorder the instructions for some optimizations. Sometimes 78 | reads could go ahead of buffered writes. 79 | 80 | Reads may be reordered with older writes to different memory locations 81 | but not with older writes to same memory location. 82 | 83 | That is, if we write to location 1 and read from location 2, then the read from 84 | location 2 could become globally visible before write to location 1. 85 | 86 | ``` 87 | let x = y = 0 88 | 89 | processor 0 processor 1 90 | x = 1 y = 1 91 | print y print x 92 | 93 | output = (0, 0) is possible 94 | ``` 95 | 96 | Stores are usually buffered before being sent to memory (L1 cache). We 97 | prioritise loads more than stores. Since they are on critical path. The instructions 98 | are waiting for the data to be loaded before they can run. 99 | Although if a store followed by a load are for same memory location then we will 100 | definitely follow program order. 101 | 102 | ### Software Reordering 103 | Compiler can also sometimes reorder instructions in our program for optimizations. 104 | For example, store to 2 different memory locations can be reordered by our 105 | compiler. 106 | 107 | ### Avoid memory reordering 108 | In a multi-threaded environment techniques need to be employed for making 109 | program results visible in a timely manner. 110 | The techniques for making memory visible from a processor core are known as 111 | memory barriers or fences. 112 | 113 | Memory barriers provide two properties. Firstly, they preserve externally 114 | visible program order by ensuring all instructions either side of the barrier 115 | appear in the correct program order if observed from another CPU and, secondly, 116 | they make the memory visible by ensuring the data is propagated to the cache 117 | sub-system. 118 | 119 | #### Asking compiler not to reorder 120 | `asm volatile("" : : : "memory");` Fake instruction that asks compiler to not 121 | reorder any memory instruction around this barrier. A hint to compiler that 122 | whole of the memory can be touched by this instruction: hence don't do any 123 | reordering. 124 | 125 | *In this case the hardware can still reorder instructions, even though we asked 126 | our compiler to not reorder! Hence we will have to use hardware barriers.* 127 | 128 | ```cpp 129 | #include 130 | void _mm_mfence (void) // Use this instruction as a barrier to prevent re-ordering in the hardware! 131 | ``` 132 | 133 | Perform serializing operation on all `load-from-memory` and `store-to-memory` 134 | instructions that were issued prior to this instruction. 135 | Guarantees that every memory access that precedes, in program order the memory 136 | fence instruction is globally visible before any memory instruction which 137 | follows the fence in program order. 138 | 139 | *It drains the `store buffer`, before any following `loads` can go into memory.* 140 | 141 | ### Performance Impact of Memory Barriers 142 | 143 | Memory barriers prevent a CPU from performing a lot of techniques to hide 144 | memory latency therefore they have a significant performance cost which must be 145 | considered. To achieve maximum performance it is best to model the problem so 146 | the processor can do units of work, then have all the necessary memory barriers 147 | occur on the boundaries of these work units 148 | 149 | ## C++ Memory Model 150 | 151 | - [Memory Model Article](https://dev.to/kprotty/understanding-atomics-and-memory-ordering-2mom) 152 | - [Post on Stack Overflow](https://stackoverflow.com/questions/12346487/what-do-each-memory-order-mean) 153 | 154 | 155 | -------------------------------------------------------------------------------- /notes/metaprogramming.md: -------------------------------------------------------------------------------- 1 | ## Template Metaprogramming 2 | 3 | [Link to CPPCON Talk](https://youtu.be/Am2is2QCvxY?si=QrulPFBy7Dg5poQ1) 4 | 5 | - Do work at compile time that otherwise would be done at Runtime. 6 | - In C++, the template instantiation happens at the compile time, hence we make 7 | use of it. 8 | - For example if we call `f(x)` the compiler will manufacture(instantiate) the 9 | function for us (assume f is a template here). 10 | - It is not free, as the heavy work needs to be done at compile time which 11 | leads to increased compile time. 12 | - Can't rely on runtime primitives like virtual functions, dynamic dispatch. 13 | Keep things constant while metaprogramming. 14 | 15 | 1. **Absolute Value Metafunction** 16 | 17 | ```cpp 18 | template 19 | struct ABS { 20 | static constexpr int value = (N < 0) ? -N : N; 21 | }; 22 | ``` 23 | 24 | - A metafunction call: The arguments are passed through the template's 25 | arguments. 26 | - `Call` syntax is a request for the template's static value. 27 | - `const int ans = ABS<-142>::value;` 28 | 29 | 2. **Compile Time GCD** 30 | 31 | Here we use compile time recursion. For base cases, we have to do pattern 32 | matching. 33 | 34 | ```cpp 35 | template 36 | struct gcd { 37 | static constexpr int value = gcd::value; 38 | }; 39 | 40 | template 41 | struct gcd { 42 | static_assert(N != 0); 43 | static constexpr int value = N; 44 | }; 45 | ``` 46 | 47 | 3. **Metafunction can take a type as Parameter/Argument** 48 | We can make a metafunction similar to `sizeof` 49 | 50 | ```cpp 51 | // primary template handles scalar (non-array) types as base case: 52 | template 53 | struct rank { 54 | static constexpr size_t value = 0u; 55 | }; 56 | 57 | // partial specialization recognizes any array type: 58 | template 59 | struct rank { 60 | static constexpr size_t value = 1 + rank::value; 61 | }; 62 | 63 | const int N = rank::value; // gives 3 at compile time 64 | ``` 65 | 66 | *Here we didn't recurse on the primary template, but did on the specialisation.* 67 | 68 | 4. **Type** 69 | ```cpp 70 | #include 71 | #include 72 | 73 | // A simple type trait to remove constness 74 | template 75 | struct RemoveConst { 76 | using type = T; // Default case: T is unchanged 77 | }; 78 | 79 | template 80 | struct RemoveConst { 81 | using type = T; // Specialized case: remove const qualifier 82 | }; 83 | 84 | int main() { 85 | // Using the RemoveConst trait 86 | RemoveConst::type x = 42; // 'RemoveConst::type' is equivalent to 'int' 87 | std::cout << "x = " << x << std::endl; 88 | 89 | return 0; 90 | } 91 | ``` 92 | 93 | 5. **Conditional Types during compile time** 94 | ```cpp 95 | #include 96 | #include 97 | 98 | 99 | template 100 | struct type_is { 101 | using type = T; 102 | }; 103 | 104 | // primary template assumes the bool value is true: 105 | template 106 | struct conditional_type : type_is {}; 107 | 108 | // partial specialization recognizes a false value: 109 | template 110 | struct conditional_type : type_is {}; 111 | 112 | int main() { 113 | constexpr bool q = false; 114 | conditional_type::type s; 115 | std::cout << sizeof(s) << '\n'; 116 | return 0; 117 | } 118 | ``` 119 | `false_type` and `true_type` can have static value with F/T. 120 | 121 | ![](../assets/meta1.png) 122 | 123 | How to deal with parameters pack: 124 | 125 | ![](../assets/meta2.png) 126 | 127 | -------------------------------------------------------------------------------- /notes/move_semantics.md: -------------------------------------------------------------------------------- 1 | ## Move Semantics 2 | 3 | Move Semantics by Klaus Iglberger (CppCon 2019): [Link](https://www.youtube.com/watch?v=St0MNEU5b0o) 4 | 5 | ```cpp 6 | std::vector v1 {1, 2, 3, 4, 5}; 7 | // in stack we will just store the pointers (to the start and the end) 8 | // the actual elements will be stored in heap 9 | std::vector v2 {}; 10 | v2 = v1; 11 | // when we do v2 = v1, we copy the contents, that is new memory is assigned in 12 | // heap and content of old vectors are copied 13 | 14 | v2 = std::move(v1); 15 | // in this case we are transferring the ownership. that is, now the pointer of 16 | // v2 will point to the heap memory orignally pointed by v1. 17 | // and pointers of v1 will be set to 0. 18 | ``` 19 | 20 | ### L value vs R value 21 | 22 | 1. **L-Value (Left Value)**: 23 | An **L-value** is an expression that **refers to a memory location** and 24 | can persist beyond a single expression. It typically appears on 25 | the **left-hand side** of an assignment, but it can also be used on the 26 | right-hand side. 27 | 28 | - **L-values** have an identifiable location in memory, meaning you can take 29 | their address using the `&` operator. 30 | - Examples of **L-values** include variables, dereferenced pointers, or array 31 | elements. 32 | - **Modifiable L-values** are L-values that can be changed (i.e., non-const), 33 | while **non-modifiable L-values** are constants. 34 | 35 | #### Examples of L-values: 36 | ```cpp 37 | int x = 5; // `x` is an L-value because it refers to a memory location 38 | x = 10; // `x` can appear on the left-hand side of an assignment 39 | 40 | int* p = &x; // You can take the address of an L-value 41 | *p = 20; // Dereferenced pointer `*p` is an L-value 42 | ``` 43 | 44 | In the code above: 45 | - `x` is an L-value because it refers to a memory location that persists across 46 | expressions. 47 | - `*p` is also an L-value because it refers to the value stored at the 48 | address in `p`. 49 | 50 | 2. **R-Value (Right Value)**: 51 | An **R-value** is an expression that does **not have a persistent memory location**. 52 | It usually represents **temporary values** that only exist during the evaluation 53 | of an expression. R-values typically appear on the **right-hand side** of an 54 | assignment and are not addressable (you can't take the address of an R-value). 55 | 56 | - R-values are usually **temporary objects**, **literals**, or **expressions** 57 | like `2 + 3`. 58 | - You can't assign to an R-value because they do not refer to a memory location 59 | that can be modified. 60 | 61 | #### Examples of R-values: 62 | ```cpp 63 | int x = 5 + 10; // `5 + 10` is an R-value (a temporary value) 64 | int y = 42; // `42` is an R-value (literal) 65 | 66 | x = y + 1; // `y + 1` is an R-value (result of the expression) 67 | ``` 68 | 69 | In the code above: 70 | - `5 + 10` is an R-value because it's a temporary result and cannot be assigned to. 71 | - `42` is a literal R-value. 72 | 73 | 74 | #### L-value References: 75 | - Traditional references (as introduced in earlier versions of C++) are **L-value references**. 76 | - They can only bind to L-values. 77 | 78 | Example: 79 | ```cpp 80 | int x = 10; 81 | int& ref = x; // L-value reference to `x` 82 | ref = 20; // Modifies `x` 83 | ``` 84 | 85 | #### R-value References: 86 | - **R-value references** (introduced in C++11) are used to bind to R-values, 87 | allowing you to modify them. 88 | - They are denoted by `&&`. 89 | - Commonly used in **move semantics** to avoid unnecessary copies. 90 | 91 | Example: 92 | ```cpp 93 | int&& rref = 5; // R-value reference to the temporary value `5` 94 | rref = 10; // Modifies the R-value 95 | ``` 96 | 97 | Here, `rref` is an R-value reference that allows us to bind to a temporary 98 | object (`5`) and even modify it. 99 | 100 | - **Move semantics** make use of R-value references to "move" resources from 101 | one object to another, avoiding expensive deep copies. This is especially useful 102 | when dealing with temporary objects (R-values). 103 | 104 | Example: 105 | ```cpp 106 | std::string s1 = "Hello"; 107 | std::string s2 = std::move(s1); // Moves the contents of `s1` to `s2` 108 | // after ownership transfer s1 will now be a valid but undefined state! 109 | ``` 110 | 111 | In the code above: 112 | - `std::move(s1)` is an R-value reference that allows the move constructor of 113 | `std::string` to transfer ownership of the data from `s1` to `s2`. 114 | - `std::move` unconditionally casts its input into an rvalue reference. 115 | It doesnt move anything! 116 | - `std::move(s)` when s is const, leads to copy not move! 117 | 118 | ```cpp 119 | const std::string s1 = "Shivam Verma"; 120 | std::string s2 = std::move(s1); // this is COPY not MOVE 121 | std::cout << s1 << ' ' << s2 << '\n'; 122 | ``` 123 | 124 | 125 | ### Operators 126 | 1. Copy Assignment Operator 127 | 128 | `vector& operator=(const vector& rhs);` It takes an lvalue 129 | 130 | 2. Move Assignment Operator 131 | 132 | `vector& operator=(vector&& rhs);` It takes an rvalue 133 | 134 | ```cpp 135 | class Widget { 136 | private: 137 | int i {0}; 138 | std::string s{}; 139 | int* pi {nullptr}; 140 | 141 | public: 142 | // Move constructor: Goal is to transfer content of w into this 143 | // Leave w in a valid but undefined state 144 | Widget (Widget&& w) : i (w.i), 145 | s (std::move(w.s)), 146 | pi (w.pi) { 147 | w.pi = nullptr; 148 | 149 | // we could also do: i (std::move(w.i)), 150 | // s (std::move(w.s)), 151 | // pi (std::move(w.pi)) 152 | 153 | } 154 | 155 | // Move assignment operator 156 | Widget& operator=(Widget&& w) { 157 | i = std::move(w.i); 158 | // s = w.s // don't do this, it copies not move 159 | s = std::move(w.s); 160 | delete pi; // need to clear exisiting resources first! 161 | pi = std::move(w.pi); 162 | 163 | w.pi = nullptr; // reset content of w 164 | 165 | return *this; 166 | } 167 | } 168 | ``` 169 | 170 | 171 | ### Small string optimisation 172 | 173 | 174 | ## Small String Optimization (SSO) 175 | 176 | ```mermaid 177 | graph TD; 178 | A[Small String] -->|Stored in| B[Stack Buffer]; 179 | C[Larger String] -->|Stored in| D[Heap Allocation]; 180 | B --> E{SSO}; 181 | D --> F{Heap Allocator}; 182 | ``` 183 | 184 | - For small strings (e.g., "short str"), the data is stored directly in the 185 | object on the stack. 186 | - For larger strings, the data is dynamically allocated on the heap, and the 187 | object holds a pointer to that data. 188 | 189 | 190 | ## Universal References (Forwarding Reference) 191 | 192 | ```cpp 193 | template 194 | void f(T&& x); // Forwarding Reference 195 | 196 | auto&& var2 = var1; // Forwarding Reference 197 | ``` 198 | They represent: 199 | - an `lvalue` reference if they are initialised by an lvalue. 200 | - an `rvalue` reference if they are initialised by an rvalue. 201 | 202 | ```cpp 203 | template 204 | void foo(T&& ) { 205 | print("foo(T&&)"); 206 | } 207 | 208 | int main () { 209 | Widget w{}; 210 | foo(w); // prints "foo(T&&)" 211 | foo(Wifget{}) // also prints "foo(T&&)" 212 | 213 | // w was lvalue, Widget{} was rvalue: T&& binded to both 214 | } 215 | ``` 216 | - `std::forward` conditionally casts its input into an rvalue reference. 217 | It doesnt forward anything! 218 | 219 | - If given value is lvalue, cast to an lvalue reference. 220 | - If given value is rvalue, only then cast to an rvalue reference. 221 | 222 | `rvalues` can bind to lvalue reference to const, but not to lvalue reference. 223 | ```cpp 224 | void f(Widget& ); // 1 225 | void f(const Widget& ); // 2 226 | template // 3 227 | void f(T&& ); 228 | 229 | int main() { 230 | f(getWidget{}); // this can bind to 3, 2 but not 1 231 | } 232 | ``` 233 | 234 | ![](../assets/binding.png) -------------------------------------------------------------------------------- /notes/os_booting.md: -------------------------------------------------------------------------------- 1 | # Linux Boot Process 2 | 3 | Booting is the process of loading an OS from disk and starting it. 4 | 5 | ## The OS Boot Process 6 | 7 | 1. **Hit the power button** 8 | 9 | - Triggers a `power good` signal. 10 | - Electric pulse sent to reset pin of the CPU (Power On Reset). 11 | - CPU is in `Reset` mode, i.e., it is not executing any instructions. 12 | - All devices get power and initialize themselves. 13 | - Every register is set to zero, except `Code Segment (CS)` and 14 | `Instruction Pointer (IP)`, which are set to `0xf000` and `0xfff0` respectively. 15 | - Thus, the `physical address = (CS << 4) + IP = (0xf000 << 4) + 0xfff0 = 0xf0000 + 0xfff0 = 0xffff0` (We are operating in 16-bit mode right now). 16 | - This physical address is the place where the CPU starts executing instructions. 17 | - The CPU is activated in Real Mode and it starts executing from `0xffff0` (or `ffff0h`), which is a memory location in the `BIOS chip` and not in the RAM. 18 | - The BIOS chip (Basic Input/Output System) is a small, `non-volatile` memory chip located on the motherboard of a computer. 19 | - Real Mode 20 | - Only 1 MB of RAM addressability in the range `0x0` to `0x100000`. 21 | - This is because there are 20 physical address bus lines available. (2^20 = 1048576 = 1 MB) 22 | - `16 bit addressing:` Available registers (Eg: `AX`) are of size 16 bits, so two registers are combined to give the physical address. 23 | - Logical address (LBA) = segment:offset 24 | - `Physical address = (segment << 4) + offset` 25 | - The segments are segments/parts of the addressable 1 MB of RAM and the offsets are offsets into that segment. 26 | - Eg: If the segment is `0xf000` and the offset is `0xfff0`, then the `physical address = (CS << 4) + IP = (0xf000 << 4) + 0xfff0 = 0xf0000 + 0xfff0 = 0xffff0` 27 | 2. **Basic Input/Output System (BIOS) takes over.** 28 | - Placed in Flash/EPROM Non-Volatile Memory. Its job is load the bootloader. 29 | - In a multi-processor environment, one processor is a `Boot Processor (BSP)` which executes all instructions and the others are `Application Processors (APs)`. 30 | - Conducts a Power-On Self-Test (`POST`). 31 | - Performs system inventory. 32 | - Checks and registers all devices connected. 33 | - Finds `Master Boot Record (MBR)` in the first sector of a device (the hard disk, SSD, USB, etc.) that is usually 512 bytes in size, loads it into RAM at position `0x7c00`, jumps to that location and starts executing. 34 | - MBR is a 512 byte sector that's logically split into three sections. 35 | - The first 446 bytes is reserved for a program, which is usually a Bootloader. (Eg: [GRUB](https://www.gnu.org/software/grub)) 36 | - The next 64 bytes (16x4 bytes) are for a partition table with four partitions in it. 37 | - The last two bytes are for the Boot Signature bytes `0x55` (or `55h`) and `0xAA`(or `AAh`) in order, that identify that a particular sector is the MBR. 38 | - If this signature is not found in the first sector, then the next device is searched. 39 | - The BIOS loads the Bootloader into memory. 40 | - This might be the first stage of the Bootloader, which loads the second stage of the Bootloader into memory, as 446 bytes are not sufficient to store all the complex logic required to load an OS. 41 | - Bootloader might give an option to load a particular Operating System. 42 | - Sets up the `GDT/IVT` for the Operating System. 43 | - Switches from `Real Mode` to `Protected Mode`. 44 | - Memory addressability goes from 1 MB to the entire range of available RAM. 45 | 3. **The Bootloader starts executing and checking the partition table for an active/bootable partition table.** 46 | 47 | - On finding the bootable partition, the Bootloader loads the first sector of that partition (called the Boot Record) from the hard disk to the RAM. 48 | 4. **The Boot Record loads the operating system into memory.** 49 | 5. **Timers, devices, hard disks, etc. are initialized by the Operating System in the Kernel Space.** 50 | 6. **In Linux, the `init` process is the first process in User Space that initializes the OS processes, daemons and displays login prompt.** 51 | 52 | ```mermaid 53 | graph TD 54 | A[Power Button Pressed] --> B[Power Good Signal] 55 | B --> C[CPU Starts in Reset Mode] 56 | C --> D[BIOS Loads from 0xFFFF0] 57 | D --> E[Power-On Self-Test POST] 58 | E --> F[BIOS Searches for MBR] 59 | F --> G[MBR Found on Bootable Device] 60 | G --> H[BIOS Loads Bootloader at 0x7C00] 61 | H --> I[Bootloader Starts Executing] 62 | I --> J[Bootloader Checks Partition Table] 63 | J --> K[Bootloader Finds Active Partition] 64 | K --> L[Bootloader Loads OS Kernel] 65 | L --> M[Switch to Protected Mode] 66 | M --> N[Kernel Starts Initializing Hardware] 67 | N --> O[Kernel Mounts Root Filesystem] 68 | O --> P[Kernel Starts init/systemd Process] 69 | P --> Q[init/systemd Initializes System Services] 70 | Q --> R[User Login Prompt Displayed] 71 | ``` 72 | ## Resources 73 | 74 | - [PC Booting: How PC Boots](https://www.youtube.com/watch?v=ZplB2v2eMas) 75 | - [Booting an Operating System](https://www.youtube.com/watch?v=7D4qiFIosWk) 76 | 77 | -------------------------------------------------------------------------------- /notes/packet_handling.md: -------------------------------------------------------------------------------- 1 | ## The Network Packet's Diary 2 | 3 | PDF [Notes](../assets/From%20NIC%20to%20Application.pdf) 4 | 5 | A packet consists of: 6 | 7 | ``` 8 | | Ethernet Header | IP Header | TCP Header | Data | 9 | ``` 10 | Network Interface Controller (NIC): 11 | - Receives the packet 12 | - Compares the MAC destination address (the address to be compared against is 13 | programmed by the OS) 14 | - Verifies the Ethernet (Frame) Checksum FCS 15 | - Stores the packet to buffer programmed by the driver using DMA 16 | - Triggers an interrupt 17 | 18 | Interrupt: 19 | - Top half processing: 20 | - Acknowledge the interrupt 21 | - Schedule the 'Bottom Half Processing' 22 | 23 | - Bottom half processing: 24 | - It identifies the memory where the packet is stored. 25 | - Allocates `sk_buf` (it is a struct which contains various pointers, like 26 | pointer to different headers, pointer to data and other metadata). 27 | 28 | ```mermaid 29 | graph TD; 30 | A[sk_buff] --> B[Memory Buffer]; 31 | B --> C[Head Pointer]; 32 | B --> D[Data Pointer]; 33 | B --> E[Tail Pointer]; 34 | B --> F[End Pointer]; 35 | 36 | A --> G[Packet Headers]; 37 | G --> H[Ethernet Header]; 38 | G --> I[IP Header]; 39 | G --> J[TCP/UDP Header]; 40 | 41 | A --> K[Metadata]; 42 | K --> L[Reference Counters]; 43 | K --> M[Checksum Info]; 44 | K --> N[Flags]; 45 | 46 | B --> O[Packet Data]; 47 | ``` 48 | - Passes the `sk_buf` to the protocol stack. 49 | 50 | The `sk_buf` traverses various levels, where some checksums are verified, headers 51 | are removed and other metadata processing is done. 52 | 53 | Eventually, it reaches TCP stack: 54 | - TCP checksum verified 55 | - Handles the TCP state machine 56 | - Enqueues the data to socket's recevive queue 57 | - Signals the fd that the data is available (for processes sleeping on `select`) 58 | 59 | On Socket read (by user process): 60 | - Dequeue data from socket's receive queue 61 | - Copy to user buffer 62 | - Release the `sk_buf` 63 | -------------------------------------------------------------------------------- /notes/padding_packing.md: -------------------------------------------------------------------------------- 1 | ## The Lost Art of Structure Packing & Unaligned Memory Accesses 2 | 3 | [Padding and Packing](http://www.catb.org/esr/structure-packing/) 4 | 5 | [Memory Alignment](https://docs.kernel.org/core-api/unaligned-memory-access.html) 6 | 7 | Unaligned memory accesses occur when you try to read `N` bytes of data starting 8 | from an address that is not evenly divisible by `N` (i.e. `addr % N != 0`). 9 | 10 | For example, reading 4 bytes of data from address `0x10004` is fine, but reading 11 | 4 bytes of data from address `0x10005` would be an unaligned memory access. 12 | 13 | `Natural Alignment`: When accessing `N bytes` of memory, the base memory address 14 | must be evenly divisible by `N`, i.e. `addr % N == 0`. 15 | 16 | *When writing code, assume the target architecture has natural alignment requirements.* 17 | 18 | ### Why unaligned access is bad 19 | 20 | The effects of performing an unaligned memory access vary from architecture 21 | to architecture. A summary of the common scenarios is presented below: 22 | 23 | - Some architectures are able to perform unaligned memory accesses transparently, 24 | but there is usually a significant performance cost. 25 | 26 | - Some architectures raise processor exceptions when unaligned accesses happen. 27 | The exception handler is able to correct the unaligned access, at significant 28 | cost to performance. 29 | 30 | - Some architectures raise processor exceptions when unaligned accesses happen, 31 | but the exceptions do not contain enough information for the unaligned access to 32 | be corrected. 33 | 34 | - Some architectures are not capable of unaligned memory access, but will 35 | silently perform a different memory access to the one that was requested, 36 | resulting in a subtle code bug that is hard to detect! 37 | 38 | If our code causes unaligned memory accesses to happen, out code will not work 39 | correctly on certain platforms and will cause performance problems on others. 40 | 41 | ### How Compiler helps 42 | 43 | The way our compiler lays out basic datatypes in memory is constrained in order 44 | to make memory accesses faster. 45 | 46 | *Each type except `char` has an alignment requirement: `chars` can start on any 47 | byte address, but `2-byte shorts` must start on an even address, `4-byte ints` 48 | or `floats` must start on an address divisible by 4, and `8-byte longs` or 49 | `doubles` must start on an address divisible by 8. 50 | Signed or unsigned makes no difference.* 51 | 52 | Self-alignment makes access faster because it facilitates generating 53 | single-instruction fetches and puts of the typed data. Without alignment 54 | constraints, on the other hand, the code might end up having to do two or more 55 | accesses spanning machine-word boundaries. 56 | 57 | Characters are a special case: they’re equally expensive from anywhere they 58 | live inside a single machine word. That’s why they don’t have a preferred alignment. 59 | 60 | ```cpp 61 | // consider these variables declaration 62 | char *p; 63 | char c; 64 | int x; 65 | 66 | // actual layout in memory 67 | char *p; /* 4 or 8 bytes */ 68 | char c; /* 1 byte */ 69 | char pad[3]; /* 3 bytes */ 70 | int x; /* 4 bytes */ 71 | ``` 72 | 73 | ```cpp 74 | // consider these variables declaration 75 | char *p; 76 | char c; 77 | long x; 78 | 79 | // actual layout in memory 80 | char *p; /* 8 bytes */ 81 | char c; /* 1 byte */ 82 | char pad[7]; /* 7 bytes */ 83 | long x; /* 8 bytes */ 84 | ``` 85 | 86 | ![](../assets/pad.png) 87 | 88 | #### Structure alignment and padding 89 | 90 | ```cpp 91 | struct foo1 { 92 | char *p; 93 | char c; 94 | long x; 95 | }; 96 | 97 | // Assuming a 64-bit machine, any instance of struct foo1 will have 8-byte alignment. 98 | 99 | struct foo1 { 100 | char *p; /* 8 bytes */ 101 | char c; /* 1 byte 102 | char pad[7]; /* 7 bytes */ 103 | long x; /* 8 bytes */ 104 | }; 105 | 106 | ``` 107 | 108 | ```cpp 109 | struct foo5 { 110 | char c; 111 | struct foo5_inner { 112 | char *p; 113 | short x; 114 | } inner; 115 | }; 116 | 117 | // The char *p member in the inner struct forces the outer struct to be pointer-aligned as well as the inner. 118 | 119 | struct foo5 { 120 | char c; /* 1 byte*/ 121 | char pad1[7]; /* 7 bytes */ 122 | struct foo5_inner { 123 | char *p; /* 8 bytes */ 124 | short x; /* 2 bytes */ 125 | char pad2[6]; /* 6 bytes */ 126 | } inner; 127 | }; 128 | ``` 129 | 130 | In the below example, we can observe that padding is even added at the end, for complete alignment (in case we 131 | have array of structs). Even if we don't have an array, we will have this padding: 132 | ```cpp 133 | struct mystruct_A { 134 | char a; 135 | char pad1[3]; /* inserted by compiler: for alignment of b */ 136 | int b; 137 | char c; 138 | char pad2[3]; /* -"-: for alignment of the whole struct in an array */ 139 | } x; 140 | ``` 141 | 142 | Now that we know how and why compilers insert padding in and after our structures 143 | we’ll examine what we can do to squeeze out the slop. 144 | This is the art of structure packing. 145 | 146 | The first thing to notice is that slop only happens in two places: 147 | - One is where storage bound to a larger data type (with stricter alignment 148 | requirements) follows storage bound to a smaller one. 149 | - The other is where a struct naturally ends before its stride address, requiring 150 | padding so the next one will be properly aligned. 151 | 152 | The simplest way to eliminate slop is to reorder the structure members by 153 | decreasing alignment. 154 | 155 | That is: make all the pointer-aligned subfields come first, because on a 64-bit 156 | machine they will be 8 bytes. Then the 4-byte ints; then the 2-byte shorts; 157 | then the character fields. 158 | 159 | #### Overriding Alignment Rules 160 | 161 | We can ask our compiler to not use the processor’s normal alignment rules by 162 | using a pragma, usually `#pragma pack`. 163 | 164 | *Do not do this casually, as it forces the generation of more expensive and slower code.* 165 | 166 | ```cpp 167 | #pragma pack(1) // Force 1-byte alignment 168 | struct PackedExample { 169 | char a; // 1 byte 170 | int b; // 4 bytes 171 | }; 172 | 173 | // Here, b is no longer aligned on a 4-byte boundary. 174 | // It forces the CPU to perform unaligned memory accesses. 175 | 176 | #pragma pack() // Reset to default alignment 177 | ``` 178 | 179 | ### Endianness: Big Endian and Little Endian 180 | It specifies the order in which bytes of a word are stored in memory. 181 | 182 | *`x86(32/64 bit)` is `little-endian`.* 183 | 184 | *While `ARM` defaults to `little-endian`, it can be configured to operate in 185 | big-endian mode as well.* 186 | 187 | (Big-Endian is actually more intuitive at first sight!) 188 | 189 | ![](../assets/endian_1.png) 190 | ![](../assets/endian_2.png) 191 | -------------------------------------------------------------------------------- /notes/performance.md: -------------------------------------------------------------------------------- 1 | ## Follow these guidelines 2 | 3 | 1. No unnecessary work 4 | - No extra copying 5 | - No extra allocations 6 | 7 | 2. Use all your computing power 8 | - Use all the cores available 9 | - SIMD 10 | 11 | 3. Avoid waits and stalls 12 | - Lockless Data Structures 13 | - Async APIs 14 | - Job Systems 15 | 16 | 4. Use Hardware efficiently 17 | - Cache friendly 18 | - Well predictable code 19 | 20 | 5. OS Level Efficiency 21 | 22 | ## Efficient C++ 23 | 24 | ### Use constexpr wherever possible 25 | Computation already done at compile time, saving us time during runtime. 26 | 27 | ### Make Variables `const` 28 | Knowing a variable is const allows the compiler to perform various 29 | optimisations. 30 | 31 | For example: 32 | ```cpp 33 | { 34 | const float sum = std:: accumulate(data.begin(), data.end(),0.01); 35 | for (auto& num : data) { 36 | num -= sum / data.size(): 37 | } 38 | return data; 39 | } 40 | 41 | // can be optimised to below code by compiler since sum is const 42 | 43 | { 44 | const float sum = std:: accumulate(data.begin(), data.end(),0.01); 45 | const float __mean = sum / data.size(): // this expression is loop invariant 46 | for (auto& num : data) { 47 | num -= __mean; // no expensive division inside loop now 48 | } 49 | return data; 50 | } 51 | ``` 52 | 53 | Also it can help compiler to do this optimization: 54 | 55 | ```cpp 56 | bool condition = getBool(); 57 | for (int i {}; i < n; i++) { 58 | if (condition) { 59 | A(i); 60 | } else { 61 | B(i); 62 | } 63 | } 64 | 65 | // if variable was declared const: 66 | 67 | const bool condition = getBool(); 68 | if (condition()) { 69 | for (int i = 0; i < n; i++) { 70 | A(i); 71 | } 72 | } else { 73 | for (int i = 0; i < n; i++) { 74 | B(i); 75 | } 76 | } 77 | ``` 78 | Here we have reduced branching. In earlier case when bool was not const, compiler 79 | may be afraid that functions A or B, might change its value, thus preventing the 80 | opportunity to optimise. 81 | 82 | ### Noexcept all the things 83 | void f(); `could throw exception` 84 | 85 | void f() noexcept; `WILL NEVER throw an exception` 86 | 87 | In the functions call stack, the compiler can now not do exception handling, 88 | which reduces some overhead. 89 | 90 | ### Use static for internal linkage 91 | The functions which are to be used only in this source file should be marked 92 | static. It is another hint to the compiler to inline it, apart from using the 93 | inline keyword. 94 | 95 | ### Use `[[likely]]` and `[[unlikely]]` in conditionals 96 | Better branch predicting, if the conditionals are marked `[[likely]]` or 97 | `[[unlikely]]`. 98 | 99 | ### Avoid Copying in str. bindings 100 | 101 | `auto [first person, age] = *map. begin();` is bad 102 | 103 | `const auto& [first person, age] = *map.begin();` use this instead 104 | 105 | ### Cache Friendly 106 | 107 | ```plaintext 108 | CONTIGUOUS SCATTERED 109 | 110 | std::array std::list 111 | std::vector std::set 112 | std::deque std::unordered_set 113 | std::flat_map std::map 114 | std::flat_set std::unordered_map 115 | ``` 116 | 117 | Also while designing classes, we have certain member varibles which will be 118 | used very rarely, for example debugging info. 119 | 120 | So instead of having an object of `debugInfo` inside our class, we can instead 121 | keep a `unique_ptr` to it. So that everytime our class' object is fetched into 122 | cache, large non-important things are not pre-fetched (size of pointer will be 123 | small, and small non-important thing will be feteched here.) 124 | 125 | ### False Sharing 126 | Data on same cache line, being accessed by different threads. 127 | 128 | Use `alignas` to prevent this. 129 | 130 | ### Avoid Indirect Calls 131 | Virtual function call are indirect calls, as they require `vtable` lookup. 132 | -------------------------------------------------------------------------------- /notes/placement_new.md: -------------------------------------------------------------------------------- 1 | ## Placement New in C++ 2 | 3 | - Allocation and construction are different. 4 | - A memory allocator is simply supposed to return uninitialized bits of memory. 5 | - It is not supposed to produce “objects”. 6 | - Constructing the object is role of the constructor, which runs after the 7 | memory allocator. 8 | 9 | ```cpp 10 | // assume we have a allocator object pool 11 | Pool pool; 12 | void* raw = pool.alloc(sizeof(Foo)); 13 | Foo* p = new(raw) Foo(); 14 | 15 | // the above is equivalent to 16 | Foo* p = new Foo(); 17 | ``` 18 | - It is used to place an object at a particular location in memory. This is 19 | done by supplying the place as a pointer parameter to the new part of a new 20 | expression: 21 | 22 | ```cpp 23 | #include // Must #include this to use "placement new" 24 | int main () 25 | { 26 | char memory[sizeof(Fred)]; // creating memory big enough to hold fred 27 | void* place = memory; // unnecessary step 28 | Fred* f = new(place) Fred(); // Contructing the object (call Fred::Fred()) 29 | // The pointers f and place will be equal 30 | 31 | // Remark: 32 | // We are taking sole responsibility that the pointer we pass to the 33 | // “placement new” operator points to a region of memory that is big enough 34 | // and is properly aligned for the object type that you’re creating. 35 | 36 | // Neither the compiler nor the run-time system will make any attempt to check 37 | // whether we did this right. 38 | 39 | f->~Fred(); 40 | // We need to explicitly call the destructor 41 | } 42 | ``` 43 | 44 | - We may want to do this for optimization when we need to construct multiple 45 | instances of an object, and it is faster not to re-allocate memory each time 46 | we need a new instance. Instead, it might be more efficient to perform a 47 | single allocation for a chunk of memory that can hold multiple objects, 48 | even though we don't want to use all of it at once. 49 | 50 | ### How std::vector uses Placement New 51 | 52 | - Take containers like unordered_map, vector, or deque. These all allocate 53 | more memory than is minimally required for the elements you've inserted so 54 | far to avoid requiring a heap allocation for every single insertion. 55 | 56 | ```cpp 57 | vector vec; 58 | 59 | // Allocate memory for a thousand Foos: 60 | vec.reserve(1000); 61 | ``` 62 | ... that doesn't actually construct a thousand Foos. It simply allocates/reserves 63 | memory for them. If vector did not use placement new here, it would be 64 | default-constructing Foos all over the place as well as having to invoke their 65 | destructors even for elements you never even inserted in the first place. 66 | 67 | Vector Example: [Link](https://medium.com/@dgodfrey206/c-placement-new-1298ccbb076e) 68 | -------------------------------------------------------------------------------- /notes/pre-post-increment.md: -------------------------------------------------------------------------------- 1 | ## `i++` vs `++i` 2 | 3 | First lets see for our class how can we overload, the ++prefix and postfix++ 4 | operators. 5 | 6 | ```cpp 7 | class Number { 8 | public: 9 | Number& operator++ (); // ++prefix 10 | Number operator++ (int); // postfix++ 11 | }; 12 | ``` 13 | 14 | *Note the different return types: the prefix version returns by reference, the 15 | postfix version by value.* 16 | 17 | ```cpp 18 | Number& Number::operator++ () 19 | { 20 | // do some logic here to increment 21 | return *this; 22 | } 23 | 24 | Number Number::operator++ (int) 25 | { 26 | Number ans = *this; 27 | ++(*this); // or just call operator++() 28 | return ans; 29 | } 30 | ``` 31 | 32 | ### Which is more efficient: `i++` or `++i`? 33 | 34 | - `++i` is sometimes faster than, and is never slower than, `i++`. 35 | 36 | - For intrinsic types like `int`, it doesn’t matter: `++i` and `i++` are the 37 | same speed. For Number class (above example), `++i` very well might be faster 38 | than `i++` since the latter might make a copy of the this object. 39 | -------------------------------------------------------------------------------- /notes/program_to_process.md: -------------------------------------------------------------------------------- 1 | ## Story of the Program to a Process 2 | 3 | `Preprocessing` is the first pass of any C++ compilation. It processes 4 | include-files, conditional compilation instructions and macros. 5 | 6 | `Compilation` is the second pass. It takes the output of the 7 | preprocessor, and the source code, and generates assembly source code. 8 | 9 | `Assembler` is the third stage of compilation. It takes the assembly 10 | source code and produces machine code with offsets. The 11 | assembler output is stored in an object file. 12 | 13 | `Linking` is the final stage of compilation. It takes one or more 14 | object files or libraries as input and combines them to produce a 15 | single (usually executable) file. In doing so, it resolves references 16 | to external symbols, assigns final addresses to procedures/functions 17 | and variables, and revises code and data to reflect new addresses (a 18 | process called relocation). 19 | 20 | ![](/assets/compilation_steps.png) 21 | 22 | ### Object Files 23 | 24 | After the source code has been assembled, it will produce an Object 25 | files `(e.g. .o, .obj)` and then linked, producing an executable files. 26 | 27 | An object and executable come in several formats such as `ELF` 28 | (Executable and Linking Format) and `COFF` (Common Object-File Format). 29 | For example, ELF is used on Linux systems, while COFF is used on 30 | Windows systems. 31 | 32 | The object file contains various areas called sections. These sections can 33 | hold executable code, data, dynamic linking information, debugging data, 34 | symbol tables, relocation information, comments, string tables, and notes. 35 | 36 | Some sections are loaded into the process image and some provide 37 | information needed in the building of a process image while still others 38 | are used only in linking object files. 39 | 40 | ![](../assets/sections.png) 41 | 42 | `readelf` and `objdump` can be use to read headers and content of an object 43 | file. 44 | 45 | ### Relocation Records: 46 | Because the various object files will include references to each other's code 47 | and/or data, these shall be combined during the link time. 48 | 49 | For example, the object file that has main() includes calls to 50 | function printf(). 51 | 52 | After linking all of the object files together, the linker uses the 53 | relocation records to find all of the addresses that need to be filled in. 54 | 55 | Each object file has a symbol table that contains a list of names and their 56 | corresponding offsets in the text and data segments. 57 | 58 | ![](../assets/linking.png) 59 | 60 | ### Shared Libraries 61 | 62 | - In a typical system, a number of programs will be running. Each program 63 | relies on a number of functions, some of which will be standard C library 64 | functions, like `printf()`, `malloc()`, `strcpy()`, etc. and some are 65 | non-standard or user defined functions. 66 | 67 | - If every program uses the standard C library, it means that each program 68 | would normally have a unique copy of this particular library present within 69 | it. Unfortunately, this result in wasted resources, degrade the efficiency 70 | and performance. 71 | 72 | - Since the C library is common, it is better to have each program reference 73 | the common, one instance of that library, instead of having each program 74 | contain a copy of the library. 75 | 76 | - This is implemented during the linking process where some of the objects are linked during the link time whereas some done during the run time 77 | (Dyanimic Linking). 78 | 79 | #### Static Linking 80 | - The term `statically linked` means that the program and the particular 81 | library that it’s linked against are combined together by the linker at link 82 | time. 83 | 84 | - Programs that are linked statically are linked against archives of objects 85 | (libraries) that typically have the extension of `.a`. An example of such a 86 | collection of objects is the standard C library, `libc.a`. 87 | 88 | - You might consider linking a program statically for example, in cases 89 | where you weren't sure whether the correct version of a library will be 90 | available at runtime, or if you were testing a new version of a library that 91 | you don't yet want to install as shared. 92 | 93 | - The drawback of this technique is that the executable is quite big in 94 | size, as all the needed information need to be brought together. 95 | 96 | #### Dynamic Linking 97 | - The term `dynamically linked` means that the program and the particular library it references are not combined together by the linker at link time. 98 | 99 | - Instead, the linker places information into the ELF that tells the 100 | loader which `shared object module` the code is in and which 101 | `runtime linker` should be used to find and bind the references. 102 | 103 | - This type of program is called a partially bound executable, because it 104 | isn't fully resolved. The linker, at link time, didn't cause all the 105 | referenced symbols in the program to be associated with specific code from 106 | the library. Instead, the linker simply said something like: “This 107 | program calls some functions within a particular shared object, so I'll just 108 | make a note of which shared object these functions are in, and continue on”. 109 | 110 | - The binding between the program and the shared object is done at runtime 111 | that is after the program starts , the appropriate shared 112 | objects are found and bound. 113 | 114 | - Programs that are linked dynamically are linked against shared objects 115 | that have the extension `.so`. An example of such an object is the shared 116 | object version of the standard C library, `libc.so`. 117 | 118 | - Some advantages: 119 | - Program files (on disk) become much smaller because they need not hold 120 | all necessary text and data segments information. 121 | 122 | - Dynamic linking permits two or more processes to share read-only 123 | executable modules such as standard C libraries. Using this technique, 124 | only one copy of a module needs be resident in memory at any time, and 125 | multiple processes, each can executes this shared code (read only). 126 | This results in a considerable memory saving. 127 | 128 | ### Process Loading 129 | 130 | 1. In Linux processes loaded from a file system (using either the 131 | `execve()` or `spawn()` system calls) are in `ELF` format. 132 | 133 | 2. Before we can run an executable, firstly we have to load it into memory. 134 | 135 | 3. This is done by the loader, which is generally part of the operating system. 136 | 137 | 4. Memory and access validation: Firstly, the OS system kernel reads in the 138 | program file’s header information and does the validation for type, access 139 | permissions, memory requirement and its ability to run its instructions. It 140 | confirms that file is an executable image and calculates memory requirements. 141 | 142 | - Allocates primary memory for the program's execution. 143 | - Copies address space from secondary to primary memory. 144 | - Copies the `.text` and `.data` sections from the executable into primary 145 | memory. 146 | - Copies program arguments (e.g., command line arguments) onto the stack. 147 | - Initializes registers: sets the esp (stack pointer) to point to top of 148 | stack, clears the rest. 149 | - Jumps to `__start` routine, which: copies main()'s arguments off of the 150 | stack, and jumps to `main()`. 151 | 152 | 5. The memory layout, consists of three segments (text, data, and stack). 153 | The dynamic data segment is also referred to as the heap, the place dynamically 154 | allocated memory (such as from `malloc()` and `new()`) comes from. Dynamically 155 | allocated memory is memory allocated at run time instead of compile/link time. 156 | 157 | 158 | ### Runtime Linking 159 | 160 | 1. The runtime linker is invoked when a program that was linked against a 161 | shared object requests that a shared object be dynamically loaded. 162 | 163 | 2. Run-time dynamic linking: The application program is read from disk (ELF 164 | file) into memory and unresolved references are left as invalid (typically 165 | zero). The first access of an invalid, unresolved, reference results in a 166 | software trap. The run-time dynamic linker determines why this trap occurred 167 | and seeks the necessary external symbol. Only this symbol is loaded into 168 | memory and linked into the calling program. 169 | 170 | 3. The runtime linker is contained within the C runtime library. The runtime 171 | linker performs several tasks when loading a shared library (`.so` file). 172 | 173 | 4. For resolving a symbol at runtime the runtime linker will search through the 174 | list of libraries for this symbol. In ELF files, hash tables are used for the 175 | symbol lookup, so they're very fast. 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /notes/rvo.md: -------------------------------------------------------------------------------- 1 | ## Return Value Optimisation 2 | 3 | The object to be returned is constructed in the return value slot. 4 | Expensive copying is avoided in this case. Otherwise the object would be first 5 | created in local stack and then copied into the return slot (copying involved). 6 | 7 | Good Video by Arch Coffee: https://www.youtube.com/watch?v=Qp_XA8G5H3M 8 | 9 | ![](../assets/rvo.png) 10 | 11 | *Here we can see that the un-named object S{} is directly created in the return 12 | value slot. 13 | We have avoided unnecessary copying.* 14 | 15 | ![](../assets/no-rvo.png) 16 | 17 | *In this case the compiler doesn't know which among `s1`, `s2` needs to be returned. 18 | Hence it can't directly constuct the object in the return slot. It first creates 19 | both the objects in the stack and then based on the conditional test, copies one 20 | of them into the return value slot.* 21 | 22 | ### Understanding stack segment while function call 23 | 24 | This is how the stack looks like. First of all we have the return slot. 25 | Then the arguments to the function and finally the return address. 26 | Stack pointer points below this at start of first instuction. 27 | 28 | ```cpp 29 | Fruit apples_to_apples (int i, Fruit x, int j) { 30 | return x 31 | } 32 | ``` 33 | The return slot is allocated by the `caller` itself. And its address is passed to 34 | the `callee` via the `rdi` register (this is a hidden parameter in this case). 35 | 36 | In case of RVO, the returned object will be constructed in this slot, otherwise it would 37 | be created locally in stack and then copied here. 38 | 39 | 40 | ![](../assets/stack.png) 41 | 42 | Here we can't elide copy, since the stack address of Fruit x and the return 43 | slot are different. We must get data out of `x` and put it into the return slot. 44 | 45 | ### Slicing from derived to base 46 | 47 | ```cpp 48 | struct Cat : Aniaml { 49 | int rats_eaten; 50 | } 51 | 52 | Animal chopped() { 53 | Cat x = ...; 54 | return x; 55 | } 56 | ``` 57 | 58 | In this case we do control the physical location of x (where we can constuct it), 59 | but x is of the wrong type for constructing into the return slot. 60 | (*Slot of Animal return would be smaller size than Cat object size*). 61 | 62 | In these cases, where return type is Base and the object returned is Derived, 63 | the extra properties of the derived object is sliced away. We will only be 64 | returning the Animal part now. 65 | (*To avoid this, we use pointers. Run-Time Polymorphism*) 66 | -------------------------------------------------------------------------------- /notes/set_pq.md: -------------------------------------------------------------------------------- 1 | ## Set vs Priority Queue 2 | 3 | ### Priority Queue 4 | - Only gives access to one element in sorted order: the highest priority 5 | element. When we remove it, we get the next highest priority element. 6 | - It is backed by a heap (implemented by a vector) 7 | - In a heap `P < L` and `P < R` (Parent, Left, Right) 8 | - The highest priority element is at top of tree (or front of vector) 9 | - O(1) access to top element 10 | - Deletion is `O(logn)` (We replace the top of tree with the extreme element 11 | and then perform swapping to maintain heap property) 12 | - Insertion is `O(logn)` (We put new element at the new extreme and then perform 13 | swapping to maintain heap property) 14 | - One point to note, is that operations in PQ involve a lot of swapping of 15 | elements. 16 | 17 | 18 | ### Set 19 | - A set allows you full access in sorted order. 20 | - We can do: find two elements somewhere in the middle of the set, then 21 | traverse in order from one to the other. 22 | - Insert any element `O(log n)` and the constant factor is greater than in PQ. 23 | - Much more operations (LB, UB, element lookup, iteration, etc). 24 | - Backed by self balancing BSTs. 25 | - In a binary tree `L < P < R`. 26 | - Insert and erase operations slightly slower than PQ because `std::set` makes 27 | many memory allocations. Every element of `std::set` is stored at its own 28 | allocation. 29 | - Good thing is it only involves pointer swapping. -------------------------------------------------------------------------------- /notes/smart_pointers.md: -------------------------------------------------------------------------------- 1 | ## Smart Pointers 2 | 3 | - Unique Pointers 4 | - Shared Pointers 5 | - Weak Pointers 6 | 7 | Syntax of usage is similar as before: Due to operator overloading usage remains 8 | same 9 | ```cpp 10 | std::shared_ptr p = std::make_shared("Hello"); 11 | auto q = p; 12 | p = nullptr; 13 | if (q != nullptr) { 14 | std::cout << q->length() << *q << '\n'; 15 | } 16 | ``` 17 | 18 | ```cpp 19 | { 20 | T* ptr = new T; 21 | // ... 22 | delete ptr; 23 | // its programmers responsibility to delete this pointer after usage. 24 | // otherwise there will be memory leak 25 | 26 | template 27 | class uniqute_ptr { 28 | T* p_ = nullptr; 29 | 30 | ~unique_ptr() { 31 | delete p_; // deletion done automatically upon destruction 32 | } 33 | } 34 | } 35 | ``` 36 | 37 | - A raw pointer `T*` is copyable. If I copy, then which of us has now the 38 | ownership. Who holds the responisibility of cleaning up? Both can't clear. 39 | 40 | - Unique pointer is not copyable, it is only movable. When the move from A to B, 41 | the move constructor nulls out the source pointer (maintains unique ownsership). 42 | 43 | ```cpp 44 | // unique pointer is always a template of two parameters. 45 | // second parameter is defaulted to std::default_delete 46 | 47 | template> 48 | class unique_ptr { 49 | T* p_ = nullptr; 50 | Deleter d_; 51 | 52 | ~unique_ptr() { 53 | if (p_) d_(p_); // called deleter on this pointer 54 | } 55 | }; 56 | 57 | template 58 | struct default_delete { 59 | void operator()(T *p) comst { 60 | delete p; 61 | } 62 | } 63 | 64 | // now we can use this to do some nice things 65 | 66 | struct FileCloser { 67 | void operator() (FILE *fp) const { 68 | assert (fp != nullptr); 69 | fclose(fp); // instead of delete we call close 70 | } 71 | } 72 | 73 | FILE *fp = fopen("input.txt", "r"); 74 | std::unique_ptr uptr(fp); 75 | ``` 76 | 77 | #### Rule of thumb for smart pointers 78 | - Treat smart pointer just like raw pointer types 79 | - Pass by value! 80 | - Return by value (of course)! 81 | - Passing a pointer by reference 82 | - A function taking a unique_ptr by value shows transfer of ownership 83 | 84 | ```cpp 85 | #include 86 | #include 87 | 88 | class MyClass { 89 | public: 90 | MyClass() { std::cout << "MyClass constructor\n"; } 91 | ~MyClass() { std::cout << "MyClass destructor\n"; } 92 | void show() { std::cout << "Hello from MyClass\n"; } 93 | }; 94 | 95 | // Function that takes unique_ptr by value (transfers ownership) 96 | void takeOwnership(std::unique_ptr ptr) { 97 | std::cout << "Taking ownership\n"; 98 | ptr->show(); 99 | } 100 | 101 | int main() { 102 | std::unique_ptr myPtr = std::make_unique(); 103 | 104 | // Pass unique_ptr by value to the function, transferring ownership 105 | takeOwnership(std::move(myPtr)); // myPtr is moved 106 | 107 | // At this point, myPtr is no longer valid 108 | if (!myPtr) { 109 | std::cout << "myPtr is now null\n"; 110 | } 111 | 112 | return 0; 113 | } 114 | ``` 115 | #### Shared Pointer 116 | - syntax similar to the unique_ptr 117 | - It expresses shared ownsership. Reference counting. 118 | 119 | ![](../assets/sharedPtr.png) 120 | ![](../assets/class.png) 121 | 122 | `F`, `V` are base classes. `T` is a derived class. Pointers of base class pointing 123 | to object of derived class. Both will be pointing to a different offset in the 124 | heap allocated object. 125 | 126 | ![](../assets/sharedPtr2.png) 127 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /notes/templates.md: -------------------------------------------------------------------------------- 1 | ## C++ Template 2 | 3 | Template is not a thing: Its a recipe for making things. 4 | 5 | ### Function Templates 6 | These are the recipes for making functions. 7 | ```cpp 8 | template 9 | T const& min(T const& a, T const& b) { 10 | return (a < b) ? a : b; 11 | } 12 | 13 | template(class RandomIt, class Compare) 14 | void sort(RandomIt first, RandomIt last, Compare comp); 15 | ``` 16 | ### Class Templates 17 | These are the recipes for making classes 18 | ```cpp 19 | template 20 | struct array { 21 | ... 22 | } 23 | ``` 24 | ### Alias Templates (C++11) 25 | These are recipes for making type aliases. 26 | ```cpp 27 | template 28 | using my_map = map>; 29 | 30 | my_map msi; 31 | ``` 32 | 33 | *If some `if/else` decision can be taken at compile time, then there 34 | is no branching at run time, which is pretty efficient. 35 | Hence try to take more and more decisions at compile time.* 36 | 37 | We put the `recipe` in the header files and include these header files 38 | in all those source files where need to instantiate. 39 | The compiler needs to see the recipe for making actual things. 40 | 41 | These template definitions (recipe) are treated as `inline`. 42 | 43 | `inline`: inline function and variables can have multiple definitions 44 | across different translation units (All definitions of the inline function must be identical across all translation units). 45 | 46 | In simple words, it means that we can place declaration and definition 47 | inside the header file and then include this header file in different 48 | source files. This way we can have multiple definitions but each 49 | definition is identical. 50 | 51 | #### Concepts 52 | We can use them to put some constraints for generic code. 53 | Because templates are just recipes, they are only instantiated when someone calls 54 | them. So compiler will only generate actual thing upon being called. 55 | 56 | Could be used when we need to check if certain templated function has `push_back` 57 | inside it and we don't call it with `set` container. 58 | ```cpp 59 | template 60 | concept HasPushBack = requires (Coll c, Coll::value_type v) { 61 | c.push_back(v); 62 | }; 63 | // this just checks that would calling push_back be valid or not? 64 | ``` 65 | ![](../assets/concepts.png) 66 | 67 | 68 | #### Variadic Templates 69 | 70 | ```cpp 71 | #include 72 | 73 | // Base case: This function is called when there's only one argument left 74 | template 75 | T sum(T value) { 76 | return value; 77 | } 78 | 79 | // Variadic template: This function is called with two or more arguments 80 | template 81 | T sum(T first, Args... args) { 82 | return first + sum(args...); // Recursively call sum with the remaining arguments 83 | } 84 | 85 | int main() { 86 | std::cout << "Sum of 1, 2, 3, 4, 5: " << sum(1, 2, 3, 4, 5) << std::endl; 87 | std::cout << "Sum of 1.5, 2.5, 3.5: " << sum(1.5, 2.5, 3.5) << std::endl; 88 | std::cout << "Sum of 10: " << sum(10) << std::endl; // Single argument case 89 | return 0; 90 | } 91 | ``` 92 | 93 | ### Compile time prime checker 94 | 95 | But there is a limit in maximum depth of template class instantiation in compiler. 96 | 97 | 98 | ```cpp 99 | #include 100 | 101 | // Base case: Primary template to check divisibility 102 | template 103 | struct IsPrimeHelper { 104 | static constexpr bool value = (num % divisor != 0) && IsPrimeHelper::value; 105 | }; 106 | 107 | // Specialization for the base case when divisor reaches 1 108 | template 109 | struct IsPrimeHelper { 110 | static constexpr bool value = true; 111 | }; 112 | 113 | // Specialization to handle numbers less than 2 (not prime) 114 | template<> 115 | struct IsPrimeHelper<1, 1> { 116 | static constexpr bool value = false; 117 | }; 118 | 119 | // Main template to check if a number is prime 120 | template 121 | struct IsPrime { 122 | // The helper is instantiated with num and num / 2 as the starting divisor 123 | static constexpr bool value = (num > 1) && IsPrimeHelper::value; 124 | }; 125 | 126 | int main() { 127 | // Check if 29 is prime 128 | constexpr int number1 = 29; 129 | constexpr bool isNumber1Prime = IsPrime::value; 130 | std::cout << number1 << (isNumber1Prime ? " is prime." : " is not prime.") << std::endl; 131 | 132 | // Check if 10 is prime 133 | constexpr int number2 = 10; 134 | constexpr bool isNumber2Prime = IsPrime::value; 135 | std::cout << number2 << (isNumber2Prime ? " is prime." : " is not prime.") << std::endl; 136 | 137 | return 0; 138 | } 139 | ``` 140 | 141 | ```cpp 142 | #include 143 | 144 | // Helper function to check for divisibility at compile time 145 | constexpr bool isDivisible(int num, int divisor) { 146 | return (num % divisor == 0); 147 | } 148 | 149 | // Recursive constexpr function to check if a number is prime 150 | constexpr bool isPrimeHelper(int num, int divisor) { 151 | // Base case: If divisor is 1, it's prime 152 | if (divisor == 1) return true; 153 | // If the number is divisible by the current divisor, it's not prime 154 | if (isDivisible(num, divisor)) return false; 155 | // Recursively check for next divisor 156 | return isPrimeHelper(num, divisor - 1); 157 | } 158 | 159 | // Main constexpr function to check primality 160 | constexpr bool isPrime(int num) { 161 | // Handle special cases for numbers less than 2 162 | return (num > 1) && isPrimeHelper(num, num / 2); 163 | } 164 | 165 | int main() { 166 | // Test the compile-time prime checker 167 | constexpr int number1 = 29; 168 | constexpr int number2 = 10; 169 | 170 | // These conditions are evaluated at compile time 171 | if constexpr (isPrime(number1)) { 172 | std::cout << number1 << " is prime." << std::endl; 173 | } else { 174 | std::cout << number1 << " is not prime." << std::endl; 175 | } 176 | 177 | if constexpr (isPrime(number2)) { 178 | std::cout << number2 << " is prime." << std::endl; 179 | } else { 180 | std::cout << number2 << " is not prime." << std::endl; 181 | } 182 | 183 | return 0; 184 | } 185 | ``` -------------------------------------------------------------------------------- /notes/tmux.md: -------------------------------------------------------------------------------- 1 | ## Tmux cheat sheet 2 | 3 | By default the prefix key is Ctrl + B, I have changed it to Ctrl + Space 4 | 5 | Access my tmux config at [link](https://github.com/Shivam5022/.dotfiles/blob/main/tmux.conf) 6 | 7 | Some of the commands below are as per my configuration, which is different from the default ones. 8 | 9 | ### Sessions 10 | 11 | 1. Start a new session 12 | 13 | tmux new -s 14 | 15 | 2. Attach to a session (from outside tmux) 16 | 17 | tmux attach -t 18 | tmux a 19 | 20 | 3. Detach from a session 21 | 22 | Press , then d 23 | 24 | 4. List sessions 25 | 26 | tmux ls 27 | 28 | ### Windows 29 | 30 | We can have windows inside a session 31 | 32 | 1. Create a new window 33 | 34 | Press , then c 35 | 36 | 2. Rename the current window: 37 | 38 | Press , then , 39 | 40 | 3. Kill a window / session 41 | 42 | Press Ctrl+d 43 | 44 | 4. Switch between sessions and windows 45 | 46 | Press , then w 47 | Press , then 48 | Press , then p (previous window) 49 | Press , then n (next window) 50 | 51 | ### Panes 52 | 53 | In a window we can have multiple panes: 54 | 55 | 1. Split horizontal 56 | 57 | Press , then | 58 | 59 | 2. Split vertically 60 | 61 | Press , then _ 62 | 63 | 3. Switch between panes 64 | 65 | Press , then 66 | 67 | ### More 68 | 69 | 1. Rename session 70 | 71 | tmux rename-session -t 72 | 73 | 2. Kill all sessions 74 | 75 | tmux kill-server 76 | -------------------------------------------------------------------------------- /notes/vim.md: -------------------------------------------------------------------------------- 1 | ## NeoVim 2 | 3 | **Update:**: I have moved to [Helix](https://helix-editor.com). The config is available at same repo as below. 4 | (Personally found it better than nvim, because it doesn't require much configuration. It simply works.) 5 | 6 | My neovim configuration is available at [this link](https://github.com/Shivam5022/.dotfiles) 7 | 8 | Some resources to get started with Modal Editing: 9 | 10 | - The Primagen Youtube Playlist [Link](https://youtube.com/playlist?list=PLm323Lc7iSW_wuxqmKx_xxNtJC_hJbQ7R&si=cu_8_omQjZSTbiL7) 11 | - Vim motions tutorial [Link](https://youtu.be/IiwGbcd8S7I?si=xO5xlPMpo-Vn5hrA) 12 | - NeoVim configuration setup [Link](https://youtu.be/6pAG3BHurdM?si=2lm02xVFhozGPGFF) 13 | - Kickstart.nvim [Link](https://youtu.be/m8C0Cq9Uv9o?si=Bz17f3KxKFoxVaQc) 14 | -------------------------------------------------------------------------------- /notes/virtual_functions.md: -------------------------------------------------------------------------------- 1 | ## Virtual Functions and VTables 2 | 3 | Must read C++ FAQ Article: [Link](https://isocpp.org/wiki/faq/virtual-functions) 4 | 5 | - Used when we want to call member function of derived class by a pointer of 6 | base class. 7 | 8 | ```cpp 9 | class Base { 10 | public: 11 | Base() { 12 | std::cout << "Base Constructed" << std::endl; 13 | } 14 | ~ Base () { 15 | std::cout << "Base Destructed" << std::endl; 16 | } 17 | void func() { 18 | std::cout << "Base member function" << std::endl; 19 | } 20 | } 21 | 22 | class Derived : public Base { 23 | public: 24 | Derived() { 25 | std::cout << "Derived Constructed" << std::endl; 26 | } 27 | ~ Derived () { 28 | std::cout << "Derived Destructed" << std::endl; 29 | } 30 | void func() { 31 | std::cout << "Derived member function" << std::endl; 32 | } 33 | } 34 | 35 | int main () { 36 | Derived instance; 37 | instance.func(); 38 | 39 | // Base Constructed 40 | // Derived Constructed 41 | // Derived member function ---> derived will be printed 42 | // Derived Destructed 43 | // Base Destructed 44 | 45 | 46 | Base* ptr = new Derived; 47 | ptr->func(); 48 | delete ptr; 49 | 50 | // Base Constructed 51 | // Derived Constructed 52 | // Base member function ---> base will be printed 53 | // Base Destructed 54 | 55 | // 2 things to note: Base member function called, and Derived not destructed. 56 | 57 | } 58 | ``` 59 | 60 | In the above example if we wish that `func` of derived should be called then, we will have to mark the base function as `virtual` and the 61 | derived function should override that function. 62 | 63 | ```cpp 64 | virtual void func() {}; // in base class 65 | 66 | void func() override {}; // in derived class 67 | 68 | ptr->Base::func(); // to explicitly call base member function even after 69 | // declaring it virtual. 70 | ``` 71 | 72 | #### Both the destructors should be called! 73 | 74 | To ensure this, mark the destructor of base class as virtual. This will ensure 75 | that the destructor of derived class is also called, upon object destruction. 76 | 77 | `virtual ~Base() {}` 78 | 79 | ### How can C++ achieve dynamic binding yet also static typing? 80 | 81 | When you have a pointer to an object, the object may actually be of 82 | a class that is derived from the class of the pointer (e.g., a 83 | Vehicle* that is actually pointing to a Car object; this is called 84 | “polymorphism”). Thus there are two types: the (static) type of the 85 | pointer (Vehicle, in this case), and the (dynamic) type of the 86 | pointed-to object (Car, in this case). 87 | 88 | Static typing means that the legality of a member function 89 | invocation is checked at the earliest possible moment: by the 90 | compiler at compile time. The compiler uses the static type of the 91 | pointer to determine whether the member function invocation is 92 | legal. If the type of the pointer can handle the member function, 93 | certainly the pointed-to object can handle it as well. E.g., if 94 | Vehicle has a certain member function, certainly Car also has that 95 | member function since Car is a kind-of Vehicle. 96 | 97 | Dynamic binding means that the address of the code in a member 98 | function invocation is determined at the last possible moment: 99 | based on the dynamic type of the object at run time. It is called 100 | “dynamic binding” because the binding to the code that actually 101 | gets called is accomplished dynamically (at run time). Dynamic 102 | binding is a result of virtual functions. 103 | 104 | ### Virtual Table (vTable) - Supports Dynamic Dispatch 105 | 106 | To infer which function will be called, when we have some scenario like: 107 | 108 | ```cpp 109 | base* ptr = &derived; 110 | ptr->function(); 111 | ``` 112 | 113 | - If `function()` is not virtual: 114 | - we will have `early binding` done during compile time only. The function 115 | corresponding to the pointer type (here `base`) will be called. 116 | - If `function()` is virtual: 117 | - we will have `late binding` done during the run time. We will *use `vtable` 118 | of the `derived class`* to fetch the appropriate function. If this function is 119 | overridden in the derived class then the vtable will point to this overridden 120 | function. Otherwise, if we have not overridden this function in the derived 121 | class, then the vtable will point to the function of base class (since all the 122 | base class function are inherited by the derived class). 123 | - Hence: 124 | - If overridden: call the overridden function of derived class. 125 | - Else call the function of base class. 126 | 127 | [PDF version](../assets/Vtables.pdf) 128 | 129 | ![](../assets/vtables.png) 130 | 131 | ## Virtual Functions in Constructor and Destructor 132 | 133 | ![](../assets/constructor.png) 134 | 135 | When the constructor of base class is being invoked, the derived class has yet 136 | not been created: hence the function corresponding to base will be called. 137 | 138 | Same is with destructor. The derived class has already been destroyed, and hence 139 | the function corresponding to base will be called. 140 | 141 | Therefore, don't do any fancy things inside constructor/destructors which 142 | involve invoking virtual functions. 143 | 144 | --------------------------------------------------------------------------------