top

Search

C Tutorial

.

UpGrad

C Tutorial

Storage Classes in C

Understanding Storage Classes in C?

Storage classes in C are a set of keywords that guide the compiler in storing and manipulating variables in memory. Each storage class has its own rules and behaviour, including scope, lifetime, and linkage.

Storage classes in C form the backbone of memory management, providing control over variable storage, scope, and duration. They help define the visibility and lifespan of a variable or function within a C program, enabling the programmer to manage data and memory more efficiently.

Use Of Storage Class In C

Storage classes in C are used to specify the lifetime, scope, and visibility of a variable or function. These attributes guide how the program handles memory storage, data visibility, and data retention across multiple function calls. The precise use of storage classes in C will become clearer as you keep reading and encountering the different types of storage classes in C and understand their attributes! 

Types of Storage Class In C

Wondering how many storage classes in C are there? There are four types of storage classes in C:

  1. Automatic Storage Class

  2. Static Storage Class

  3. Register Storage Class

  4. External Storage Class

Each of these storage classes has distinct rules, limitations, and behaviours. Let’s dive deep into each of these classes in the subsequent sections! 

Automatic Storage Class

Definition and Example of Automatic Storage Class

The auto storage classes in C are the default storage class for all local variables. The lifetime of these variables is limited to the function they are declared in, and they get destroyed once the execution of the function block is complete.

#include<stdio.h>

void func() {
    auto int x = 1; 
    printf("%d\n", x); 
    x += 1; 
} 

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

In the above code, the auto int x variable is automatically destroyed after each function call. So, it prints '1' each time func() is called instead of incrementing.

Rules and Limitations of Automatic Storage Class

  1. The 'auto' keyword is rarely used because local variables are considered 'auto' by default.

  2. Automatic variables are not recognised outside of the function in which they are declared, and their default value is garbage.

Comparison with Other Storage Classes

Compared to other storage classes, the key difference of an automatic storage class is its limited lifetime, which is only valid within the duration of its function block.

Static Storage Class

Definition and Example of Static Storage Class

Static storage class in C, represented by the keyword 'static', has a lifetime that exists for the entire execution of the program. This means that the value of a static variable persists between function calls.

#include<stdio.h>

void func() {
    static int x = 1;
    printf("%d\n", x);
    x += 1;
}

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

In this code, the static int x variable retains its value between function calls. So, it prints '1' on the first call and '2' on the second.

Rules and Limitations of Static Storage Class

  1. Static variables are initialised only once, and their scope is local to the block in which they are defined.

  2. Their default value is zero.

  3. Static variables retain their values between function calls.

Comparison with Other Storage Classes

Unlike automatic variables, static variables persist in their value between function calls. Their scope, however, remains limited to the block they're defined in.

Register Storage Class

Definition and Example of Register Storage Class

The register storage class in C, denoted by the keyword 'register', advises the compiler to store the variable in the CPU's register instead of RAM for faster access.

#include<stdio.h>

void func() {
    register int x = 1;
    printf("%d\n", x);
    x += 1;
}

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

In this example, the register int x variable is suggested to be stored in the CPU register.

Rules and Limitations of Register Storage Class

  1. The 'register' keyword is a request, not a command. The compiler can choose to ignore it if there are no available registers.

  2. The default value of a register variable is garbage, and its scope is local to the block where it is defined.

  3. You can't take the address of a register variable since it is stored in a CPU register rather than in memory.

Comparison with Other Storage Classes

The register class is similar to the auto class, which suggests the compiler stores the variable in a CPU register.

External Storage Class

Definition and Example of External Storage Class

External storage class in C, represented by the keyword 'extern', is used to give a reference of a global variable that is visible to all program files. When a variable is declared with the 'extern' keyword, it indicates that the variable is defined in another file and the current file is using its reference.

#include<stdio.h>
int x;  // x is a global variable

void func() {
    extern int x;  // x is referenced here
    printf("%d\n", x);
    x += 1;
}

int main() {
    x = 1;
    func();
    func();
    return 0;
}

In this example, the extern int x variable refers to the global int x variable.

Rules and Limitations of External Storage Class

  1. Extern variables cannot be initialised. They only point to an existing variable.

  2. They have a global scope, meaning they can be accessed from any part of the program, and their lifetime extends for the duration of the program. 

  3. Their default value is zero.

Comparison with Other Storage Classes

The extern storage class provides a method to access global variables across different files, making it unique compared to the other storage classes.

Scope and Lifetime

Explanation of Scope and Lifetime of Variables

The scope of a variable refers to the part of the program where the variable can be accessed. The lifetime of a variable refers to the period during which the variable exists in the memory during the execution of the program.

How Storage Classes Affect Scope and Lifetime

Each storage class in C has its own rules that determine the scope and lifetime of a variable. For instance, automatic variables have a scope limited to the function block they are declared in, and their lifetime ends when the execution of the function block is completed.

Best Practices

Using storage classes effectively requires understanding their properties and the best practices related to them. Here are some best practices to follow:

Declare Variables Appropriately

1. Local Variables: Use the 'auto' keyword for variables that are only required within a single function or block. This ensures that the variable's scope is limited, preventing potential conflicts with variables of the same name in other functions or blocks.

void function() {
    auto int localVariable = 0; // Local to this function
    // Code here...
}

