April 15, 2024 • C/C++, Interview

Introduction

One day I got an invitation to an interview where I was asked a wide sea of questions related to static keyword. One of the questions was on interpreting the following code snippets:

static int status;

vs.

int status;

The question seems to be obvious to answer, at the same time, the topic seems to be confusing for many engineers and software developers who learn C/C++.

The main objective of the article is to demonstrate usage of static keyword in C/C++ language. This is the article related to C only, where C++ will be described in another one.

Scope

The keyword static cannot be discussed without understanding scope.

scope - the part of the program where the variable (name) can be used. More formally, it can be said that this is the part for the program where the name binding is valid, in other words, where the name can be used to refer the entity [WikipediA].

The following scopes are available:

- Block scope

- Function scope

Block Scope

Any entity defined in the block scope may be used and visiable only in this scope and it is not accessed anywhere out of the defined scope (block).

Some examples of the block scope may be found below:

for (int i = 0; i < 5; i++) {
    printf("This is local i %d\n", i);
}
printf("Print the local i %d\n", i);

Since, access to the local variable i is not possible outside the cycle for. Then, the following error will be presented:

 error: 'i' undeclared (first use in this function)

One more example, this one is on my favorite list:

int a = 5;
switch (a) {
  case 0:
  case 1:
  case 2:
    break;
  case 3:
  case 4:
  {
    int a = 4;
  }
  case 5:
  {
    int a = 90;
    printf("What will be on the screen %d\n", a);
  }
  break;
}

It is not hard to notice that there is local variable a in case 5, so the result will be:

What will be on the screen 90

Function Scope

As for the function scope, a variable or any entity may be accessed only in the function.

The following code snippet demonstrates this statement:

int main(void) {
    return 0;
}

int f1(int a, int b) {
    return 0;
}

int f2() {
    printf("%d\n", a);
}

It can be easily noticed that variable 'a' is not defined in the function f2, although it is defined in function f1.

Binding

A lot of people asked themselves why should they know the Binding? That is clear. Binding and Scope go together. Let`s observe Binding.

First of all, let`s take a look at 2 code snippets:

int func() {
    return 0;
}

int func;

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

and

int func() {
    int func = 0;
    printf("This is func variable %d\n", func);
    return 0;
}
int main() {
    func();
    return 0;
}

It can be said just that C forbids to use the same identifier in the same scope. So, the first example throws the error.

Binding - the process of associating entities in source code (variables, functions) to their corresponding memory.

The variable in C has one of the following bindings:

- External binding - any entity that is applied in any place of the program, for instance global variables of functions.

- Internal binding- any entity that is applied internally, for instance, static variables in files.

- No binding- any entity in a block, such as local variables in any block or in function prototype.

