Virtual assistance

C Programming Tutorial

Welcome to AIVista --India's tutorial pages on C Programming

C Programming

Storage Classes and Scope in C

Storage classes determine the lifetime, scope, and memory location of variables in C programs.

"Storage classes in C define where variables are stored, how long they exist, and what parts of the program can access them."

What are Storage Classes?

Storage classes are keywords that specify the lifetime, scope, and memory location of variables. They control how variables are allocated and accessed throughout the program.

Storage Class Specifiers

  • auto: Local variables (default for local variables)
  • static: Variables that persist between function calls
  • extern: Variables declared in other files
  • register: Variables stored in CPU registers (hint to compiler)

Scope in C

Scope determines the visibility and accessibility of variables in different parts of the program.

Types of Scope

  • Block Scope: Variables declared inside blocks {}
  • Function Scope: Labels in functions (rarely used)
  • File Scope: Global variables
  • Function Prototype Scope: Parameter names in prototypes

auto Storage Class

The auto storage class is the default for local variables. Variables are created when the block is entered and destroyed when the block is exited.

auto Variables

#include <stdio.h>

void function() {
    auto int x = 10;  // auto is optional, same as int x = 10;
    printf("x = %d\n", x);
}

int main() {
    auto int a = 5;  // Local to main
    printf("a = %d\n", a);

    {
        auto int b = 15;  // Local to this block
        printf("b = %d\n", b);
    }

    // b is not accessible here
    // printf("b = %d\n", b);  // Error!

    function();

    return 0;
}

static Storage Class

Static variables retain their values between function calls and are initialized only once.

Static Local Variables

#include <stdio.h>

void counter() {
    static int count = 0;  // Initialized only once
    count++;
    printf("Function called %d times\n", count);
}

int main() {
    for (int i = 0; i < 5; i++) {
        counter();
    }

    return 0;
}

Static Global Variables

// File: example.c
#include <stdio.h>

static int global_counter = 0;  // Only accessible in this file

void increment() {
    global_counter++;
    printf("Counter: %d\n", global_counter);
}

int main() {
    increment();
    increment();
    return 0;
}

Static Functions

// File: math_utils.c
#include <stdio.h>

static int helper_function(int x) {  // Only accessible in this file
    return x * x;
}

int public_function(int x) {
    return helper_function(x) + 10;
}

// File: main.c
#include <stdio.h>

extern int public_function(int);

int main() {
    printf("Result: %d\n", public_function(5));
    // helper_function(5);  // Error: not accessible
    return 0;
}

extern Storage Class

The extern keyword declares that a variable is defined in another file or later in the same file.

Global Variable Declaration

// File: globals.h
extern int global_variable;
extern void print_global();

// File: globals.c
#include <stdio.h>
#include "globals.h"

int global_variable = 42;  // Definition

void print_global() {
    printf("Global: %d\n", global_variable);
}

// File: main.c
#include <stdio.h>
#include "globals.h"

int main() {
    print_global();
    global_variable = 100;
    print_global();
    return 0;
}

Function Declarations

// File: math.h
#ifndef MATH_H
#define MATH_H

extern int add(int a, int b);
extern int multiply(int a, int b);

#endif

// File: math.c
#include "math.h"

int add(int a, int b) {
    return a + b;
}

int multiply(int a, int b) {
    return a * b;
}

// File: main.c
#include <stdio.h>
#include "math.h"

int main() {
    printf("Add: %d\n", add(5, 3));
    printf("Multiply: %d\n", multiply(5, 3));
    return 0;
}

register Storage Class

The register keyword suggests to the compiler that a variable should be stored in a CPU register for faster access.

register Variables

#include <stdio.h>

int main() {
    register int i;
    register int sum = 0;

    for (i = 0; i < 1000000; i++) {
        sum += i;
    }

    printf("Sum: %d\n", sum);

    return 0;
}

Limitations of register Variables

  • Cannot take address using & operator
  • Cannot be global variables
  • May be ignored by compiler
  • Limited to variables that fit in registers

Variable Lifetime and Scope Examples

Block Scope Demonstration

#include <stdio.h>

int main() {
    int x = 10;  // Block scope for main

    printf("x in main: %d\n", x);

    {
        int x = 20;  // Different x, block scope
        printf("x in inner block: %d\n", x);

        {
            int x = 30;  // Another different x
            printf("x in nested block: %d\n", x);
        }

        printf("x in inner block again: %d\n", x);
    }

    printf("x in main again: %d\n", x);

    return 0;
}

Static vs Auto Variables

#include <stdio.h>

