Storage Classes

Storage classes in C and C++ define the scope, visibility, lifetime, and memory location of variables or functions. They determine how memory is allocated and how long variables retain their values. There are several storage classes, each serving different purposes based on variable usage and program structure.

The major storage classes in C/C++ are:

  1. auto
  2. register
  3. static
  4. extern

Important Terms

1️⃣ Scope:

Scope refers to the visibility and lifetime of variables, functions, and other identifiers in your code. It defines where a variable can be accessed and how long it exists in memory. There are four primary types of scope in C/C++:

  1. Block Scope (Local Scope)
  2. File Scope (Global Scope)
  3. Function Scope
  4. Class/Namespace Scope (C++ specific

Block Scope (Local Scope)

  • Definition: Variables declared inside a block {} have block scope. These variables are only visible and accessible within the block where they are defined. The block is typically a function, a loop, or a conditional statement.
  • Lifetime: These variables exist only while the block is executing and are destroyed when the block finishes.
void myFunction() {
    int x = 10;  // x has block scope (local to myFunction)
    
    if (x > 5) {
        int y = 20;  // y has block scope (local to this if block)
        // y is accessible here
    }
    // y is not accessible here
}

Key Points:

  • Variables in block scope are destroyed when the block ends.
  • Block-scoped variables are local to the block and cannot be accessed outside of it.

2 File Scope (Global Scope)

  • Definition: Variables and functions declared outside of all functions or blocks have file scope, meaning they are visible throughout the entire file in which they are defined. These variables are commonly referred to as global variables.
  • Lifetime: They exist for the entire duration of the program.
int globalVar = 100;  // globalVar has file scope (visible throughout this file)

void myFunction() {
    globalVar = 200;  // Accessible and modifiable here
}

void anotherFunction() {
    globalVar = 300;  // Accessible and modifiable here too
}

Key Points:

  • File-scoped variables are accessible from any function within the file.
  • Global variables persist for the entire duration of the program and can lead to side effects if not handled carefully.

3 Function Scope

  • Definition: Labels (used for goto statements) have function scope. These labels are visible throughout the function in which they are declared, but only used for goto jumps within the same function.
  • Lifetime: Labels exist throughout the function’s execution, but they do not occupy memory like variables.
void myFunction() {
    int x = 10;
    
    if (x > 5) {
        goto label;  // Jump to label
    }

    label:  // Label with function scope
    printf("Reached the label");
}
  • Key Points:
    • Labels can be used throughout the function but cannot cross function boundaries.
    • goto statements and labels are generally discouraged in modern programming because they make code harder to read and maintain.

4 Class/Namespace Scope (C++ specific)

  • Class Scope: In C++, members of a class (variables, functions) have class scope. They are accessible within the class and its methods but are often private to the class unless specified otherwise.
    • Class members are either accessed via an object of the class or through the class itself (in case of static members).
class MyClass {
public:
    int publicVar;   // public member (accessible anywhere through an object)
private:
    int privateVar;  // private member (accessible only inside the class)
    
    void display() {
        privateVar = 10;  // Accessible here
    }
};

int main() {
    MyClass obj;
    obj.publicVar = 20;  // Accessible
    // obj.privateVar = 30;  // Error: privateVar is not accessible here
}
  • Namespace Scope: Variables, functions, and classes declared inside a namespace have namespace scope. These are accessible within the namespace and can be referenced outside using the namespace name or using directive.
namespace MyNamespace {
    int var = 10;
}

int main() {
    MyNamespace::var = 20;  // Access using the scope resolution operator
    return 0;
}

2️⃣ Visibility

Visibility refers to the accessibility of variables, functions, and other identifiers from different parts of a program. It is closely related to scope, but while scope defines where a variable exists in a program, visibility defines where a variable can be accessed from within its scope.

Levels of Visibility in C/C++:

  1. Block (Local) Visibility
  2. File (Global) Visibility
  3. External Visibility
  4. Class Visibility (C++ specific)
1 Block (Local) Visibility:
  • Definition: Variables defined within a block (such as within a function or inside loops) are only visible inside that block. They cannot be accessed from outside the block, even if the block is inside a function that has a broader scope.
void myFunction() {
    int x = 10;  // x is visible only inside this block
    if (x > 5) {
        int y = 20;  // y is visible only inside this block
    }
    // y is not visible here
}
  • Key Points:
    • Variables inside a block have local (block-level) visibility and are not accessible outside of the block in which they are defined.
    • This limits the visibility to ensure the variable is only used within the intended block, avoiding unintended access from outside.
2 File (Global) Visibility
  • Definition: Variables and functions declared outside of all functions (i.e., at the top level in a file) have file-level visibility. They can be accessed by any code in the same file. These are known as global variables or global functions.
int globalVar = 100;  // globalVar is visible throughout this file

void myFunction() {
    globalVar = 200;  // globalVar is accessible here
}
  • Key Points:
    • File-scoped variables and functions are visible to all functions within the file.
    • These variables have global visibility within the file unless modified with the static keyword.
3 External Visibility
  • Definition: By default, global variables and functions have external visibility, meaning they can be accessed by other files in the same program if declared with the extern keyword.
  • External Linkage: Using extern makes a variable or function visible across multiple files, allowing them to share the same variable or function.
// File 1

int sharedVar = 50;  // visible globally

void sharedFunction() {
    // Function definition
}
// File 2

extern int sharedVar;  // sharedVar is accessed from another file

void anotherFunction() {
    sharedVar = 100;  // sharedVar is accessible here
}
  • Key Points:
    • External visibility allows variables and functions to be shared across multiple files.
    • Use extern to declare that a variable or function is defined in another file, and to make it visible in the current file.
4 Class Visibility (C++ Specific)
  • Definition: In C++, visibility of class members (variables and functions) is controlled using access specifiers: public, private, and protected. These specifiers determine whether the class members are visible from outside the class, within derived classes, or only inside the class itself.

Access Specifiers:

  • public: Members are accessible from anywhere.
  • private: Members are only accessible within the class.
  • protected: Members are accessible within the class and derived classes.
class MyClass {
public:
    int publicVar;    // public visibility: accessible everywhere
private:
    int privateVar;   // private visibility: accessible only within MyClass
protected:
    int protectedVar; // protected visibility: accessible in MyClass and derived classes
};

int main() {
    MyClass obj;
    obj.publicVar = 10;  // Accessible here
    // obj.privateVar = 20;  // Error: privateVar is not accessible
    return 0;
}
  • Key Points:
    • public members are visible everywhere, private members are only visible inside the class, and protected members are visible within the class and derived classes.

3️⃣ Lifetime

Lifetime refers to the duration for which a variable exists in memory during the execution of a program. It determines how long a variable retains its value and when it is created and destroyed.

Types of Lifetime

  1. Automatic Lifetime
  2. Static Lifetime
  3. Dynamic Lifetime
  4. Thread Lifetime (C++11 and later)
1 Automatic Lifetime
  • Definition: Variables with automatic lifetime are automatically created and destroyed when a block of code (such as a function or a loop) is executed. They are typically local variables declared within functions or blocks.
  • Creation: Variables with automatic lifetime are created when the block in which they are declared is entered.
  • Destruction: They are destroyed when the block is exited
void myFunction() {
    int x = 10;  // x has automatic lifetime
    // x is created when the function is called
    // x is destroyed when the function exits
}
  • Key Points:
    • Automatic variables are stored on the stack.
    • Their lifetime is limited to the duration of the block or function in which they are declared.
2 Static Lifetime
  • Definition: Variables with static lifetime are created when the program starts and destroyed when the program ends. They retain their value between function calls or across different invocations.
  • Creation: Static variables are initialized once and retain their value throughout the program’s execution.
  • Destruction: They are destroyed when the program exits.
void myFunction() {
    static int count = 0;  // count has static lifetime
    count++;
    std::cout << count << std::endl;  // Count retains its value between calls
}

int main() {
    myFunction();  // Output: 1
    myFunction();  // Output: 2
    return 0;
}
  • Key Points:
    • Static variables are stored in the data segment of memory.
    • They can be either global or local, with local static variables retaining their value across multiple function calls.
3 Dynamic Lifetime
  • Definition: Variables with dynamic lifetime are allocated and deallocated manually using dynamic memory allocation functions such as malloc/free (in C) or new/delete (in C++). Their lifetime is controlled by the programmer.
  • Creation: They are created when memory is allocated dynamically.
  • Destruction: They are destroyed when memory is explicitly deallocated.
void myFunction() {
    int* ptr = new int;  // ptr has dynamic lifetime
    *ptr = 10;
    std::cout << *ptr << std::endl;  // Output: 10
    delete ptr;  // Deallocate memory
}

int main() {
    myFunction();
    return 0;
}
  • Key Points:
    • Dynamic variables are managed manually using memory allocation and deallocation functions.
    • Failure to deallocate memory properly can lead to memory leaks.

Major Storage Classes

1️⃣ auto (Automatic) Storage Class

  • Purpose: Specifies automatic storage duration, meaning the variable is automatically created and destroyed when the block in which it's defined is entered and exited. Variables with auto storage class are local to the block they are defined in.
  • Default: Local variables inside functions are auto by default. Since C++11, auto has a new meaning related to type inference, where the compiler automatically deduces the type of the variable.
  • Lifetime: The variable exists during the function or block execution.
  • Scope: Auto variables 

Example:

void function() {
    auto int num = 10;  // auto is the default for local variables
    // num is destroyed after the function scope ends
}
  • C++11 type inference:
auto x = 5;  // Compiler deduces that x is of type int

2️⃣ register Storage Class

  • Purpose: Requests the compiler to store the variable in a CPU register instead of RAM to optimize access speed. It’s used for variables that are accessed frequently, such as loop counters. However, modern compilers typically handle this automatically, so explicit use of register is rare.
  • Scope: Same as auto (local to the function/block).
  • Limitation: You cannot take the address of a register variable because it may not be stored in memory.

Why register is Less Relevant Today:

  1. Modern Compilers: Modern compilers automatically optimize the placement of variables, including deciding whether to store variables in registers. As a result, explicitly using register rarely leads to any performance gains.
  2. Deprecated in C++17: In C++17, the register keyword has been deprecated, and in C++20, it is completely removed. This means that in newer C++ standards, you should not use register.
    1. You can use register in C and in C++ code up to C++14.
  3. Modern Alternative:
    1. Instead of relying on register, trust modern compilers to optimize your code efficiently. If you want to optimize performance, focus on algorithmic improvements, better use of memory, and leveraging compiler-specific optimizations.

Example:

void fastFunction() {
    register int i;  // Suggests to store i in a CPU register for faster access
    for (i = 0; i < 1000; i++) {
        // loop
    }
}

3️⃣ static Storage Class

  • Purpose: Alters the lifetime and scope of a variable:
    • For local variables: A static variable inside a function retains its value between function calls. It is initialized only once.
    • For global variables and functions: Limits their visibility to the current translation unit (file), preventing them from being accessed by other files.
  • Lifetime: Exists for the entire program duration, but the scope can vary (local or global).

Example (Local static):

void counter() {
    static int count = 0;  // Retains value across function calls
    count++;
    printf("%d\n", count);
}

4️⃣ extern (External) Storage Class

  • Purpose: The extern keyword declares a variable or function that is defined in another file. It tells the compiler that the variable exists elsewhere, and it should not allocate storage for it. This is useful for sharing global variables or functions across different files.
  • Scope: Global, but visible across multiple files.
  • Declaration: The variable is defined in one file and declared with extern in others.

Example:

File 1:
int sharedVar = 10;  // Definition
File 2:
extern int sharedVar;  // Declaration
void printSharedVar() {
    printf("%d", sharedVar);
}

5️⃣ mutable Storage Class (C++ only)

  • Purpose: Allows a class member to be modified even if the containing object is const. Typically, when an object is const, none of its members can be modified. However, mutable provides an exception for certain members.
  • Scope: Only applicable to non-static class members.

Example:

class MyClass {
    mutable int counter;
public:
    void increment() const {
        counter++;  // Can modify counter even in const object
    }
};