Static & Extern Variables

Before going to static and extern, we must understand the scope rules of variables especially lifetime & visibility of variables.

Lifetime of the variable usually starts when the block is executed and the variable is declared, and ends when the block is finished. But, if the variable is static, the lifetime(or retaining value) would be throughout the program though it's scope is finished.

Scope/Visibility/Accessibility of the variable is where ever the variables can be accessed.

There are four main scopes(visibility/accessibility):
  • Function Scope 
  • Block/Local Scope i.e ({ and )}
  • File Scope 
  • Program Scope 

Static Variables

  1. Static variables are initialized to 0 by default if not initialized explicitly. These can be initialized using only constant literals, so we can't use the return value of any function. 
#include<stdio.h> 

int function(void) 
{ 
    return 73; 
} 
   
int main() 
{ 
    static int i = function(); 
    printf(" value of i = %d", i);  
    return 0; 
} 

Output:
1
2
In function 'main':
Line 10: error: initializer element is not constant
  1. Static variable preserves its value (lifetime of the variable is throughout the program) even after it is out of its scope. It remains in memory while the program is running. It basically used to count the number of times program/counter increments throughout the program. 
  2. Static variables are allocated memory in the data segment. Click here to know the memory layout of the process.
  3. Static variables shouldn't be declared inside the structure because C compiler expects structure elements to be placed together as the value of the structure elements retrieved by counting the offset from the beginning of the structure, so all structure elements should be placed in the same memory segment. 

Function/Block Scope


I couldn't find any difference between function and block scope, so both are same according to me as of now. I might change if there is any difference. 

#include<stdio.h> 
#define NUMBER_OF_FUNC_CALLS 3

int fun() 
{ 
   static int count = 0; 
   count++; 
   return count; 
} 

int main() 
{ 
   int i; 
   for (i = 0; i < NUMBER_OF_FUNC_CALLS; i++) {
      printf("%d ", fun()); 
   }
   return 0; 
}

Output:
1
1 2 3 

The static variable count can be returned to the caller function as mentioned in the above function, but it can't be accessed in the main function. 

#include<stdio.h> 
#define NUMBER_OF_FUNC_CALLS 3

void fun() 
{ 
   static int count = 0; 
   count++;
   printf(" In local fun() count is %d\n", count); 
   return; 
} 

int main() 
{ 
   int i; 
   for (i = 0; i < NUMBER_OF_FUNC_CALLS; i++) {
      fun();
      printf(" In main count is %d\n", count);
   }
   return 0; 
}

Output:
1
2
3
4
In function 'main':
Line 17: error: 'count' undeclared (first use in this function)
Line 17: error: (Each undeclared identifier is reported only once
Line 17: error: for each function it appears in.)

As variable count is declared in the local function "fun()", the scope of the variable i.e visibility (accessibility of the variable) is only within the function, so count is not accessible in the main(). 

#include<stdio.h> 
#define NUMBER_OF_FUNC_CALLS 3

void fun() 
{ 
   static int count = 0; 
   count++;
   printf(" In local fun() count is %d\n", count); 
   return; 
} 

int main() 
{ 
   int i; 
   for (i = 0; i < NUMBER_OF_FUNC_CALLS; i++) {
      fun();
   }
   return 0; 
}

Output:
1
2
3
 In local fun() count is 1
 In local fun() count is 2
 In local fun() count is 3

Nested Block Scope 

<to be updated>

File Scope 


If a variable is declared as static globally in any file, its scope or accessibility or visibility is limited to that particular file, but the lifetime of the variable is throughout the program. 

Example 1

main.c
#include <stdio.h>
#include "tree.h"

int main()
{
    int iterator;
    for (iterator = 0; iterator < NO_ITERATOR; iterator++) {
       counter++;
    }
    printf("In main counter value %d", counter);
    return 0;
}
tree.h
#ifndef TREE_H                                                                                                                                                                             
#define TREE_H                                                                                                                                                                             
                                                                                                                                                                                                    
#define NO_ITERATOR 5                                                                                                                                                             
static int counter = 0;                                                                                                                                                                      

#endif 
Output:
1
In main counter value 5

Here, the static variable counter is limited to main.c, so it printed the retained value of that particular file.

Example 2

tree.h
#ifndef TREE_H                                                                                                                                                                             
#define TREE_H                                                                                                                                                                             
                                                                                                                                                                                                    
#define NO_ITERATOR 5                                                                                                                                                             
static int counter = 0;                                                                                                                                                                      

#endif 

main.c
#include <stdio.h>
#include "tree.h"

int main()
{
    int iterator;
    for (iterator = 0; iterator < NO_ITERATOR; iterator++) {
       counter++;
    }
    printf("In main.c counter value %d", counter); 
    function();
    return 0;
}

file1.c
#include <stdio.h>
#include "tree.h"

int function()
{
    printf("In file1.c counter value %d", counter);
    return 0;
}