void auto_demo() {
    auto int auto_var = 0;
    auto_var++;
    printf("Auto variable: %d\n", auto_var);
}

void static_demo() {
    static int static_var = 0;
    static_var++;
    printf("Static variable: %d\n", static_var);
}

int main() {
    printf("Calling auto_demo:\n");
    auto_demo();
    auto_demo();
    auto_demo();

    printf("\nCalling static_demo:\n");
    static_demo();
    static_demo();
    static_demo();

    return 0;
}

Practical Applications

1. Function Call Counter

#include <stdio.h>

void function_called() {
    static int call_count = 0;
    call_count++;
    printf("Function called %d times\n", call_count);
}

int main() {
    function_called();
    function_called();
    function_called();

    return 0;
}

2. Maintaining State Between Calls

#include <stdio.h>

int get_next_id() {
    static int next_id = 1000;
    return next_id++;
}

int main() {
    printf("ID 1: %d\n", get_next_id());
    printf("ID 2: %d\n", get_next_id());
    printf("ID 3: %d\n", get_next_id());

    return 0;
}

3. Configuration Variables

// File: config.h
#ifndef CONFIG_H
#define CONFIG_H

extern int max_connections;
extern char* server_address;
extern int debug_mode;

#endif

// File: config.c
#include "config.h"

int max_connections = 100;
char* server_address = "localhost";
int debug_mode = 0;

// File: main.c
#include <stdio.h>
#include "config.h"

int main() {
    printf("Max connections: %d\n", max_connections);
    printf("Server address: %s\n", server_address);
    printf("Debug mode: %s\n", debug_mode ? "ON" : "OFF");

    return 0;
}

4. Singleton Pattern

#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int id;
    char name[50];
} Singleton;

Singleton* get_instance() {
    static Singleton* instance = NULL;

    if (instance == NULL) {
        instance = (Singleton*)malloc(sizeof(Singleton));
        instance->id = 1;
        strcpy(instance->name, "Singleton Instance");
        printf("Singleton created\n");
    }

    return instance;
}

int main() {
    Singleton* s1 = get_instance();
    Singleton* s2 = get_instance();

    printf("Same instance: %s\n", (s1 == s2) ? "Yes" : "No");
    printf("ID: %d, Name: %s\n", s1->id, s1->name);

    return 0;
}

5. Fast Loop Variables

#include <stdio.h>

int main() {
    register int i, j;
    int sum = 0;

    // Using register for loop counters
    for (i = 0; i < 1000; i++) {
        for (j = 0; j < 1000; j++) {
            sum += i * j;
        }
    }

    printf("Sum: %d\n", sum);

    return 0;
}

Storage Class Summary

Storage Class Lifetime Scope Default Value Memory Location
auto Block duration Block scope Garbage Stack
static Program duration Limited scope Zero Data segment
extern Program duration File scope Zero Data segment
register Block duration Block scope Garbage CPU registers

Common Mistakes and Best Practices

1. Shadowing Variables

int x = 10;  // Global

int main() {
    int x = 20;  // Local shadows global
    printf("x = %d\n", x);  // Prints 20

    {
        int x = 30;  // Shadows outer x
        printf("x = %d\n", x);  // Prints 30
    }

    printf("x = %d\n", x);  // Prints 20
    return 0;
}

2. Static Variables in Headers

// Wrong: static in header
// static int counter = 0;  // Each file gets its own counter

// Correct: extern in header, define in one source file
// Header: extern int counter;
// Source: int counter = 0;

3. Register Variable Addresses

register int x = 10;
// int *ptr = &x;  // Error: cannot take address of register variable

4. Static Function Visibility

// File: utils.c
static void internal_function() {
    // Only accessible within utils.c
}

void public_function() {
    internal_function();  // OK
}

// File: main.c
// internal_function();  // Error: not visible

Debugging Variable Scope Issues

Variable Visibility Checker

#include <stdio.h>

int global_var = 100;

void test_scope() {
    int local_var = 200;
    static int static_var = 300;

    printf("Global: %d\n", global_var);
    printf("Local: %d\n", local_var);
    printf("Static: %d\n", static_var);

    static_var++;
}

int main() {
    test_scope();
    printf("Global after function: %d\n", global_var);

    // printf("Local after function: %d\n", local_var);  // Error!
    // printf("Static after function: %d\n", static_var);  // Error!

    test_scope();  // Static variable retains value

    return 0;
}

Conclusion

Understanding storage classes and scope is crucial for writing efficient and bug-free C programs. The choice of storage class affects memory usage, performance, and program behavior. Use auto for temporary variables, static for persistent local data, extern for sharing variables across files, and register for performance-critical variables! 🚀