Storage Classes
Storage classes are used to describe the scope, visibility and lifetime of a variable or function.
Core Properties
Scope
The scope of a variable specifies where it can be accessed in a file. It can be scoped to a block, i.e a function, an if/else statement body, or a loop, or between {} or globally scoped, i.e defined outside of any function and can be accessed anywhere in the file.
Visibility
Visibility or linkage of a variable specifies if the variable is visible to other files in a multi-file program.
Lifetime
Lifetime of a variable specifies how long it is alive; some variables are only alive within a scope, and some stay alive for the entire duration of the program. And some are allocated and cleaned up for a specific duration.
Storage Modifiers
C has 4 storage modifiers: auto, extern, static, & register.
Auto Storage Class
Auto is the default modifier for visibility & lifetime is bound to its scope; its memory is acquired when it comes into scope and freed when it goes out of scope.
You don't need to specify auto unless you are shadowing a global variable and want to be specific about the shadowing.
int main(void) {
int i = 10;
auto int j = 10;
}
Both i and j are auto.
Extern Storage Class
Visibility or linkage of a variable specifies if the variable is visible to other files in a multi-file program.
Extern modifiers control the visibility of a global variable in a multi-file project. Functions default to extern.
// main.c
int i = 10;
void foo(void);
int main(void) {
foo(); // will print i = 10;
return 0;
}
// foo.c
extern int i;
void foo(void) {
printf("i = %d\n", i);
}
This way, foo.c can access i, which is defined and declared in main.c.
void foo(void) {
extern int i;
printf("i = %d\n", i);
}
extern can be used within a scope to define that we are using a global variable defined in another file.
// main.c
char text[255];
char text2[255][50];
// foo.c
extern char text[];
extern char text2[][50];
When using an array, a one-dimensional array doesn't need to specify its size cause the compiler already knows it. But for a multidimensional array the nested sizes need to be defined.
extern int add(int a, int b);
// and
int add(int a, int b);
Are effectively the same cause functions default to extern.
Static Storage Class
A static lifetime means the value will exist until the end of the program, so any value with a static lifetime can be shared across threads.
Any variable declared static exists before main and remains in scope until the program ends. Values can also be leaked on the heap to guarantee they exist for the program's entire duration.
static has different behaviour for local and global variables.
void foo(void) {
static int i = 0;
printf("i = %d\n", i);
i++;
}
int main(void) {
foo(); //prints i = 0
foo(); //prints i = 1
foo(); //prints i = 2
foo(); //prints i = 3
return 0;
}
Local variables with the static modifier retain their value between function executions, unlike auto variables that are allocated on the stack; local static variables are defined in the data segment (or BSS segment). They stay from the start to the end of the program.
The whole struct can have the static modifier but individual members can't have a static modifier due to the alignment rule.
static variables have to be known at compile time, so
static int i = get_i();
will not work.
// main.c
static int i = 10;
static int foo(void) {
return 10;
}
Both the variable i and the function foo are now visible only in main.c, the file where they are defined.
Register Storage Class
Register modifiers tell the compiler to place the variable in a CPU register instead of RAM. The compiler still can choose not to do that for the sake of optimisation. It's more like a request. While a whole struct can have a register modifier, one field of a struct can't have the register modifier.
Register modifiers tell the compiler to place the variable in a CPU register instead of RAM. The compiler still can choose not to do that for the sake of optimisation. It's more like a request. While a whole struct can have a register modifier, one field of a struct can't have the register modifier.
int main(void) {
register int i = 10; // is in one of the CPU registers
return 0;
}
No Address Rule
You cannot take the address/reference of a register variable, as it has no address while in a register.
int main(void) {
register int i = 10; // is in one of the CPU registers
int* ptr_i = &i; // not allowed will cause an error
return 0;
}
While putting the address in a register works:
int main(void) {
int i = 10;
register int* ptr_i = &i; // allowed
return 0;
}
register has scope, lifetime, and visibility; register can't be global.
