⚙️ Unit 02 • C++ Systems Engineering

Variables, Data Types & Input/Output

First-principles memory architecture and stream I/O in C++: variable allocation, immutability with const/constexpr, fundamental primitives (int, float, double, char, bool, void), signed and unsigned type modifiers, implicit promotions vs static_cast, stream insertion and extraction, escape sequences, and precision formatting via <iomanip>.

💾 Memory Primitives: int, float, double, char, bool, void
🛡️ Modifiers: short, long, signed, unsigned
🔄 Type Safety: Implicit Promotion & static_cast<T>
📟 Streams & Formatting: cin, cout, setw, setprecision
01

Variables, Memory Addresses & Constant Immutability

In C++, a variable is a symbolically named storage location in physical RAM bound to a specific byte address and size. Declaring a variable instructs the compiler to reserve a designated number of bytes in memory based on its data type:

Variable Declaration vs Definition vs Initialization
  • Declaration: Informs compiler of the name and type without allocating memory (e.g., extern int globalCounter;).
  • Definition: Allocates memory space on the stack or data segment (e.g., int score;).
  • Initialization: Allocates memory and assigns an initial value simultaneously.
  • Uniform / Direct List Initialization (C++11): Using curly braces (int age{25};) prevents narrowing conversions at compile time!
Constants: const vs constexpr
  • Runtime Constant (const): The value cannot be mutated after initialization, but the initial value may be determined at runtime (e.g., reading from user input).
  • Compile-Time Constant (constexpr): Introduced in C++11; the expression must be completely evaluated at compile time, allowing the value to be used in array sizes, template arguments, and embedded ROM.
⚠️ Identifier Naming Rules & Best Practices
  • Allowed Characters: Uppercase letters (A-Z), lowercase letters (a-z), digits (0-9), and underscores (_).
  • Digit Rule: An identifier must never begin with a digit (e.g., 2variable is illegal syntax; variable2 is valid).
  • Case Sensitivity: latency, Latency, and LATENCY are three completely distinct variables in memory.
  • Reserved Identifiers: Names beginning with an underscore followed by an uppercase letter (_Temp) or containing double underscores (__count) are reserved by the C++ implementation/compiler.
1
Step 1: Declaration & Stack Reservation
int bufferSize = 4096; allocates 4 contiguous bytes on the thread stack at address 0x7ffee4.
2
Step 2: Read / Write Operations
CPU registers access the memory address via pointer dereferencing or direct stack offset load/store opcodes.
3
Step 3: Scope Exit & Stack Deallocation
When the enclosing block {} terminates, the stack pointer adjusts back, automatically releasing memory.
02

Fundamental Data Types & Memory Layout

C++ categorizes fundamental primitives into integral, floating-point, character, boolean, and incomplete types. Because the ISO C++ standard defines minimum size guarantees rather than absolute sizes, memory footprints vary by CPU architecture (32-bit vs 64-bit data models):

Data Type Typical Size Value Range (Standard 64-bit LP64/LLP64) IEEE Standard / Engineering Purpose
bool 1 Byte true (1) or false (0) Logical conditional expressions and boolean flags
char 1 Byte -128 to 127 (or 0 to 255) ASCII character codes and raw byte manipulation
int 4 Bytes -2,147,483,648 to 2,147,483,647 (-231 to 231-1) Standard integer arithmetic and loop indexing
float 4 Bytes ±1.18 × 10-38 to ±3.40 × 1038 (~7 decimal digits) IEEE 754 Single-Precision; standard for 3D graphics shaders
double 8 Bytes ±2.23 × 10-308 to ±1.79 × 10308 (~15-17 decimal digits) IEEE 754 Double-Precision; scientific and financial math
void 0 Bytes Valueless / Incomplete Functions returning no value, or generic raw pointers (void*)
data_type_sizes.cpp
#include <iostream>
#include <limits>

