Dr. Roger Ianjamasimanana

How to use variables in C?

By Dr. Roger Ianjamasimanana

1. What are variables in C?

Variables are containers to store data that can be manipulated throughout the program’s execution. This lesson explores different aspects of variables in C, including, the types, scope, lifetime, and best practices. I provdie detailed explanations and practical examples to help you understand the concept.

2. Types of C variables

In C, variables can be categorized based on their scope, lifetime, and storage class. The primary types include local variables, global variables, and static variables.

2.1 Local variables

Local variables are declared within a function or a block and are accessible only within that function. They are created when the function is called and destroyed when the function exits.

// Example of local variables
#include <stdio.h>

void display_age() {
    int age = 25; // Local variable
    printf("Age: %d\n", age);
}

int main() {
    display_age();
    // printf("Age: %d\n", age); // Error: 'age' is not accessible here
    return 0;
}

In the above program, the variable age is declared within the function display_age; therefore, it can only be accessed within that function. If we had tried to access it within another function, such as in main, we would have gotten an error.

2.2 Global variables

Global variables are declared outside of all functions and are accessible from any function within the program. They exist for the duration of the program’s execution.

// Example of global variables
#include <stdio.h>

int global_age = 30; // Global variable

void display_global_age() {
    printf("Global age: %d\n", global_age); // We can access the global variable here
}

int main() {
    display_global_age(); 
    global_age = 35; // modifying the global variable
    display_global_age(); 
    return 0;
}

Dangers of global variables

Overusing global variables or using them improperly can lead to significant issues in your programs. Since they can be accessed and modified by any function, it’s easy for their values to be changed unintentionally. This can lead to bugs that are difficult to trace. Let’s illustrate it in the following example.

// Example of unintended modification with similar variable names
#include <stdio.h>

// Global variables for sum and count
double sum = 0.0; // Global variable for sum
int count = 0;     // Global variable for count

// Function to add a number to the sum
void add_number(double num) {
    int count = 0; // Mistakenly declares a local variable 'count', shadowing the global 'count'
    sum += num;
    count += 1;     // This only modifies the local 'count', leaving global 'count' unchanged
}

// Function to reset the sum and count
void reset_calculations() {
    sum = 0.0;
    count = 0;
}

int main() {
    add_number(10.0);
    add_number(20.0);
    add_number(30.0);
    
    printf("Global Sum: %.2lf\n", sum);   // Outputs: Global Sum: 60.00
    printf("Global Count: %d\n", count); // Outputs: Global Count: 0
    if(count != 0) {
        printf("Average: %.2lf\n", sum / count);
    } else {
        printf("Average: Undefined (Count is 0)\n"); // Correctly handles division by zero
    }
    
    reset_calculations(); // Resets sum and count
    return 0;
}

The above program calculates the sum and number (count) of multiple numbers entered by the user using global variables. It has two primary functions: add_number(), which adds a given number to the variable sum and increments the count, and reset_calculations(), which resets both the sum and count to zero. In the main() function, the program adds three numbers to the sum, prints the sum and count, and attempts to calculate and display the average. Additionally, it includes a check to handle cases where the count is zero to prevent division by zero errors. The mistake here is initializing the count variable again inside the add_number() function. Every time we call add_number(), it is the local variable count that gets incremented, and the global variable count remains unchanged.

The output of the above program is

Global Sum: 60.00
Global Count: 0
Average: Undefined (Count is 0)

Now, remove the int count=0 inside add_number() and observe the output. The program should now correctly calculate the sum, count, and the corresponding average of the added values.

2.3 Static variables

Static variables retain their value between function calls. When they are declared inside a function, they preserve their value across multiple invocations of that function.

// Example of static variables
#include <stdio.h>

void counter() {
    static int count = 0; // Static variable
    count++;
    printf("Count: %d\n", count);
}

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

The output of the above program is:

Count: 1
Count: 2
Count: 3

Can you guess the output if we remove the static keyword before the variable count? Static variables have the following properties.

  • Static variables are allocated when the program starts and deallocated only when the program terminates. This means they retain their value throughout the entire execution of the program, unlike automatic (local) variables, which are created and destroyed each time a function is called and exited.
  • If not explicitly initialized, static variables are automatically initialized to zero for numeric types or NULL for pointers.
  • They are ideal for situations where a function needs to remember information between calls without using global variables
  • They help in encapsulating data within a function or file, enhancing modularity and reducing the risk of unintended interactions.
  • In multi-threaded applications, static variables are shared across all threads unless thread-local storage is explicitly used.

Did you get our challenge right? Now let’s remove the static keyowrd and observe the output.

#include <stdio.h>

void counter() {
    int count = 0;
    count++;
    printf("Count: %d\n", count);
}

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

The output of the above program is:

Count: 1
Count: 1
Count: 1

2.4 Register variables

In the C programming language, the register keyword is used to suggest to the compiler that a particular variable will be heavily utilized. The primary goal of this hint is to store the variable in a machine register rather than in RAM, potentially leading to more efficient and faster program execution. However, it's important to note that modern compilers often optimize variable storage automatically, making the explicit use of register obsolete or less critical.

Declaring register variables

To declare a variable as a register variable, you prefix its declaration with the register keyword. Here are some examples:

register int counter;
register char letter;

The register keyword is applicable only to automatic variables (those declared within a function) and to the formal parameters of a function. When used with function parameters, the syntax is as follows:

void process_data(register unsigned int data, register long count) {
    register int index;
    // Function implementation
}

While the register keyword serves as a recommendation to the compiler, it does not enforce the placement of the variable in a register. Compilers may choose to ignore this hint based on various factors such as register availability and optimization strategies. Additionally, there are limitations on the number and types of variables that can be designated as register variables, which can vary between different hardware architectures.

