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.
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!
Wondering how many storage classes in C are there? There are four types of storage classes in C:
Automatic Storage Class
Static Storage Class
Register Storage Class
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!
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.
The 'auto' keyword is rarely used because local variables are considered 'auto' by default.
Automatic variables are not recognised outside of the function in which they are declared, and their default value is garbage.
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 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.
Static variables are initialised only once, and their scope is local to the block in which they are defined.
Their default value is zero.
Static variables retain their values between function calls.
Unlike automatic variables, static variables persist in their value between function calls. Their scope, however, remains limited to the block they're defined in.
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.
The 'register' keyword is a request, not a command. The compiler can choose to ignore it if there are no available registers.
The default value of a register variable is garbage, and its scope is local to the block where it is defined.
You can't take the address of a register variable since it is stored in a CPU register rather than in memory.
The register class is similar to the auto class, which suggests the compiler stores the variable in a CPU register.
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.
Extern variables cannot be initialised. They only point to an existing variable.
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.
Their default value is zero.
The extern storage class provides a method to access global variables across different files, making it unique compared to the other storage classes.
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.
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.
Using storage classes effectively requires understanding their properties and the best practices related to them. Here are some best practices to follow:
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 |
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 |
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.
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.
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
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.
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 *