Let`s observe the following example:

int global_var; // External or Internal binding. Global variable with the file or the entire program scope

static int static_global_var = 0; // Internal binding. Static variable with file scope

int function() {
    int local_var_in_function; // No binding
}

Static variables in C

Finally, we have approached the main topic of the article after some long introduction.

The syntax of the static variable is

static  variable_name [ = variable_value];

Where the variable_value may be empty, it means that the memory will be filled with zeroes (that depends on the type).

Static variables in C

Local static variable inside a function

A static variable may be defined in any function and will be available until the program is completed.

The following example demonstrates the static variable in a function.

int func() {
    // Static variable that has initial value
    static int first_read = 0;

    if (first_read == 0) {
        first_read = 1;
        // Do some initialization here
        printf("::: Initialization :::\n");
    } else {
        // Do general business
        printf("::: General business :::\n");
    } 
    return 0;
} 

int main() {
    for (int i = 0; i < 3; i++)
        func();

    return 0;
} 

There variable first_read remains in memory during the entire program execution. In other words, it will be destroyed once the program is completed. At the same time, the scope of the variable first_read is only in the function func().

The output of the program will be:

::: Initialization :::
::: General business :::
::: General business :::

Random value initializer for static variable

Any static variable in C may only be initialized with a constant value.

Let's overview the following code that, as a result, the code is throwing the error:

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

int main() {
    time_t t;
    srand(time(0));
    static int variable = rand() % 100;
    return 0;
} 

The error will be thrown:

error: initializer element is not constant

Static variable in a structure

Static variables cannot be defined in a structure. In other words, the following definition is impossible:

typedef struct __struct_with_static_var {
    int var1;
    static int var2;
} STATIC_VAR_STRUCT;

It will be reported that the storage class cannot be specified.

Global static variable

There may be a case that a static variable may be defined globally in a file. There, the static keyword means that the variable can only be accessed in the defined file. So, the scope of the static variable is only the file where the variable is defined. Any function in the file can only access the static variable. There is no possibility to access this variable from another file.

Let's observe the following code:

// File1.c
int main() {
    printf("%d\n", static_variable);
    return 0;
} 

// File2.c
static int static_variable = 10;

static int static_func() {
    printf("This is the Static Func from the second file\n");
    printf("Static variable [%d]\n", static_variable);
    return 0;
} 

It may be noticed that the static_variable is defined in the second file and cannot be accessed inside the main() function.

The following error will be presented during the compilation:

error: ‘static_variable’ undeclared

Global static variables in a file are a great opportunity to make the variables 'private'.

The following code demonstrates this behavior:

// File1.c
int variable = 20;

int main() {
    printf("%s::: Local variable [%d] Addr [%p]\n", __func__, variable, &variable);
    increment_static_variable();
    return 0;
} 

// File2.c
static int static_variable = 10;

// Increment static variable
static int increment_static_variable() {
    printf("This is the Static Func from the second file\n");
    printf("Static variable [%d]\n", static_variable);
    return 0;
} 

The variable is defined in both files, although it has different scopes, where the static variable is only accessed in File2.c, at the same time, it can be modified through the function increment_static_variable().

There is an interesting thing in the output that is constructed:

main::: Local variable[20] Addr[0x56027dd1f014]
increment_static_variable:: : New result of static variable[11] Addr[0x56027dd1f010]

It can be noticed that addresses have 4 bytes of differences. Does it really mean that the variable may be read and modified out of the scope (File2.c)?

Theoretically, it is the topic of another article, but the short answer is 'Yes'.

Static functions in C

All functions in C are global. This statement can be easily checked by the readelf utility.

Let's compile the simple program:

int f1() { return 0; }

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

Then start readelf -s <program_name>

There is a Global binding for the function f() in the Symbol table.

 Symbol table '.symtab' contains 36 entries:
 Num:    Value               Size Type    Bind   Vis       Ndx Name
 ...
 27 :    0000000000001129    15   FUNC    GLOBAL DEFAULT   14  f1
 ...

However, defining the function as static may restrict usage of the Global scope.

int f1() { return 0; }
static int f2() { return 0; }

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

Static function f2() is accessible only in the file and cannot be accessed from another file.

In case of comparing the output from readelf -s there will be 2 entries one is the local where the other one is global.

 Symbol table '.symtab' contains 37 entries:
 Num:    Value               Size Type    Bind   Vis       Ndx Name
 ...
 12 :    0000000000001138    15   FUNC    LOCAL  DEFAULT   14  f2
 ...
 28 :    0000000000001129    15   FUNC    GLOBAL DEFAULT   14  f1
 ...

A case from life

During the 'Cool feature' implementation, I had a strong need to use one 3rd-party library that was released as the static one. Unfortunately, I did not have access to the source code and was not able to modify it. Although, it has the complex code defined below 😊 (I do not think that I need to clarify):

// libcool_print_data.a
#include <stdio.h>

int print_data(int *array, int array_size) {
    int status = 10;
    printf("[LIB][PRINT][INFO] :: Print data started\n");
    if (0 == array_size)       status = 01;
    if (array_size == NULL)    status = 02;
    if (array_size > 100)      status = 03;
    if (status != 0) {
        printf("[LIB][PRINT][ERROR] :: Check the data. Status [%d]\n", status);
        return status;
    }
    for (int i = 0; i < array_size; i++) {
        printf("[LIB][PRINT] :: Idx [%2d] Element [%d]\n", i, *(array + i));
    }

    return status;
} 

I was so happy to get this functionality in my code that also was an extremely complex and had the only one main.c file:

// main.c
#include <stdio.h>
#include "cool_print_header.h"

int print_data(int *array, int array_size) {
    printf("Just print the data only\n");
    printf("No checks! I know what I am processing...\n");
    for (int i = 0; i < array_size; i++) {
        printf("::: %d<->%d :::\n", i, array[i]);
    }
    return 0;
}

int main() {
    int ar[5] {0, 1, 2, 3, 4};
    print_data(ar, 5);
    return 0;
}

Compile the program using the following commands:

gcc -g -c main.c

gcc -g -o main main.o -L${PWD} -lcool_print_data

Then the issue was appearing to me:

multiple definition of 'print_data'

I really desired to have my own print_data() in my program and not to call the function from the library, at the same time, I could not recompile the static library and I did not want to change the name of my local function. There is the simple solution is to make the function print_data static.

static int print_data(int *array, int array_size) { }

Unfortunately, it works only for the file. If there are multiple calls from many files this approach won't work.

Conclusion

To conclude, this is the first article describing the code static keyword in C. As for C++, it will be described in the next article.

You may probably forget about Scope and Binding, although, it is important to know if you are working with static variables and functions. Static keyword is applicable to variables and functions. Also, you must consider where the variable or function has been defined, since it has its own scope. As for the functions defined with static keyword, they only be called in the defined file.