C Programming

Variables and data storage

C Programming Exercise 2 of 7

Variables and data storage

Master this concept with detailed explanations and interactive coding examples.

7 Questions
15 Minutes
0% Completed
#include <stdio.h> int main() { printf("Hello"); return 0; }

Interview Questions & Answers

Technical Interview

Variables and Data Storage

This section delves into how data is stored in memory, the scope and lifetime of variables, storage classes, type qualifiers, and memory layout in C programming.

1. What are the different storage classes in C? Explain each with an example.

Answer: Storage classes define the scope (visibility), lifetime (duration), and initial value of a variable. C provides four storage classes:

  • auto: The default storage class for local variables. They are created when the block is entered and destroyed upon exit. They can only be accessed within the block where declared.
  • register: Suggests to the compiler to store the variable in a CPU register for faster access. You cannot take the address of a register variable using the & operator.
  • static: For local variables, it retains its value between function calls and is initialized only once. For global variables/functions, it limits their scope to the file where they are declared.
  • extern: Declares a variable or function that is defined in another file or later in the same file. It extends the visibility of global variables across multiple files.
#include <stdio.h>
int globalVar = 100; // extern by default

void function() {
auto int localVar = 10; // 'auto' is optional, local by default
static int staticVar = 0; // Static local variable
register int counter = 5; // Request for register storage
staticVar++;
printf("Local: %d, Static: %d, Register: %d\n", localVar, staticVar, counter);
}

int main() {
function(); // Output: Local: 10, Static: 1, Register: 5
function(); // Output: Local: 10, Static: 2, Register: 5 (staticVar retains value)
extern int globalVar; // Not necessary here as globalVar is defined above
printf("Global: %d\n", globalVar);

return 0;
}

2. Explain the difference between variable declaration and variable definition.

Answer:

  • Declaration: Introduces the name and type of a variable to the compiler without allocating memory for it. It tells the compiler that the variable exists somewhere else. This is done using the extern keyword.
  • Definition: Allocates memory for the variable and may also initialize it. A definition also serves as a declaration unless the extern keyword is used.
#include <stdio.h>
extern int x; // Declaration: x is defined elsewhere (no memory allocation here)
int y = 20; // Definition: memory allocated for y and initialized to 20

void function() {
extern int x; // Declaration within function scope
printf("x = %d\n", x); // Accessing x defined elsewhere
}

int x = 10; // Definition: memory allocated for x

int main() {
function(); // Output: x = 10
printf("y = %d\n", y); // Output: y = 20
return 0;
}

3. What are global variables? What are their advantages and disadvantages?

Answer: Global variables are variables declared outside of all functions, typically at the top of a program after header files. They have file scope and are accessible throughout the entire program (from the point of declaration to the end of the file) and across multiple files if declared with extern.

  • Advantages:
    • Easy to share data between multiple functions without passing parameters.
    • Convenient for constants that need to be accessed everywhere.
  • Disadvantages:
    • They occupy memory for the entire program execution.
    • They reduce modularity and make code less maintainable.
    • They can lead to naming conflicts and unintended side effects (any function can modify them).
    • Debugging becomes difficult as data flow is not explicit.
#include <stdio.h>
int counter = 0; // Global variable

void increment() {
counter++; // Modifies global variable
}

void reset() {
counter = 0; // Modifies global variable
}

int main() {
increment();
increment();
printf("Counter: %d\n", counter); // Output: Counter: 2
reset();
printf("After reset: %d\n", counter); // Output: After reset: 0
return 0;
}

4. What is the difference between static and extern storage classes?

Answer: Here's a comparison table highlighting the key differences:

Feature static extern
Scope (Global) Limited to the file where declared (internal linkage). Visible across multiple files (external linkage).
Scope (Local) Variable retains its value between function calls. Not applicable to local variables.
Lifetime (Local) Entire program execution. Not applicable to local variables.
Default Initialization Initialized to 0 if not explicitly initialized. References an existing variable, no initialization.
Usage To hide data within a file or preserve value across calls. To use a global variable defined in another file.
/* File: file1.c */ #include <stdio.h> static int hidden = 50; // Only accessible within file1.c int global = 100; // Accessible in other files with 'extern'
void showHidden() {
printf("Hidden in file1: %d\n", hidden);
}

/* File: file2.c */
#include <stdio.h>
extern int global; // Declaring global from file1.c
// extern int hidden; // Error! Cannot access static variable from another file

int main() {
printf("Global from file1: %d\n", global);
// showHidden(); // Can't call unless function is declared
return 0;
}

5. What is type qualifier? Explain const and volatile with examples.

Answer: Type qualifiers provide additional information about how a variable should be accessed or modified by the compiler. The two main type qualifiers in C are const and volatile.

  • const: Declares that a variable's value cannot be modified after initialization. It helps in preventing accidental modifications and allows compiler optimizations.
  • volatile: Tells the compiler that the variable's value may change at any time without any action being taken by the code nearby. This prevents the compiler from optimizing away seemingly redundant reads or writes. It's essential for memory-mapped I/O, global variables modified by interrupt service routines (ISRs), and variables accessed by multiple threads.
#include <stdio.h> 
#include <setjmp.h> // For setjmp/longjmp example
const float PI = 3.14159; // Cannot modify PI

volatile int statusFlag; // Could be changed by hardware or ISR

// Simulating an interrupt service routine
void interrupt_handler() {
statusFlag = 1; // Modifies volatile variable
}

int main() {
// PI = 3.14; // Error! Cannot modify const variable

statusFlag = 0;
printf("Status before: %d\n", statusFlag);

// Simulate interrupt
interrupt_handler();

// Without 'volatile', the compiler might optimize this check
// by assuming statusFlag hasn't changed
if (statusFlag) {
    printf("Status changed by interrupt: %d\n", statusFlag);
}

// Const pointer examples
int value = 10;
const int *ptr1 = &value;   // Pointer to const int (data is const)
int *const ptr2 = &value;    // Const pointer to int (pointer is const)
const int *const ptr3 = &value; // Const pointer to const int

// *ptr1 = 20; // Error! Cannot modify data through ptr1
ptr1 = NULL; // OK: modifying the pointer itself

*ptr2 = 20;  // OK: modifying data through ptr2
// ptr2 = NULL; // Error! ptr2 is const pointer

return 0;
}

6. Explain the memory layout of a C program. Where are variables stored?

Answer: A C program's memory is typically divided into several segments:

  • Text (Code) Segment: Contains the compiled machine code (instructions). It's usually read-only and sharable.
  • Data Segment (Initialized Data): Stores global and static variables that are explicitly initialized with non-zero values.
  • BSS Segment (Block Started by Symbol): Stores global and static variables that are uninitialized or initialized to zero. They are initialized to zero by the kernel before program execution.
  • Heap Segment: Used for dynamic memory allocation (malloc, calloc, realloc). Grows upward (towards higher addresses).
  • Stack Segment: Stores local variables, function parameters, and return addresses. Grows downward (towards lower addresses). Each function call creates a stack frame.
#include <stdio.h> 
#include <stdlib.h>
int global_init = 100; // Stored in Data Segment
int global_uninit; // Stored in BSS Segment (initialized to 0)
static int static_global = 50; // Stored in Data Segment

void function() {
static int static_local = 25; // Stored in Data Segment (initialized once)
int local_var = 10; // Stored in Stack
printf("Local var (stack) address: %p\n", (void)&local_var);
printf("Static local (data) address: %p\n", (void)&static_local);
}

int main() {
int heap_ptr = (int)malloc(sizeof(int)); // Stored in Heap
*heap_ptr = 500;


printf("Code (text) segment address of main: %p\n", (void*)main);
printf("Global init (data) address: %p\n", (void*)&global_init);
printf("Global uninit (bss) address: %p\n", (void*)&global_uninit);
printf("Static global (data) address: %p\n", (void*)&static_global);
printf("Heap allocated address: %p\n", (void*)heap_ptr);

function();

free(heap_ptr);
return 0;
}

7. What is the difference between automatic and static variables?

Answer:

  • Automatic variables (default for local variables):
    • Created when the block is entered and destroyed when the block exits.
    • Stored in the stack segment.
    • Not initialized by default; contain garbage values.
    • Each function call creates new instances of automatic variables.
  • Static variables (local scope with static keyword):
    • Created once and persist throughout program execution.
    • Stored in the data or BSS segment (depending on initialization).
    • Automatically initialized to 0 if not explicitly initialized.
    • Retain their value between function calls.
#include <stdio.h>
void demonstrate() {
int auto_var = 0; // Automatic: created each call
static int static_var = 0; // Static: created once


auto_var++;
static_var++;

printf("Auto: %d, Static: %d\n", auto_var, static_var);
}

int main() {
printf("First call:\n");
demonstrate(); // Output: Auto: 1, Static: 1


printf("Second call:\n");
demonstrate(); // Output: Auto: 1, Static: 2

printf("Third call:\n");
demonstrate(); // Output: Auto: 1, Static: 3

return 0;
}

8. Explain variable scope in C with examples.

Answer: Scope determines where a variable can be accessed in a program. C has four types of scope:

  • Block scope: Variables declared inside a block {} are accessible only within that block and any nested blocks.
  • Function scope: Applies only to labels (used with goto). Labels are visible throughout the entire function.
  • File scope: Global variables and functions declared outside any block are accessible from the point of declaration to the end of the file.
  • Function prototype scope: Applies to parameter names in function prototypes (they don't need to match actual parameter names).
#include <stdio.h>
int file_scope_var = 100; // File scope (global)

void function_prototype_scope(int param); // 'param' has function prototype scope

void demonstrate_scope() {
// Block scope begins
int block_var = 50; // Block scope variable


{
    // Nested block
    int nested_var = 25; // Accessible only in this nested block
    printf("Nested block: nested_var = %d\n", nested_var);
    printf("Nested block: block_var = %d\n", block_var); // Can access outer block
    printf("Nested block: file_scope_var = %d\n", file_scope_var); // Can access global
}

// printf("%d", nested_var); // Error! nested_var not accessible here

// Label with function scope
goto cleanup;
cleanup: // 'cleanup' label has function scope
printf("Cleanup code executed\n");
}

int main() {
int main_local = 10; // Block scope within main


if (main_local > 0) {
    int if_block_var = 30; // Block scope within if
    printf("If block: %d, %d\n", main_local, if_block_var);
}

// printf("%d", if_block_var); // Error! Not accessible

demonstrate_scope();

return 0;
}

9. What is the lifetime of a variable? How does it differ from scope?

Answer: Lifetime (or duration) refers to the period during which a variable exists in memory and retains its value. Scope determines where a variable can be accessed. They are related but distinct concepts:

  • Automatic lifetime: Local non-static variables. Created when block enters, destroyed when block exits. Scope is limited to the block.
  • Static lifetime: Global variables and static local variables. Created when program starts, destroyed when program ends. Global scope (if global) or block scope (if static local).
  • Allocated lifetime: Dynamically allocated memory (malloc). Created when allocated, destroyed when freed. Accessed via pointers (scope of pointer variable is separate).
#include <stdio.h> 
#include <stdlib.h>
int *create_array(int size) {
int arr = (int)malloc(size * sizeof(int)); // Allocated lifetime
for (int i = 0; i < size; i++) {
arr[i] = i * 10;
}
return arr; // Pointer returned, but memory continues to exist
}

int *dangerous_function() {
int local_array[5] = {1, 2, 3, 4, 5}; // Automatic lifetime
return local_array; // WRONG! Returning pointer to automatic variable
// 'local_array' will be destroyed when function returns
}

int main() {
int *ptr = create_array(3);


// Memory still exists (allocated lifetime)
printf("Array[0] = %d\n", ptr[0]); // Valid

free(ptr); // Memory destroyed, lifetime ends

// ptr is now dangling (scope of pointer variable continues,
// but the memory it points to no longer exists)

return 0;
}

10. Explain the concept of variable shadowing (or name hiding) in C.

Answer: Variable shadowing occurs when a variable declared within a certain scope (like an inner block) has the same name as a variable declared in an outer scope. The inner variable "shadows" or "hides" the outer one, making the outer variable inaccessible within the inner scope (unless using scope resolution in C++).

#include <stdio.h>
int value = 1000; // Global variable

int main() {
int value = 100; // Local variable in main (shadows global)


printf("Local value in main: %d\n", value); // Output: 100

{
    int value = 10; // Shadows both global and main's local
    printf("Inner block value: %d\n", value); // Output: 10
    
    // In C, there's no direct way to access the outer local variable
    // But we can access the global using extern (if not shadowed)
    {
        extern int value; // Refers to global, not the block's local
        printf("Global value (using extern): %d\n", value); // Output: 1000
    }
}

printf("Back in main: %d\n", value); // Output: 100

return 0;
}

11. What is the default initial value of different types of variables?

Answer: Default initialization depends on the storage class and scope:

Variable Type Storage Class Default Initial Value
Local (automatic) auto (default) Garbage (indeterminate value)
Register register Garbage (indeterminate value)
Static local static 0 (zero-initialized)
Global extern (default) 0 (zero-initialized)
Static global static 0 (zero-initialized)
#include <stdio.h>
int global_var; // Default: 0
static int static_global; // Default: 0

void function() {
static int static_local; // Default: 0
int auto_local; // Default: Garbage
register int reg_var; // Default: Garbage (if possible)


printf("Static local: %d\n", static_local);
printf("Auto local: %d\n", auto_local); // May print garbage
printf("Register var: %d\n", reg_var);   // May print garbage
}

int main() {
int local_main; // Default: Garbage


printf("Global: %d\n", global_var);
printf("Static global: %d\n", static_global);
printf("Local in main: %d\n", local_main); // Garbage

function();

return 0;
}

12. Explain the difference between int* p, int *p, and int * p in terms of variable declaration style.

Answer: All three syntaxes are functionally identical to the compiler; they all declare a pointer to an integer. The spacing around the asterisk (*) is a matter of coding style and preference, not semantics.

  • int* p (pointer-to-int style): Emphasizes the type (int*) of the variable p. This style can be misleading when declaring multiple variables on one line.
  • int *p (C-style): Emphasizes that the dereference (*p) yields an int. This is the traditional C style and avoids confusion in multi-declarations.
  • int * p (spaced): Less common but still valid. Some developers use this for visual clarity.
#include <stdio.h>
int main() {
// All three are equivalent in terms of p1 being a pointer to int
int* p1; // Style 1: pointer-to-int type
int *p2; // Style 2: * attached to variable name (traditional C)
int * p3; // Style 3: spaced (visual preference)


int a = 10, b = 20, c = 30;

p1 = &a;
p2 = &b;
p3 = &c;

printf("p1 points to: %d\n", *p1);
printf("p2 points to: %d\n", *p2);
printf("p3 points to: %d\n", *p3);

// The important difference: declaring multiple pointers
int* ptr1, ptr2;        // ptr1 is int*, ptr2 is int (NOT a pointer!)
int *ptr3, *ptr4;       // Both ptr3 and ptr4 are int*

printf("\nSize checks:\n");
printf("Size of ptr1 (int*): %zu\n", sizeof(ptr1));
printf("Size of ptr2 (int): %zu\n", sizeof(ptr2));
printf("Size of ptr3 (int*): %zu\n", sizeof(ptr3));
printf("Size of ptr4 (int*): %zu\n", sizeof(ptr4));

return 0;
}

13. What is the purpose of the register storage class? Is it guaranteed to store the variable in a CPU register?

Answer: The register storage class is a hint to the compiler that the variable will be heavily used and should be stored in a CPU register for faster access. However:

  • It's just a suggestion; the compiler may ignore it due to register pressure or optimization decisions.
  • You cannot take the address of a register variable using the & operator because registers don't have memory addresses.
  • Modern compilers are very good at register allocation and often ignore register keywords, making it largely obsolete in contemporary programming.
  • It can only be applied to local variables and function parameters.
#include <stdio.h>
int main() {
register int counter; // Hint to compiler: store in register if possible


// int *ptr = &counter; // Compilation error! Cannot take address of register variable

for (counter = 0; counter < 1000; counter++) {
    // Loop body - compiler might optimize access if counter is in register
    printf("Iteration: %d\r", counter); // \r for overwriting line
}
printf("\nLoop completed\n");

// Modern compilers often ignore 'register' as they optimize better
// Example of what compiler might do internally:
// Without register: auto int x; (maybe in register anyway)
// With register: register int x; (can't use &, otherwise same)

return 0;
}

14. Explain the concept of type promotion in variable assignments and expressions.

Answer: Type promotion (or implicit type conversion) occurs when variables of different types are used in expressions or assignments. C automatically converts operands to a common type according to specific rules to prevent data loss:

  • Integer promotion: Types smaller than int (char, short) are promoted to int or unsigned int before operations.
  • Usual arithmetic conversions: For binary operations, the "lower" type is promoted to the "higher" type (e.g., intfloatdouble).
  • Assignment promotions: The value on the right is converted to the type of the left-hand variable.
#include <stdio.h>
int main() {
// Integer promotion
char c = 10;
short s = 20;
int result = c + s; // c and s are promoted to int before addition


// Usual arithmetic conversion
int i = 5;
float f = 2.5;
float result2 = i + f; // i is promoted to float (5.0) before addition

// Assignment promotion
int i2;
float f2 = 3.14;
i2 = f2; // f2 (3.14) is converted to int (3) - truncation occurs!

printf("c + s = %d (int result)\n", result);
printf("i + f = %f (float result)\n", result2);
printf("i2 = %d (after assigning float 3.14)\n", i2);

// Demonstration of integer promotion with char
char a = 200; // Might overflow if char is signed (range -128 to 127)
char b = 100;
int sum = a + b; // a and b promoted to int, sum is 300 (correct)
char sum_char = a + b; // Result (300) is truncated to fit in char
                       // (300 mod 256 = 44 if unsigned, or implementation-defined if signed)

printf("Sum as int: %d\n", sum);
printf("Sum as char: %d\n", sum_char); // May not be 300

return 0;
}

15. How does C handle variable initialization? What happens if you don't initialize a variable?

Answer: Variable initialization in C depends on the storage class and scope:

  • Global and static variables: Automatically initialized to zero (or null for pointers) if not explicitly initialized. This happens at compile-time or program startup.
  • Local (automatic) variables: Not initialized automatically. They contain indeterminate values (garbage) from whatever was previously in that stack memory. Using them before assignment leads to undefined behavior.
  • Dynamic memory (malloc): Not initialized; contains garbage. calloc initializes to zero.
#include <stdio.h> 
#include <stdlib.h>
int global_var; // Automatically initialized to 0
static int static_var; // Automatically initialized to 0

void function() {
static int static_local; // Automatically initialized to 0
int local_var; // NOT initialized - contains garbage


printf("In function:\n");
printf("  static_local: %d (zero-initialized)\n", static_local);
printf("  local_var: %d (garbage - may be anything)\n", local_var);

// Best practice: always initialize local variables
local_var = 42; // Now initialized
}

int main() {
int local_main; // NOT initialized - contains garbage


printf("Global: %d (zero-initialized)\n", global_var);
printf("Static: %d (zero-initialized)\n", static_var);
printf("Local in main: %d (garbage)\n", local_main);

function();

// Dynamic memory
int *malloc_ptr = (int*)malloc(sizeof(int));
int *calloc_ptr = (int*)calloc(1, sizeof(int));

printf("\nDynamic memory:\n");
printf("  malloc: %d (garbage)\n", *malloc_ptr);
printf("  calloc: %d (zero-initialized)\n", *calloc_ptr);

free(malloc_ptr);
free(calloc_ptr);

// Best practice: initialize before use
int safe_var = 0; // Explicit initialization

return 0;
}

0% read

Interactive Code Playground

Live Coding
main.c
1
$ Ready to run your code...
Tips:
  • Use Ctrl + Enter to run code
  • Include <stdio.h> for printf/scanf
  • main() function must return int
Sample Snippets:

Detailed Explanation

Variables and data storage

Understanding Variables in C

A variable in C is a named storage location in memory that holds a value which can be modified during program execution. Each variable has:

  • Data Type - Determines size and layout (int, float, char, etc.)
  • Name - Identifier to access the value
  • Value - The actual data stored
  • Memory Address - Location where it's stored
  • Scope - Where it can be accessed
  • Lifetime - How long it exists

Key Points to Remember:

Memory Allocation

Variables occupy memory based on their data type. int = 4 bytes, char = 1 byte, float = 4 bytes on most systems.

Naming Rules

Must start with letter or underscore, case-sensitive, cannot use keywords. Use descriptive names like 'studentCount' not 'x'.

Initialization

Always initialize variables before use. Uninitialized local variables contain garbage values (unpredictable).

Scope Visibility

Variables declared inside blocks {} are local to that block. Global variables are visible throughout the file.

Expert Interview Tips:

When asked about variables, start by explaining the memory perspective: "Variables are named memory locations that store data. The compiler allocates memory based on data type..."

3 min read 5,200 views Most Common

Common mistake: Forgetting that char variables store ASCII values, not the character itself. char ch = 'A'; actually stores 65.

2 min read 3,800 views Common Pitfall

Pro tip: Use sizeof() operator to check variable sizes - shows understanding of portability across different systems.

1 min read 2,100 views Pro Tip

Common Follow-up Questions:

Can you explain the difference between auto and register storage classes with memory perspective?
Intermediate

Auto variables are stored in stack memory. Each function call creates new instances. Default for local variables.

Register variables are stored in CPU registers if possible, for faster access. Cannot take address (& operator).

void example() { auto int x = 10; register int y = 20; printf("x address: %p\n", &x); }
💡 'register' is a request, not a command. Compiler may ignore it.
When should you use static variables instead of global variables?
Advanced

Use static when you need:

  • Data hiding within a file (file-level static)
  • Variables that persist between function calls but shouldn't be accessible globally
  • Encapsulation - limiting access to the current file only

Use global sparingly, only for truly shared data across files.

Feature Static (file level) Global
Scope Current file only Whole program (with extern)
Memory Data/BSS segment Data/BSS segment
Access Controlled, modular Anywhere, less control
Can a variable be both const and volatile? Give a real-world example.
Advanced

Yes! This is common in embedded systems programming.

Example: A status register at a fixed memory address that's read-only (const) but can be changed by hardware (volatile).

// Hardware status register at address 0xFF00
// Read-only for software, but hardware can change it
const volatile uint8_t *status_reg = (uint8_t*)0xFF00;

while (!(*status_reg & 0x01)) {
    // Wait for hardware to set bit 0
}
💡 const means your code won't modify it. volatile means it might change unexpectedly (hardware, ISR).
Explain where different variable types are stored in memory with a diagram.
Intermediate
High Address → +------------------+
                 |      Stack       | ← Local variables, function calls
                 | (grows downward) |
                 +------------------+
                 |        ↓         |
                 |        ↑         |
                 +------------------+
                 |       Heap       | ← Dynamically allocated (malloc)
                 | (grows upward)   |
                 +------------------+
                 |     BSS Segment  | ← Uninitialized static/global
                 |                  |   (zero-initialized)
                 +------------------+
                 |  Data Segment    | ← Initialized static/global
                 |                  |
                 +------------------+
                 |   Text Segment   | ← Program code (read-only)
Low Address   →  +------------------+
  • int global = 10; → Data Segment
  • static int x; → BSS Segment
  • int local = 5; → Stack
  • int *p = malloc(4); → Heap
How does variable shadowing work and how can you access a shadowed global variable?
Basic

Variable shadowing occurs when a local variable has the same name as a global variable.

#include <stdio.h>

int value = 100;

int main() {
    int value = 50;

    printf("Local value: %d\n", value);

    {
        extern int value;
        printf("Global value: %d\n", value);
    }

    return 0;
}

C++ uses scope resolution operator ::value, but C doesn't have this.

Question 2 of 7