Output:
1
2
In main counter value 5
In file1.c counter value 0 

In example 2, the static variable counter can be accessible in different .c files as it is declared and defined in the tree.h file. The value of the variable is limited/accessible to that particular .c file since it is static. It can't retain the value "5" to the other file1.c file though its lifetime is throughout the program. Here, the static is defined in multiple .c files. Here, both main.c & file1.c has its own copy of variable counter. 

Avoid  including global static variables more than one .c file and avoid expecting the value will be retained in other .c files. In this case, use global variables where the values will be retained in all .c files. See example 5 in extern variables.

Example 3

tree.h
#ifndef TREE_H                                                                                                                                                                             
#define TREE_H                                                                                                                                                                             
                                                                                                                                                                                                    
#define NO_ITERATOR 5                                                                                                                                                             
static int counter = 0;                                                                                                                                                                      

#endif   

main.c
#include <stdio.h>
#include "tree.h"

int main()
{
    int iterator;
    for (iterator = 0; iterator < NO_ITERATOR; iterator++) {
       counter++;
    }
    printf("In main.c counter value %d", counter); 
    function();
    return 0;
}

file1.c
#include <stdio.h>
#include "tree.h"
int function() { counter++; printf("In file1.c counter value %d", counter); return 0; }

Output:
1
2
In main counter value 5
In file1.c counter value 1 

It is not good practice to define a static variable in the header file. The result will be having a private copy of that variable in each source file. This applies for static function also.  

Function arguments can't be static variables.

Static Functions


a.c
------

#include <stdio.h>

/* Undefined behavior: already defined in main.
 * Binutils 2.24 gives an error and refuses to link.
 * https://stackoverflow.com/questions/27667277/why-does-borland-compile-with-multiple-definitions-of-same-object-in-different-c
 */
/*void f() { puts("a f"); }*/

/* OK: only declared, not defined. Will use the one in main. */
void f(void);

/* OK: only visible to this file. */
static void sf() { puts("a sf"); }

void a() {
    f();
    sf();
}

main.c
-------

#include <stdio.h>

void a(void);        

void f() { puts("main f"); }

static void sf() { puts("main sf"); }

void m() {
    f();
    sf();
}

int main() {
    m();
    a();
    return 0;
}

Compile and run:
-----------------

gcc -c a.c -o a.o
gcc -c main.c -o main.o
gcc -o main main.o a.o
./main

Output:
-------

main f
main sf
main f
a sf

Static functions are limited/visible which can be used only within the file where it is defined. Though each function has the same function name, it doesn't violate the one-definition rule rather each translation unit has its own copy of the static function definition. 

Extern Variables

Before understanding extern variables, we must understand how to declare and define a variable.

int count;

The above is the declaration and definition of a variable, and it means memory is allocated for variable "count". But, if we prepend extern with C variables, the memory will not be allocated, these variables will only be declared.

extern int count;

Now, we can answer if someone asks how would you declare a variable without defining it. Ok! Let's go to our topic. Extern keyword extends the visibility of the program.

Note: The variable can be declared at any number of time.

Definition

Extern is the keyword used to declare a variable in another file and tells the compiler, the memory is already allocated(variable is defined) which will be found at linking. 

Extern forces to implement the variable outside of the it's scope.

Extern can be shared between modules, and it is linkage modifier. 

Example 1

main.c
#include<stdio.h>
int count;
void main()
{
    count = 5; 
    printf("Function %s Line %d Count %d\n", __func__, __LINE__, count);
    return;
}

Output:
1
Function main Line 7 Count 5

Here, the count is defined and declared implicitly globally.

Example 2

main.c
#include<stdio.h>
extern int count;
void main()
{
    count = 5; 
    printf("Function %s Line %d Count %d\n", __func__, __LINE__, count);
    return;
}

Output:
1
2
In function `main':
undefined reference to `count'

"count" is not defined but declared, it throws the error because the memory is not allocated, but it tries to change the value.

Example 3

main.c
#include<stdio.h>
#include "count.h"

int count;

void main()
{
    count = 5; 
    printf("Function %s Line %d Count %d\n", __func__, __LINE__, count);
    return;
}

count.h
#ifndef _COUNT_H
#define _COUNT_H
extern int count;
#endif

Output:
1
Function main Line 7 Count 5

Here, count is defined in count.h, so the program compiled successfully. 

Example 4 

main.c
#include<stdio.h>

extern int count = 6;

void main()
{
    printf("Function %s Line %d Count %d\n", __func__, __LINE__, count);
    return;
}

Output:
1
Function main Line 8 Count 6

Here comes the surprise, though extern is prepended in the variable, it can be defined or the memory can be allocated as well if it is initialized.

The above program might throw a warning as " 'count' initialized and declared 'extern' [enabled by default]"

Example 5

main.c
#include<stdio.h>
extern int count;

