storage class in c

Storage class in C ( C Storage Classes specifiers)

In this article, you will learn the C storage classes specifiers with the help of programming examples. In this blog post, we will try to solve your following doubts,

  • What is the storage class in C?
  • What do you mean by storage classes explain each with example code?
  • How many storage classes are in C?
  • What is the default storage classes of local variable?
  • What is the mean of scope and life of a variable?

 

Storage class in C:

In the context of C variables, storage class specifiers are part of the sequence of declaration specifiers that control the linkage, storage duration, and memory location.

Before the C11, the C language had mainly 4 storage classes, the names are auto, static, extern, and register. You will see each storage class with the example code in the below section of the articles.

Let’s understand this concept with an example,

Suppose you have created a variable in C, the two properties always associated with the variable name that is its type and one storage class. For example,

static int data;

Here static is the storage class specifiers that control the linkage, storage duration, and storage memory location of the “data”. In the above example, data has static storage duration because we have used static storage-class-specifier.

 

Note: An identifier declared with the storage-class-specifier static has static storage duration.

 

Before using the storage class in C you must remember an important point that is only one storage class specifier may appear in a declaration, except that thread_local may be combined with static or with extern.

static int a; //valid

auto static int b; //Invalid

register extern int c; //Invalid

extern int d; //Valid

 

Storage-class specifiers supported by C:

C provides the following storage-class specifiers. Items declared with the auto or register specifier have local or automatic lifetimes. Items declared with the static or extern specifier have global or static lifetimes (throughout the execution of the program)

  • auto         ->   automatic duration and no linkage
  • extern    ->  static duration and external linkage (unless already declared internal )
  • static       ->   static duration and internal linkage ( unless at block scope )
  • register ->    automatic duration and no linkage (address of this variable cannot be taken)
  • Thread_local  -> thread storage duration (since C11)
  • typedef   -> typedef specifier is called a ‘‘storage-class specifier’’ for syntactic convenience only it does not specify storage.

 

Note: We can use one storage-class specifier in the declaration of specifiers, except that _Thread_local may appear with static or extern.

 

Now let’s see C Storage-class specifiers in detail with some example code that helps you to in understanding. But before explaining storage-class specifiers, I want to discuss the linkage and storage duration. I have already written an article on it if you want you can read it, C Linkage, you should know.

Storage Duration and Lifetime of identifier:

“Lifetime” is the period of the runtime of the program during which a variable or function exists. It is the duration for which storage is guaranteed to be reserved memory for it.

If an identifier is referred to outside of its lifetime, the behavior is undefined. Consider the below example code.

#include<stdio.h>

//Access data beyond its life time
int* fun()
{
    int data;

    int *ptr = &data;

    *ptr  = 5;

    return ptr;
}

int main()
{
    int *ptr = fun();
    
   //Behavior is undefined
    printf("%d\n",*ptr);

    return 0;
}

Output:

Undefined

 

The storage duration of the identifier determines its lifetime. All variables in a program have one of the following storage durations:

Automatic storage duration:

The storage for the variable is allocated at the beginning of the enclosing code block "{"and deallocated at the end of the enclosing block "}".

All variables defined within a block have automatic storage duration except those declared static, extern, or thread_local.

Static storage duration:

The storage for the variable is allocated when the program begins and deallocated when the program ends. Global and static variables have static storage duration. It is the reason they exist throughout the execution of the program.

Dynamic storage duration:

The storage for the variable is allocated and deallocated upon request by using dynamic memory allocation functions. For example, by using the malloc or calloc function we allocate the memory, and using the free function we delicate the allocated memory.

Thread storage duration.

The storage for the variable is allocated when the thread begins and deallocated when the thread ends. Each thread has its own instance of the object. Only objects declared thread_local (C11) have this storage duration. Only thread_local storage specifier is a type of storage specifier, which can be used together with static or extern to adjust linkage.

Note: static storage duration is also called global storage duration.

 

Scope rules in C:

Each identifier that appears in a C program is visible (i.e., can be used) only within a region of program text called its scope.

Different entities designated by the same identifier either have different scopes or are in different namespaces. It means the same identifier can not denote more than one entity in the same scope or namespaces.

Example,

You will get a compiler error when you will compile this code,

#include<stdio.h>