int main() {
    std::cout << "--- C++ Fundamental Memory Footprints ---\n";
    std::cout << "sizeof(bool)        : " << sizeof(bool)        << " Byte\n";
    std::cout << "sizeof(char)        : " << sizeof(char)        << " Byte\n";
    std::cout << "sizeof(int)         : " << sizeof(int)         << " Bytes\n";
    std::cout << "sizeof(float)       : " << sizeof(float)       << " Bytes\n";
    std::cout << "sizeof(double)      : " << sizeof(double)      << " Bytes\n";
    std::cout << "sizeof(long double) : " << sizeof(long double) << " Bytes\n";

    std::cout << "\n--- Numeric Range Boundaries ---\n";
    std::cout << "Min int : " << std::numeric_limits<int>::min() << "\n";
    std::cout << "Max int : " << std::numeric_limits<int>::max() << "\n";
    return 0;
}
03

Type Modifiers: short, long, signed & unsigned

Type modifiers alter the memory width or sign-bit representation of fundamental integral types:

Width Modifiers: short & long
  • short int (at least 16 bits / 2 bytes): Conserves memory in large audio or sensor arrays (-32,768 to 32,767).
  • long int (at least 32 bits / 4 or 8 bytes depending on OS).
  • long long int (at least 64 bits / 8 bytes): Holds values up to ~9.22 × 1018. Guaranteed 64 bits across all modern compilers.
Sign Modifiers: signed & unsigned
  • signed: Uses Two’s Complement notation where the most significant bit (MSB) denotes sign (0 = positive, 1 = negative). Default for int.
  • unsigned: Dedicates all bits to magnitude, strictly representing non-negative values (≥ 0). Doubles the maximum positive range (e.g., unsigned int: 0 to 4,294,967,295).
⚠️ The Unsigned Underflow Trap

Never decrement an unsigned integer past zero! In unsigned arithmetic, 0u - 1u wraps around modulo 2N to 4,294,967,295, resulting in dangerous infinite loops:
for (unsigned int i = 10; i >= 0; --i) // BUG: Infinite loop! i is never negative!

04

Type Conversion: Implicit Promotion vs Explicit Casting

Data conversion occurs when an expression combines variables of differing data types:

1. Implicit Conversion (Type Coercion)

Performed automatically by the compiler following standard integer promotion and usual arithmetic conversions (promoting smaller types to larger types without precision loss):

int a = 5;
double b = 2.5;
double result = a + b; // 'a' is implicitly promoted to double (5.0)
2. Explicit Casting (Type Casting)

Instructs the compiler to deliberately convert a type, even if precision loss occurs:

  • C-Style Cast (Avoid in C++): (int)myDouble. Blindly reinterprets bits without compile-time safety.
  • Modern C++ static_cast<T>: Compile-time verified cast ensuring compatible types.
casting_precision.cpp
#include <iostream>

int main() {
    int totalMarks = 485;
    int totalSubjects = 5;

    // Integer division bug: 485 / 5 = 97 (truncates if fractional)
    int badAvg = 482 / 5; // Yields 96, discarding .4

    // Explicit Type Casting using static_cast (Best Practice)
    double accurateAvg = static_cast<double>(482) / totalSubjects; // Yields 96.4

    std::cout << "Accurate Average: " << accurateAvg << "\n";
    return 0;
}
05

Stream Input/Output: cin, cout, Escape Sequences & <iomanip>

C++ handles standard input and output through stream abstractions. std::cin reads from the standard input stream (keyboard buffer), while std::cout writes to the standard output buffer:

Stream Extraction (cin >>) vs getline()
  • Stream Operator (cin >> x): Reads characters until encountering whitespace (space, tab, newline). Leaving subsequent words in the buffer.
  • Line Reading (std::getline(cin, str)): Reads the entire line including spaces until the newline character.
  • Buffer Flushing: When switching from cin >> to getline(), invoke cin.ignore() to discard the trailing newline character.
C++ Escape Sequences
  • \n: Newline (Line feed; moves cursor to next line without flushing buffer).
  • \t: Horizontal tab (Advances cursor to next 8-space tab stop).
  • \\: Backslash literal.
  • \": Double quotation mark literal.
  • std::endl: Inserts a newline and immediately forces a hardware buffer flush.
stream_formatting_iomanip.cpp
#include <iostream>
#include <iomanip>  // Input/Output Parametric Manipulators