void main()
{
    printf("Function %s Line %d Count %d\n", __func__, __LINE__, count);
    count_function();
    printf("Function %s Line %d Count %d\n", __func__, __LINE__, count);
    return;
}

count.c
#include<stdio.h>
int count = 0;

void count_function()
{
    int i;
    for (i = 0; i < 5; i ++) {
        count++;
    }
    printf("Function %s Line %d Count %d\n", __func__, __LINE__, count);
    return;
}

Output:
1
2
3
Function main Line 14 count 0
Function count_function Line 10 count 5
Function main Line 16 count 5

Variables can be prepended with extern in the .h files which is included in many .c files if you want to use the variable in multiple files.

Please be cautious not to add extern variables/functions in .c files. Always use extern in .h files.

Example 6 (Must Read *)


head.h
--------
#ifndef _HEAD_
#define _HEAD_

int count = 0;
void file_two();
#endif


file1.c
--------
#include <stdio.h>
#include "head.h"

int main()  
{
  printf("main count %d \n", count);
  file_two();
  return 0;
}


file2.c
--------
#include <stdio.h>
#include "head.h"

void file_two()
{
  printf("file two count %d \n", count);
  return;
}

Output:
1
2
3
4

gcc -o extrn file1.c file2.c

duplicate symbol '_count' in:

    /var/folders/yq/q17gqnjx0kqg4w8c80h8dly40000gn/T/file1-c7bbdc.o

    /var/folders/yq/q17gqnjx0kqg4w8c80h8dly40000gn/T/file2-f85f57.o

ld: 1 duplicate symbol for architecture arm64

clang: error: linker command failed with exit code 1 (use -v to see invocation)


Here, it says it finds duplicate definition for the count. 


Example 7 (Must Read *)


Now, I added the extern keyword in the head.h file 

head.h
--------
#ifndef _HEAD_
#define _HEAD_

extern int count = 0;
void file_two();
#endif


file1.c
--------
#include <stdio.h>
#include "head.h"

int main()  
{
  printf("main count %d \n", count);
  file_two();
  return 0;
}


file2.c
--------
#include <stdio.h>
#include "head.h"

void file_two()
{
  printf("file two count %d \n", count);
  return;
} 

Output:
1
2
3
4

gcc -o extrn file1.c file2.c

In file included from file1.c:2:

./head.h:4:12: warning: 'extern' variable has an initializer [-Wextern-initializer]

extern int count = 0;

           ^

1 warning generated.

In file included from file2.c:2:

./head.h:4:12: warning: 'extern' variable has an initializer [-Wextern-initializer]

extern int count = 0;

           ^

1 warning generated.

duplicate symbol '_count' in:

    /var/folders/yq/q17gqnjx0kqg4w8c80h8dly40000gn/T/file1-50d9c3.o

    /var/folders/yq/q17gqnjx0kqg4w8c80h8dly40000gn/T/file2-161afb.o

ld: 1 duplicate symbol for architecture arm64

clang: error: linker command failed with exit code 1 (use -v to see invocation)


Again this is a definition, so it throws duplicate definition error. This definition is included in both .c files, so it's wrong. 


Example 7 (Must Read *)


head.h
--------
#ifndef _HEAD_
#define _HEAD_

extern int count;
void file_two();
#endif


file1.c
--------
#include <stdio.h>
#include "head.h"

int main()  
{
  printf("main count %d \n", count);
  file_two();
  return 0;
}


file2.c
--------
#include <stdio.h>
#include "head.h"

void file_two()
{
  printf("file two count %d \n", count);
  return;
} 

Output:
1
2
3
4

gcc -o extrn file1.c file2.c

Undefined symbols for architecture arm64:

  "_count", referenced from:

      _main in file1-839344.o

      _file_two in file2-64c86e.o

ld: symbol(s) not found for architecture arm64

clang: error: linker command failed with exit code 1 (use -v to see invocation)


Now, the duplicate error is gone, but the count is not defined because it is just declared. 

Example 8 (Must Read *)


head.h
--------
#ifndef _HEAD_
#define _HEAD_

extern int count;
void file_two();
#endif


file1.c
--------
#include <stdio.h>
#include "head.h"
int count = 1;

int main()  
{
  printf("main count %d \n", count);
  count++;
  file_two();
  return 0;
}


file2.c
--------
#include <stdio.h>
#include "head.h"

void file_two()
{
  cunt = 90;
  printf("file two count %d \n", count);
  return;
} 

Output:
1
2
3

main count 1 

file two count 90


Now, the count is defined in any of the .c file. This count variable visibility is extended where ever it is declared via the header file. 

Here, each of the .c files don't have a private copies of this variable count. 

Advantages 

Avoid multiple memory allocation by defining. 
It doesn't violate the one definition rule as well. 

Key Takeaways 

  • Don't have static variable in .h 
  • Don't have extern variable declaration in .c

Sources : GeeksforGeeks and aelinik, SO link

Comments

Post a Comment