2. Persistent Variables: Use the 'static' keyword for variables that need to maintain their value between function calls but should not be accessible outside their function. This is useful for functions that need to keep track of their state between calls.

void function() {
    static int persistentVariable = 0; // Retains its value between function calls
    // Code here...
}

3. Performance-Critical Variables: Use the 'register' keyword for variables that require quick access, such as counters in intensive loops. But remember, using the 'register' keyword is merely a suggestion to the compiler, and it is up to the compiler to put the variable in a register or not.

void function() {
    for (register int i = 0; i < 1000000; i++) {
        // Code here...
    }
}

4. Global Variables: Use the 'extern' keyword to refer to variables that are used across several files. Declare the variable in one file and use 'extern' in any other files where the variable is needed.

main.c:

nt globalVariable = 0; // Declare global variable

utils.c:

extern int globalVariable; // Reference to the global variable

Avoid Common Mistakes

1. Initialization of Extern Variables: Do not use 'extern' to declare a variable in the file where it is initialised. This might lead to complications. An 'extern' variable is a reference to a variable declared elsewhere, so it should not be initialised where it is declared.

Incorrect:

extern int globalVariable = 0; // Incorrect

Correct:

extern int globalVariable; // Correct

2. Scope Confusion: Do not use a variable with the same name in different scopes without understanding the implications. This can lead to confusion and bugs that are hard to trace. If a variable with the same name is declared in a smaller scope, it will overshadow the larger one.

int variable = 0; // Global scope

void function() {
    int variable = 1; // Local scope, overshadows global 'variable'
}

3. Uninitialized Variables: Do not forget to initialise 'static' and 'extern' variables as they retain their value across different function calls. Uninitialized 'static' and 'extern' variables can lead to unpredictable program behaviour.

Incorrect:

static int uninitializedVariable; // Incorrect, contains garbage value

Correct:

static int initializedVariable = 0; // Correct, initialized to 0

Case Studies

Case Study 1: Usage of Static Variables in System Calls

An excellent example of the use of storage classes in real-world programming can be seen in the design of operating systems. System calls in operating systems often use static variables to retain state information between different calls.

Consider the simple case of a system call that reads a file in chunks:

#include<stdio.h>

#define CHUNK_SIZE 256

void readFileInChunks() {
    static FILE *file = NULL;
    static char buffer[CHUNK_SIZE];

    if(file == NULL) {
        file = fopen("largeFile.txt", "r");
        if(file == NULL) {
            perror("Error opening file");
            return;
        }
    }

    if(fgets(buffer, CHUNK_SIZE, file) != NULL) {
        printf("%s", buffer);
    } else {
        fclose(file);
        file = NULL;
    }
}

int main() {
    for(int i = 0; i < 10; i++) {
        readFileInChunks();
    }
    return 0;
}

In this example, the readFileInChunks function uses static variables to hold the file pointer and a buffer. These variables maintain their state between function calls, enabling the function to remember its position in the file and read it in chunks.

Case Study 2: Usage of Register Variables in Performance-Critical Applications

Register variables are commonly used in performance-critical applications such as video games, embedded systems, or high-frequency trading platforms where a delay of microseconds can have substantial impacts.

Here is an example of a game loop that might use a register variable:

#include<stdio.h>

void gameLoop() {
    register int frameCounter = 0;
    while(1) {
        // Game logic here...
        frameCounter++;
        printf("Frame %d\n", frameCounter);
    }
}

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

In this example, the frame counter in the game loop is declared as a register variable. This suggests the compiler to try and store this variable in a CPU register for faster access. While it's up to the compiler whether to take this suggestion or not, using the register keyword in this way may lead to performance improvements.

Case Study 3: Usage of Extern Variables in Multi-File Programs

In large projects split over multiple source files, the 'extern' keyword can be used to indicate that a variable is declared in another file. This way, the variable can be shared across different files.

Consider a simple case where a program is split into two files, 'main.c' and 'utils.c'. The global variable 'globalVar' is declared in 'main.c' and used in 'utils.c'.

main.c:

#include<stdio.h>

int globalVar = 10; // Global variable

void printGlobalVar(); // Function declaration

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

utils.c:

#include<stdio.h>

extern int globalVar; // Reference to the global variable

void printGlobalVar() {
    printf("Global Var: %d\n", globalVar);
}

In the above example, extern int globalVar in 'utils.c' references the int globalVar declared in 'main.c', thus sharing the variable across files.C

Conclusion

Understanding storage classes in C is essential to becoming a proficient C programmer. Each storage class has its own specific use cases and behaviour, such as defining variable scope, lifetime, and linkage.

Mastering the use of storage classes can significantly enhance your efficiency and effectiveness as an expert programmer. This allows you to better manage and control how your variables are stored, accessed, and manipulated.

Ready to dive deep into C programming? Check out the comprehensive Executive Post Graduate Programme in Software Development course provided by upGrad. Under the guidance of IIIT-B, this course strives to solidify your knowledge and elevate your programming skills.

FAQs

1. Why do we use storage classes in C?

Storage classes in C are used to specify the scope, lifetime, and visibility of a variable or a function in a program.

2. What is the default storage class in C?

'auto' is the default storage class for local variables in C.

3. Can a register variable be a global variable?

No, a register variable cannot be global, as its scope is limited to the block in which it is declared.

Leave a Reply

Your email address will not be published. Required fields are marked *