✍️
Modern compiler

Modern compilers are highly optimized and often make better decisions about register allocation without explicit hints from the programmer. As a result, the use of the register keyword has become less common in contemporary C programming.

Below is an example demonstrating the declaration and use of register variables within a function:

#include <stdio.h>

void compute_sum(register int a, register int b) {
    register int sum = a + b;
    printf("Sum: %d\n", sum);
}

int main() {
    compute_sum(5, 10);
    return 0;
}

In this example, the variables a, b, and sum are suggested to be stored in registers for potentially faster access. However, the actual placement depends on the compiler's optimization process.

3. Variable scope and lifetime

The scope of a variable determines where it can be accessed within the code, while its lifetime defines how long the variable exists in memory during program execution.

3.1 Variable scope in C

Scope can be classified into three categories:

  • Local scope: accessible only within the function or block where it is declared.
  • Global scope: accessible throughout the entire program.
  • Block scope: accessible only within the block (e.g., loops, conditionals) where it is defined.

The program below demonstrates the use of variable scope in C.

// Example demonstrating variable scope
#include <stdio.h>

int global_var = 10; // Global scope

void display() {
    int local_var = 20; // Local scope
    printf("Global variable: %d\n", global_var);
    printf("Local variable: %d\n", local_var);
}

int main() {
    display();
    printf("Global variable in main: %d\n", global_var);
    // printf("Local variable in main: %d\n", local_var); // Error: 'local_var' is not accessible here
    return 0;
}

3.2 Variable lifetime

The lifetime of a variable defines the period during which the variable exists in memory. It is determined by the storage class of the variable. It shows how a global variable, declared outside of all functions, is accessible within any function of the program, while a local variable, declared inside a function, is only accessible within that function.

// Example demonstrating variable lifetime
#include <stdio.h>

void demoLifetime() {
    int local_var = 5; // Lifetime: duration of the function call
    printf("Inside function: %d\n", local_var);
}

int main() {
    demoLifetime();
    // printf("Outside function: %d\n", local_var); // Error: 'local_var' does not exist here
    return 0;
}

4. Variable declaration and initialization

Declaring and initializing variables correctly is fundamental in C programming. Proper declaration ensures that variables are allocated memory, while initialization assigns them initial values.

4.1 Declaration

Variable declaration specifies the type and name of the variable. It informs the compiler about the variable's data type and reserves memory for it.

// Example of variable declarations
#include <stdio.h>

int main() {
    int age; // Declaration of an integer variable
    float salary; // Declaration of a float variable
    char grade; // Declaration of a char variable
    return 0;
}

Proper variable declarations ensures that memory is used efficiently, as the compiler can allocate just the right amount of space needed for each data type.

4.2 Initialization

Initialization assigns an initial value to a variable at the time of declaration. It can be done using assignment operators or initializer lists.

// Example of variable initialization
#include <stdio.h>

int main() {
    int age = 25; // Initialization with assignment
    float salary = 50000.50f; // Initialization with assignment
    char grade = 'A'; // Initialization with assignment

    // Initialization using initializer list (for arrays)
    int scores[3] = {85, 90, 95};

    printf("Age: %d\n", age);
    printf("Salary: %.2f\n", salary);
    printf("Grade: %c\n", grade);
    return 0;
}

5. Best practices

Below are some recommended practices about variables in C:

✍️
Use meaningful names

Choose descriptive names for variables to enhance code readability.

✍️
Initialize variables

Always initialize variables to prevent undefined behavior.

✍️
Consistent formatting

Maintain a consistent code style for defining and using variables.

✍️
use typedef for simplification

Use typedef to create aliases for complex types, reducing repetitive syntax.

✍️
Encapsulate data

Keep related data grouped together using structures to maintain modularity.

6. Example cases

To solidify the understanding of variables in C, let's explore a practical example that combines different types of variables.

// Comprehensive example using various types of variables
#include <stdio.h>

// Global variable
int global_count = 100;

// Structure definition
struct Employee {
    char name[50];
    int id;
    float salary;
};

// Function demonstrating local and static variables
void incrementCounter() {
    static int counter = 0; // Static variable retains its value between calls
    counter++;
    printf("Counter: %d\n", counter);
}

int main() {
    // Local variables
    int local_age = 28;
    float localSalary = 55000.75f;

    // Pointer variable
    int *ptr = &local_age;

    // Array of structures
    struct Employee employees[2] = {
        {"Alice", 101, 60000.50f},
        {"Bob", 102, 65000.75f}
    };

    // Using global variable
    printf("Global Count: %d\n", global_count);

    // Accessing local variables through pointer
    printf("Local Age: %d\n", *ptr);
    printf("Local Salary: %.2f\n", localSalary);

    // Accessing structure members
    for(int i = 0; i < 2; i++) {
        printf("Employee %d: %s, ID: %d, Salary: %.2f\n", i+1, employees[i].name, employees[i].id, employees[i].salary);
    }

    // Demonstrating static variable
    incrementCounter(); // Output: Counter: 1
    incrementCounter(); // Output: Counter: 2

    return 0;
}

Explanation

  • Global variable: global_count is accessible throughout the program.
  • Local variables: local_age and localSalary are accessible only within the main function.
  • Pointer variable: ptr holds the address of local_age, allowing indirect access.
  • Array of structures: employees holds multiple Employee structures, facilitating batch processing.
  • Static variable: counter inside incrementCounter() retains its value between function calls.

feature-top
Readers’ comment
feature-top
Log in to add a comment
🔐 Access