01 C++ vs C & Core Basics

Overview: The Shift from C to C++

While C++ began as “C with Classes”, it has evolved into a multi-paradigm language. Understanding C++ requires moving from C’s implicit conversions and loose scoping toward strict compile-time type safety and clear namespace boundaries.


1. The Entry Point: main and command-line arguments

In both C and C++, the execution of a program starts with the global function main.

int main(int argc, char** argv) {
    return 0; // 0 indicates success status
}

Under the Hood:

  • argc (Argument Count): An integer representing the number of items passed via the command line. It is always at least 1, as the first argument (argv[0]) is the executable name/path.
  • argv (Argument Vector): A pointer to an array of null-terminated C-style strings (char*).
  • Memory Layout:
    argv ---> [ argv[0] ] ---> "/path/to/exec\0"
              [ argv[1] ] ---> "arg1\0"
              [ argv[2] ] ---> "arg2\0"
              [ nullptr ]
    
  • Exit Status: The return value is passed back to the operating system shell. Returning 0 signifies successful termination, whereas non-zero values (like 1 or -1) indicate specific error states.

2. Character Literal Type Difference: sizeof('c')

A classic trivia question with profound design implications:

  • In C: sizeof('c') == sizeof(int) (typically 4 bytes).
  • In C++: sizeof('c') == sizeof(char) (exactly 1 byte).

Why the difference?

  • C History: In early C (K&R), character literals were treated as integers. Functions without prototypes automatically promoted char parameters to int during stack frame setup.
  • C++ Overload Resolution: C++ supports function overloading. If 'c' were an int, calling print('c') would ambiguously match print(int) instead of print(char). C++ enforced character literals to be true char types to guarantee correct overloaded function matching at compile time.

3. Strict Function Prototyping

How empty parameter lists are interpreted depends on the compiler mode:

void func(); 
  • In C: This means a function accepting an unknown, unspecified number of arguments of arbitrary types. To declare a function that takes absolutely zero arguments in C, you must write void func(void).
  • In C++: This explicitly means a function that takes zero arguments (identical to void func(void)).

Why?

C++ requires the compiler to verify function calls against exact parameter signatures at compile time to perform function overloading and type safety checks. Allowing arbitrary parameters without signatures would break compiler checks.


4. Pointer Safety: nullptr vs. NULL / 0

C++11 introduced the keyword nullptr to replace the traditional macro NULL or literal 0.

// The Problem:
void print(int val);
void print(int* ptr);
 
print(NULL); // Ambiguity! NULL is macro-defined as 0, matching print(int).
print(nullptr); // Correctly matches print(int*)

How nullptr works:

  • nullptr is a strongly-typed literal of type std::nullptr_t.
  • It can be implicitly converted to any raw pointer type (char*, int*, MyClass*) or member-pointer type.
  • It cannot be implicitly converted or promoted to integer types (like int or long), completely avoiding function overload selection errors.

5. Standard Wrapper Headers

C++ provides standard wrapper headers for C libraries:

#include <cstdio>  // C++ Version (Preferred)
#include <stdio.h>  // C Version

The Difference:

  • <stdio.h>: Places all function names (like printf, scanf) directly in the global namespace. This risks name collisions in larger codebases.
  • <cstdio>: Places these functions inside the std namespace (e.g. std::printf). This prevents global namespace pollution, keeping your scope clean.

💻 Conceptual Code Demonstration

#include <cstdio> // Imports std::printf into the 'std' namespace
 
void printType(int x) {
    std::printf("Called printType(int) with value: %d\n", x);
}
 
void printType(char x) {
    std::printf("Called printType(char) with char: %c\n", x);
}
 
void printType(int* ptr) {
    if (ptr == nullptr) {
        std::printf("Called printType(int*) with a null pointer!\n");
    }
}
 
int main() {
    // 1. Literal Character Size Demonstration
    std::printf("sizeof('a') in C++: %zu\n", sizeof('a')); // Outputs 1, not 4
    
    // 2. Overload Resolution matching
    printType('a');    // Calls printType(char) because char literals are char-type
    printType(10);     // Calls printType(int)
    
    // 3. Nullptr safety
    // printType(NULL); // ERROR: ambiguous call if NULL compiles as 0
    printType(nullptr); // Safely resolves to printType(int*)
    
    return 0;
}