int main()
{
    //data denote int and float in same scope
    int data;

    float data;

    return 0;
}

 

There are four kinds of scopes:

  • Block scope.
  • File scope.
  • Function scope.
  • Function prototype scope.
Scope

Meaning

Block Scope If the declarator or type specifier that declares the identifier appears inside a block, the identifier has block scope, which terminates at the end of the associated block. The Identifiers of the block scope are visible in the block.
File Scope If the declarator or type specifier that declares the identifier appears outside of any block or list of parameters, the identifier has file scope, which terminates at the end of the translation unit. The Identifiers of the File scope are visible all over the file.
Function Scope Function scope is similar to the block scope, begins at the opening of the function, and terminates at the end of the associated function. A label name is the only kind of identifier that has a function scope. It can be used (in a goto statement) anywhere in the function in which it appears.
Function Prototype scope If the declarator or type specifier that declares the identifier appears within the list of parameter declarations in a function prototype ( not part of a function definition ), the identifier has a function prototype scope, which terminates at the end of the function declarator. The Identifiers of the Function Prototype scope are visible within the prototype.

 

If an identifier designates two different entities in the same namespace, the scopes might overlap. If so, the scope of one entity (the inner scope) will end strictly before the scope of the other entity (the outer scope). The entity declared in the outer scope is hidden (and not visible) within the inner scope.

#include<stdio.h>


int main()
{
    int data = 10;
    {
        int data = 20;
        printf("%d \n", data);
    }
    printf("%d \n", data);

    return 0;
}

 

Linkages of identifiers:

An identifier declared in different scopes or in the same scope more than once can be made to refer to the same object or function by a process called linkage. There are three kinds of linkage: external, internal, and none. For specific information on Linkage, see Linkages of identifiers in C programming.

Note: There is no linkage between different identifiers.

 

Explanation of Different storage classes in C:

So now let’s see the storage class in C one by one with example codes. Here mainly I will describe auto, static, extern, and register storage classes.

auto:

An auto storage class is the default storage class. if we have declared a local variable (non-static) without specifying any storage class, then the variable is automatically promoted to auto storage class.

int fun()
{
    int data; //auto variable
}

The auto storage-class specifier declares an automatic variable, a variable with a local lifetime. An auto variable is visible (scope) only in the block in which it is declared.

Let’s consider an example,

#include<stdio.h>


int main()
{
    {
        int data = 0;
    }

    printf("%d",data);

    return 0;
}

When you run the above program, you will get an error undeclared identifier ‘data’. It’s because data is declared inside the block. Outside of the block, it’s undeclared.

Some properties related to an auto variable

  • Scope: Within the block in which it is declared
  • Life: Live till the control remains in the block.
  • Storage: stack.
  • Default value: The values of uninitialized auto variables are undefined.
  • Linkage: No
  • Storage duration: automatic storage duration

Note: Result is implementation-defined if we try to indirectly access an object with automatic storage duration from a thread other than the one with which the object has associated.

 

static:

A variable declared within the block with the static storage-class specifier has a static storage duration ( global lifetime ) and it is only visible within the block in which it is declared. Let’s take an example,

#include<stdio.h>


int main()
{
    int *ptr;
    {
        //no linkage but live through the
        //program execution
        static int data = 10;

        ptr = &data; //valid
    }

    printf("%d", *ptr);

    return 0;
}

When you run the above program, you will get 10 as output. The identifier ‘data’ is not visible the outside of the block but it lives throughout the program execution.

Note: The static specifier specifies static storage duration only when not combined with storage-class specifier  _Thread_local.  ( Since C11).

Static variable preserves its previous value and it is initialized only once, prior to program startup. It is the reason the use of static is useful with a constant lookup table because it alleviates the overhead of frequent initialization in often-called functions.

Let’s take an example to understand this concept,

#include<stdio.h>

int test()
{
    static int data = 0;
    data++;
    return data;
}

int main()
{
    printf("data = %d\n", test());
    printf("data = %d\n", test());
    printf("data = %d\n", test());
    return 0;
}

Output:

static variable in c

In the above program, you can see the value of data persist between the different function calls. During the first function call, the value of data is initialized to 0. Its value is increased by 1. Now, the value of data is 1, which is printed on the screen. During the second function call, data is not initialized to 0 again. It’s because data is a static variable. The value data is increased by 1. Now, its value will be 2, which is printed on the screen and it also happens for each function call.

 

