Preprocessor Stringification and Concatenation
The # dircetive transforms macro arguments into string literals during compilation. It must precede a parameter name within a parameterized macro definition.
#define DEBUG_IDENTIFIER(var) std::printf("[Check] %s evaluated\n", #var)
DEBUG_IDENTIFIER(sensor_temp);
The ## directive merges two preprocessing tokens into a single identifier. Whitespace surrrounding the operator is ignored.
#define CONFIG_REGISTER(channel) reg_ctrl_##channel
CONFIG_REGISTER(3) = 0x0F; // Expands to: reg_ctrl_3 = 0x0F;
The volatile Storage Qualifier
Applying volatile instructs the compiler to bypass optimizations that assume a variable's value remains stable between accesses. The memory location is read or written directly on every reference.
Thread Synchronization Flags
Shared flags modified by concurrent execution contexts require volatile to prevent cached register reads.
class TaskScheduler {
private:
volatile bool termination_signal = false;
public:
void requestStop() { termination_signal = true; }
bool isRunning() const { return !termination_signal; }
};
Hardware Memory Mapping Peripheral registers mapped into the address space must be marked volatile, as external hardware can alter their contents asynchronously.
volatile uint32_t* const uart_status_reg = reinterpret_cast<volatile uint32_t*>(0x40002004);
Asynchronous Signal Handling
Global variables updated inside signal handlers require volatile to guarantee visibility to the main execution flow.
#include <csignal>
#include <iostream>
volatile sig_atomic_t capture_event = 0;
void handle_interrupt(int sig) { capture_event = 1; }
int main() {
std::signal(SIGINT, handle_interrupt);
while (capture_event == 0) {
// Polling loop
}
std::cout << "Interrupt captured successfully\n";
return 0;
}
Storage Class static
Function Scope Persistence A static local variable is allocated in the data segment rather than the stack. It is initialized once and retains its value across subsequent invocations.
void track_invocations() {
static uint32_t call_counter = 0;
++call_counter;
}
Class-Level Shared Data Static class members belong to the type definition itself, not individual instances. All objects reference a single memory location.
class SensorNode {
public:
static inline int active_instances = 0;
void activate() { ++active_instances; }
};
Encapsulation via File Scope
Placing static before global variables or functions restricts their linkage to internal translation unit scope, preventing symbol conflicts during linking.
Initialization Behavior of Static Storage
Static variables reside in the .data or .bss memory segments. Because these segments are allocated for the entire runtime duration, initialization occurs exactly once during program startup or the first function call. Subsequent executions skip the initialization phase, preserving the previous state.
C and C++ Linkage Compatibility: extern "C"
C++ compilers apply name mangling to support function overloading, while C compilers use exact symbol names. Wrapping code in extern "C" disables mangling, ensuring cross-language symbol resolution during the linking phase.
Immutability with const
The const qualifier enforces read-only semantics.
Constant Variables: Must be initialized at declaration and cannot be reassigned.
Function Parameters: Prevents accidental modification of arguments within the function body.
Return Values: Returning const pointers restricts callers from modifying the pointed-to data. Applying const to primitive return types is redundant since rvalues are temporary copies.
const char* fetch_buffer();
const char* data = fetch_buffer(); // Caller cannot alter buffer content
Compile-Time Macros vs. Compiler Constants
Macros are processed by the preprocessor via text substitution, offering no type safety or debug visibility. const variables are evaluated by the compiler, enforce strict typing, consume memory (unless optimized), and respect standard scoping rules.
Dynamic Memory Allocation Comparison
| Feature | new / delete |
malloc / free |
|---|---|---|
| Type | C++ operators | C standard library functions |
| Type Safety | Returns exact pointer type | Returns void* |
| Initialization | Invokes constructors | Raw memory allocation only |
| Cleanup | Invokes destructors | Raw deallocation only |
| Failure Handling | Throws std::bad_alloc |
Returns nullptr |
| Array Support | new[] / delete[] handles sizing |
Manual byte calculation required |
String Measurement vs. Object Footprint
const char* sequence = "\0";
std::cout << strlen(sequence) << "\n"; // Output: 0
std::cout << sizeof(sequence) << "\n"; // Output: 2
strlen traverses memory until encountering the null terminator, returning the count of preceding characters. sizeof computes the compile-time byte footprint of the operand, including the terminating null character for string literals. sizeof is an operator yielding size_t, while strlen is a runtime function. When arrays decay to function parameters, they pass as pointers, losing dimension information.
Calculating Type Size Without sizeof
Pointer arithmetic can determine memory footprint by comparing adjacent addresses cast to byte pointers.
#define BYTE_FOOTPRINT(entity) \
((char*)(&(entity) + 1) - (char*)&(entity))
Aggregate Type Architecture: struct vs union
Structure (struct)
Members occupy distinct memory regions aligned for access efficiency. Total size equals the sum of member sizes plus padding. All members are accessible simultaneously. Used for composite data modeling.
Union (union)
Members overlay the same memory block. Total size matches the largest member plus alignment padding. Only one member holds a valid value at any time. Used for memory-efficient type punning or variant storage.
Algorithm: Single Element Frequency (Modulo Arithmetic)
Given an array where every integer appears three times except for one unique value, the solution can be derived using bitwise summation. Summing the bits at each of the 32 positions across all numbers yields a total. Since triplicate numbers contribute multiples of three, taking the sum modulo three isolates the bit value of the unique element.
int isolate_unique(std::vector<int>& data) {
int result = 0;
for (int bit = 0; bit < 32; ++bit) {
int sum_bits = 0;
int mask = 1 << bit;
for (int val : data) {
if (val & mask) sum_bits++;
}
if (sum_bits % 3) {
result |= mask;
}
}
return result;
}
Algorithm: Exponentiation by Squaring
Recursive reduction halves the exponent at each step, achieving logarithmic time complexity. Negative exponents are handled by computing the reciprocal.
class PowerCalculator {
double recursive_compute(double base, unsigned long exp) {
if (exp == 0) return 1.0;
double half = recursive_compute(base, exp / 2);
return (exp % 2 == 0) ? half * half : half * half * base;
}
public:
double calculate(double base, int exp) {
unsigned long abs_exp = (exp < 0) ? -static_cast<unsigned long>(exp) : exp;
double res = recursive_compute(base, abs_exp);
return (exp < 0) ? 1.0 / res : res;
}
};
Sorting: In-Place Partitioning
QuickSort recursively partitions arrays around a pivot, placing smaller elements left and larger elements right. Correct boundary checks prevent out-of-range access.
int partition_array(std::vector<int>& arr, int start, int end) {
int pivot = arr[end];
int i = start - 1;
for (int j = start; j < end; ++j) {
if (arr[j] <= pivot) {
++i;
std::swap(arr[i], arr[j]);
}
}
std::swap(arr[i + 1], arr[end]);
return i + 1;
}
void sort_recursive(std::vector<int>& arr, int low, int high) {
if (low < high) {
int pivot_idx = partition_array(arr, low, high);
sort_recursive(arr, low, pivot_idx - 1);
sort_recursive(arr, pivot_idx + 1, high);
}
}
Selection: QuickSelect for Kth Element
Adapting the partition strategy avoids full sorting. The algorithm recurses only into the partition containing the target index, yielding average O(n) time.
class ElementSelector {
int select_partition(std::vector<int>& data, int left, int right, int target_idx) {
int pivot_val = data[right];
int store_idx = left;
for (int i = left; i < right; ++i) {
if (data[i] < pivot_val) {
std::swap(data[store_idx++], data[i]);
}
}
std::swap(data[store_idx], data[right]);
return store_idx;
}
public:
int findKthLargest(std::vector<int>& nums, int k) {
int target = nums.size() - k;
int left = 0, right = nums.size() - 1;
while (left <= right) {
int pos = select_partition(nums, left, right, target);
if (pos == target) return nums[pos];
else if (pos > target) right = pos - 1;
else left = pos + 1;
}
return nums[left];
}
};
Pattern Matching: KMP Optimization
The Knuth-Morris-Pratt algorithm uses a prefix function table to skip redundant comparisons during string search.
std::vector<int> compute_prefix(const std::string& pattern) {
int m = pattern.length();
std::vector<int> pi(m, 0);
int k = 0;
for (int q = 1; q < m; ++q) {
while (k > 0 && pattern[k] != pattern[q]) k = pi[k - 1];
if (pattern[k] == pattern[q]) k++;
pi[q] = k;
}
return pi;
}
int match_first_occurrence(const std::string& text, const std::string& pattern) {
if (pattern.empty()) return 0;
std::vector<int> pi = compute_prefix(pattern);
int q = 0; // matched characters
for (int i = 0; i < text.length(); ++i) {
while (q > 0 && pattern[q] != text[i]) q = pi[q - 1];
if (pattern[q] == text[i]) q++;
if (q == pattern.length()) return i - q + 1;
}
return -1;
}