int main() {
    double pi = 3.141592653589793;
    double balance = 12450.5;

    std::cout << "--- Stream Formatting with <iomanip> ---\n";

    // 1. Fixed floating-point notation with exact decimal places
    std::cout << std::fixed << std::setprecision(2);
    std::cout << "Bank Balance (2 decimals): $" << balance << "\n"; // $12450.50

    std::cout << std::setprecision(5);
    std::cout << "Pi (5 decimals): " << pi << "\n";               // 3.14159

    // 2. Tabular Column Formatting using setw() and setfill()
    std::cout << "\n--- Tabular Data Presentation ---\n";
    std::cout << std::left; // Left-align text inside width
    std::cout << std::setw(15) << "SKU ID" 
              << std::setw(20) << "PRODUCT NAME" 
              << std::setw(10) << "PRICE" << "\n";

    std::cout << std::string(45, '-') << "\n";

    std::cout << std::setw(15) << "DEV-1001" 
              << std::setw(20) << "NVMe SSD 2TB" 
              << "$" << std::setw(9) << 149.99 << "\n";

    std::cout << std::setw(15) << "DEV-1002" 
              << std::setw(20) << "DDR5 RAM 32GB" 
              << "$" << std::setw(9) << 119.50 << "\n";

    return 0;
}
💡 Performance Tip: '\n' vs std::endl

Never use std::endl inside tight loops! std::endl forces an explicit OS kernel I/O flush (std::fflush), dramatically degrading I/O performance. In performance-critical systems, use '\n' and allow the C++ runtime to flush the stream buffer automatically when full.

06

Viva Voce & Engineering Interview Questions

Q1 • Memory What is the difference between const and constexpr in modern C++?
const guarantees immutability after initialization, but the initialization may happen at runtime (e.g., initialized from user input or a file read). constexpr (compile-time constant expression) guarantees that the value must be calculable at compile time by the compiler, allowing it to be used in template parameters, bit-fields, and zero-runtime-cost ROM tables.
Q2 • Casting Why is static_cast<T> preferred over C-style casting (T)value in C++?
C-style casts perform an unchecked, brute-force conversion that can silently invoke const_cast, reinterpret_cast, or static_cast without compiler safety checks. static_cast<T> is type-safe: the compiler verifies that a valid conversion pathway exists between the types at compile time, rejecting invalid conversions (such as casting a pointer to an unrelated struct) and preventing silent runtime memory corruption.
Q3 • Data Types How does the representation of signed and unsigned integers differ in binary?
A signed integer uses Two’s Complement notation where the most significant bit (MSB) is the sign bit (0 for positive, 1 for negative), giving an N-bit range of -2N-1 to 2N-1-1. An unsigned integer treats all N bits as magnitude, allowing only non-negative numbers from 0 to 2N-1, effectively doubling the maximum positive range.
Q4 • Performance Why is '\n' significantly faster than std::endl in console output?
std::endl outputs a newline character and forces a synchronous hardware buffer flush (invoking stream.flush()). Forcing a flush interrupts CPU caching and forces an expensive operating system kernel context switch. Using '\n' simply writes the character to the output buffer in user space, allowing the system to perform high-throughput buffered batch writes.
Q5 • Syntax What is Uniform Initialization (brace initialization) and what safety feature does it provide?
Introduced in C++11, Uniform Initialization uses curly braces (e.g., int x{10};). Its primary safety guarantee is preventing narrowing conversions. For example, int x = 5.9; silently truncates to 5 in traditional C++, whereas int x{5.9}; triggers a compile-time error, preventing accidental loss of precision.
Q6 • I/O Stream Why does std::cin >> str fail when reading a full name with spaces, and how is it resolved?
The stream extraction operator (>>) considers whitespace (spaces, tabs, newlines) as string delimiters, stopping extraction at the first space. To read an entire line including spaces, developers use std::getline(std::cin, str), which extracts characters continuously until it encounters a newline delimiter.
Q7 • Stream Manipulators What do the std::fixed and std::setprecision() manipulators accomplish together?
By default, std::setprecision(N) sets the total number of significant digits (both before and after the decimal point). When combined with std::fixed, the stream switches to fixed-point notation, causing setprecision(N) to strictly control the exact number of digits displayed after the decimal point, essential for formatting currency and coordinates.
Q8 • Undefined Behavior What happens when a signed integer overflows in C++?
In the ISO C++ standard, signed integer overflow is Undefined Behavior (UB). The compiler is legally permitted to optimize away range checks, assume overflow never occurs, or produce arbitrary results. In contrast, unsigned integer overflow is strictly defined by the standard to wrap around according to modulo 2N rules.