By default in the C language, the linkage of the global function and global variable is external that which means it is accessible by the same or another translation unit. But global function or variable with static keyword has internal linkage, so it only accesses within the translation unit (.c). It is not accessible by another translation unit. The static keyword protects your variable to access from another translation unit

Note: If you do not explicitly initialize a static variable, it is initialized to 0 by default. Let’s see an example code,

#include <stdio.h>

// Uninitialized global variable stored in BSS
static int data1;

//Initialized static variable stored in DS
static int data2 = 0;

int main(void)
{
    // Uninitialized static variable stored in BSS
    static int data3;

    //Initialized static variable stored in DS
    static int data4 = 0;

    //Printing the value
    printf("data1 =  %d\n",data1);
    printf("data2 =  %d\n",data2);
    printf("data3 =  %d\n",data3);
    printf("data4 =  %d\n",data4);
    return 0;
}

Output:

data1 = 0
data2 = 0
data3 = 0
data4 = 0

 

Note: An internal static variable (a static variable with local or block scope) must not be initialized with the address of the auto item, because the address of an auto item is not a constant.

Some properties related to a static variable

  • Scope: It can be used with functions at file scope and with variables at both file and block scope.
  • Storage duration: The static specifier specifies static storage duration only when not combined with storage-class specifier  _Thread_local.  ( Since C11).
  • Storage: If the static variable is initialized then stored in .ds otherwise stored in .bss.
  • Default value: The default value is zero.

 

extern:

The extern storage class only declares a variable and it is used to give the reference of the global variable that is accessed by all files of the program. Simply extern says to the compiler that this variable is defined elsewhere in the program, it only points to the already defined variable in the program.

A variable did not get any memory until you defined it. When you have declared a variable, then you just give the information of the variable to the compiler, but there is no memory reserve for it. You can declare a variable multiple times in your program but define it only once.

Suppose a project contains two files Display.c and Calculation.c. All calculation-related functions are defined in the calculation file and display-related functions are defined in the display file.

//File: Calculation.c
// Aticleworld.com

#include <stdio.h>

int gData; //Global Variable

main()
{
    int a,b;
    printf("Enter the data\n");
    
    scanf("%d%d",&a,&b);
    //Add the value of a and
    gData = a+b; 
}

 

//File: Display.c

//Aticleworld.com

int display()
{
    printf("Addition of Data = %d",gData);
}

 

If you compile the above project then you will get the linker error “undefine reference to gData because, at the linking time, the linker did not resolve the address of gData.

So how we can solve this problem?

Yes, you are thinking in the right way using the extern keyword, we can solve this problem. If we declare gData using the extern keyword in the Display file then the linker will resolve the address of gData.

//File: Display.c
//Aticleworld.com


extern int gData;

int display()
{
    printf("Addition of Data = %d",gData);
}

 

Some properties related to the global variable

  • Linkage: By default, all global variable has external linkage
  • Storage duration: The extern specifier specifies static storage duration only when not combined with storage-class specifier  _Thread_local.  ( Since C11).
  • storage: If the global variable is initialized then stored in .ds otherwise stored in .bss.
  • Default value: Global variables are initialized as 0 if not initialized explicitly.

 

Note: we can declare a variable multiple times in a program but define only a single time.  

 

You can also see, Local, Static and Global variables in C

register:

The register storage-class specifier is used to declare register variables. The register storage class suggests that access to the variable be as fast as possible. The extent to which such suggestions are effective is implementation-defined.

Nowadays, modern compilers are very good at code optimization and they rarely give you chance that using register variables will make your program faster. Also, the register keyword only gives the indication to the compiler to store this variable in the register instead of RAM, But it totally depends on the compiler. The compiler decides where to put the variable in register or RAM.

Note: We can not use & and * operator with a register variable because access to the address of the register variable is invalid.

int main()
{
    register int Data;
    
    int *piSumData = NULL;
    
    piSumData = &Data;
    
    *piSumData = 5;   //Assign the value
    
    printf("%d",*piSumData);
}

Output:

error: address of register variable ‘Data’ requested.

 

Check MCQs on Storage Class, Click Here

 

Recommended Articles for you: