⚙️ Unit 06 • C++ Systems Engineering

Arrays, Matrices & String Architecture

Contiguous memory sequencing and string semantics in C++: 1D, 2D, and multidimensional arrays, Row-Major Order memory offsets, matrix algorithms, array-to-pointer decay, fundamental searching and sorting (Linear, Binary, Insertion), C-style null-terminated strings (<cstring>), and the modern dynamic std::string container (SSO, capacity, manipulation, and comparisons).

📦 Array Architecture: Contiguous Row-Major Order
🔍 Algorithms: Linear (O(N)) & Binary Search (O(log N))
🧵 Text Models: char[] with '\0' vs std::string
⚡ Memory Optimization: Small String Optimization (SSO)
01

Arrays: 1D, 2D & Contiguous Memory Representation

An array is a fixed-size, homogeneous sequence of elements stored in contiguous memory addresses. Because elements are adjacent in physical RAM, indexing operates in instantaneous O(1) constant time using pointer arithmetic:

📐 The 1D Memory Offset Formula

For an array declared as T arr[N];, the memory address of element arr[i] is computed as:
Address(arr[i]) = Base_Address + (i × sizeof(T))

One-Dimensional (1D) Arrays
  • Declaration: int scores[5] = {90, 85, 78, 92, 88};
  • Zero-Based Indexing: Indices range from 0 to N - 1.
  • No Bounds Checking: C++ does not perform runtime bounds checking on raw arrays! Accessing scores[10] writes to out-of-bounds stack memory, causing buffer overflows or segmentation faults.
Two-Dimensional (2D) Arrays: Row-Major Order

C++ stores 2D arrays in Row-Major Order (entire row 0, followed by row 1, etc., in a continuous linear memory block):

For int mat[ROWS][COLS];:
Address(mat[i][j]) = Base + ((i × COLS) + j) × sizeof(int)

1
Logical 2D Grid Representation: mat[2][3]
Row 0: [10, 20, 30] | Row 1: [40, 50, 60]
2
Physical 1D Contiguous RAM Layout
Address 0x100: [10] → 0x104: [20] → 0x108: [30] → 0x10C: [40] → 0x110: [50] → 0x114: [60]
02

Array Traversal & Passing Arrays to Functions

When a raw array is passed into a function, it undergoes Array-to-Pointer Decay: the array parameter implicitly decays into a pointer pointing to its first element (int*). Consequently, the function loses all knowledge of the array's size:

array_passing_and_traversal.cpp
#include <iostream>

// Raw Array Passing: Must explicitly pass size!
// 'int arr[]' decays to 'int* arr'
void printArray(const int arr[], size_t size) {
    for (size_t i = 0; i < size; ++i) {
        std::cout << arr[i] << " ";
    }
    std::cout << "\n";
}

int main() {
    int data[] = {12, 24, 36, 48, 60};
    size_t length = sizeof(data) / sizeof(data[0]);

    // 1. Traditional Index-Based Traversal
    printArray(data, length);

    // 2. Modern C++11 Range-Based For Loop (Value Copy)
    std::cout << "Range-based for: ";
    for (int value : data) {
        std::cout << value << " ";
    }
    std::cout << "\n";

    // 3. Range-Based For by Const Reference (Zero Copy)
    for (const auto& val : data) {
        // Read-only access to val
    }

    return 0;
}
⚠️ The sizeof(arr) Inside Function Trap

Inside main(), sizeof(data) evaluates to 20 bytes (5 integers × 4 bytes). But inside printArray(const int arr[]), sizeof(arr) evaluates to 8 bytes (the size of a 64-bit pointer address)! Never attempt sizeof(arr) / sizeof(arr[0]) inside a function receiving a decayed array.

03

Matrix Algorithms & Searching/Sorting Fundamentals

Linear Search (O(N))

Iterates through elements sequentially from index 0 to N-1 until a match is found. Works on unsorted arrays. Worst-case: N comparisons.

Binary Search (O(log N))

Requires the array to be pre-sorted. Compares target with middle element, repeatedly halving the search space. 1,000,000 elements searched in only ~20 comparisons!

binary_search_and_matrix.cpp
#include <iostream>

// Binary Search Algorithm on a Sorted Array: O(log N)
int binarySearch(const int arr[], int size, int target) {
    int low = 0;
    int high = size - 1;

    while (low <= high) {
        // Prevents integer overflow: (low + high) / 2 could overflow!
        int mid = low + (high - low) / 2;

        if (arr[mid] == target) {
            return mid; // Target found at index mid
        } else if (arr[mid] < target) {
            low = mid + 1;  // Target in right half
        } else {
            high = mid - 1; // Target in left half
        }
    }
    return -1; // Target not found
}

// 2x2 Matrix Multiplication: C[i][j] = sum(A[i][k] * B[k][j])
void multiplyMatrix2x2(int A[2][2], int B[2][2], int C[2][2]) {
    for (int i = 0; i < 2; ++i) {
        for (int j = 0; j < 2; ++j) {
            C[i][j] = 0;
            for (int k = 0; k < 2; ++k) {
                C[i][j] += A[i][k] * B[k][j];
            }
        }
    }
}
04

Character Arrays, C-Style Strings & std::string

C++ supports two distinct text paradigms: historical C-style character arrays and the modern, heap-managed std::string container class:

Feature C-Style String (char[] / const char*) Modern C++ std::string Class
Memory Model Fixed-size array requiring manual null-terminator ('\0') Dynamic memory management; automatically resizes on demand
Length Tracking Requires linear scan O(N) using strlen() Stored as internal field; retrieved in O(1) via .length()
Safety High vulnerability to Buffer Overflow attacks Memory-safe with boundary checks (.at())
Copy & Assign Requires strcpy(); assignment operator illegal Native assignment operator (s1 = s2) with deep copy semantics
Optimization Zero class overhead Small String Optimization (SSO) avoids heap for short strings
c_string_vs_std_string.cpp
#include <iostream>
#include <cstring>  // For C-style functions: strlen, strcpy, strcmp
#include <string>   // For modern C++ std::string

int main() {
    // 1. C-STYLE STRING: Must account for '\0' (Null Terminator)!
    char cstr[10] = "Dev"; // Memory: ['D', 'e', 'v', '\0', ...]
    std::cout << "C-string length: " << std::strlen(cstr) << "\n"; // 3

    // 2. MODERN STD::STRING CLASS
    std::string str1 = "DevAscent";
    std::string str2 = " Academy";

    // Concatenation
    std::string fullCourse = str1 + str2; // "DevAscent Academy"

    // String Substring and Search
    std::string brand = fullCourse.substr(0, 9); // "DevAscent"
    size_t foundIndex = fullCourse.find("Academy");

    if (foundIndex != std::string::npos) {
        std::cout << "Found 'Academy' at index: " << foundIndex << "\n";
    }

    return 0;
}
💡 Small String Optimization (SSO) Internals

In modern C++ implementations (GCC libstdc++, Clang libc++, MSVC), std::string contains an internal stack buffer (typically 15 to 22 bytes). For short strings, the data resides directly on the stack, bypassing malloc/new heap allocations entirely. Only when strings exceed this capacity does std::string allocate dynamic heap memory.

05

String Input/Output, Comparisons & Manipulation

String Input: cin vs getline()
  • cin >> str;: Reads single token up to first whitespace character.
  • std::getline(std::cin, str);: Reads an entire line including spaces up to \n.
  • cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');: Flushes remaining newline after cin >>.
Lexicographical Comparisons

C++ compares strings lexicographically based on ASCII/Unicode numeric values:

  • str1 == str2 (Equality check)
  • str1 < str2 (Alphabetical sorting order)
  • Three-Way Comparison (C++20 Spaceship <=>): Yields std::strong_ordering::less, equal, or greater in a single comparison cycle.
06

Viva Voce & Engineering Interview Questions

Q1 • Architecture What is "Array-to-Pointer Decay" in C++, and why does it occur?
When an array name is passed as an argument to a function or assigned to a pointer, its type implicitly converts ("decays") into a pointer pointing to its first element (e.g., int[10] decays to int*). It occurs because C++ inherited C's memory efficiency model to avoid copying entire arrays onto the stack frame. As a consequence, array size information is lost and must be passed as an explicit companion parameter.
Q2 • Memory Layout Explain Row-Major Order and calculate the address of arr[2][3] in an int arr[4][5] starting at address 1000.
Row-Major Order stores 2D elements contiguously row-by-row. With int of 4 bytes and COLS = 5:
Offset = (row × COLS + col) = (2 × 5 + 3) = 13 elements.
Address = 1000 + (13 × 4) = 1000 + 52 = 1052.
Q3 • Algorithms Why is mid calculated as low + (high - low) / 2 instead of (low + high) / 2 in Binary Search?
If low and high are large integers (e.g., in arrays with millions of elements where indices approach INT_MAX), adding low + high exceeds 2,147,483,647 and triggers a signed integer overflow (Undefined Behavior), resulting in a negative number and causing an immediate out-of-bounds crash. low + (high - low) / 2 mathematically avoids overflow.
Q4 • String Internals What is Small String Optimization (SSO) in std::string?
SSO is an internal memory optimization where std::string uses an internal stack array (typically 15 to 22 bytes) to store short strings directly within the object itself. This eliminates the CPU latency and memory fragmentation associated with making a dynamic heap allocation (new/malloc) for small strings.
Q5 • Safety Why are C-style strings (char*) considered hazardous compared to std::string?
C-style strings rely strictly on a trailing null terminator ('\0') to determine boundaries. If a string manipulation function (like strcpy or strcat) writes past the allocated array bounds, or if the null terminator is overwritten, operations read and write into adjacent memory uncontrollably. This is the root cause of classic Buffer Overflow security exploits.
Q6 • Modern C++ How does std::array (introduced in C++11) improve upon raw C-style arrays?
std::array<T, N> is a zero-overhead container wrapping a fixed-size C-style array. Unlike raw arrays, std::array does not implicitly decay to a pointer when passed by value, knows its own size via .size(), provides safe bounds-checked element access via .at(i), supports copy/assignment syntax, and integrates natively with STL algorithms and iterators.
Q7 • Traversal When using a range-based for loop with arrays, when should you use const auto&?
You should use const auto& when iterating over elements of non-primitive types (such as an array of structs or std::string objects) to prevent creating an expensive local copy of each element on every iteration, while simultaneously guaranteeing that the loop body cannot accidentally mutate the original data.
Q8 • Performance What is the algorithmic time complexity difference between strlen(cstr) and str.length()?
strlen(cstr) must iterate character-by-character through memory until it encounters the '\0' terminator, running in O(N) linear time. In contrast, std::string::length() returns a precomputed size member variable stored inside the class instance, running in O(1) constant time.