All topics

C Programming Interview Questions & Answers

50 questions with detailed answers — for freshers and experienced candidates.

Want to actually learn C Programming?

Join a hands-on mini internship or training on iCampusLink and earn a certificate.

Explore programs →

Fresher Level

Q1. What is C programming language and why is it still widely used?

C is a high-level, general-purpose programming language developed by Dennis Ritchie at Bell Labs in the early 1970s. It is known for its efficiency, low-level memory access, and portability across various hardware platforms and operating systems. C is still widely used today for system programming (like operating systems, compilers, and embedded systems), game development, and high-performance computing due to its performance capabilities and direct hardware manipulation. Its simplicity, small runtime, and direct access to memory make it ideal for resource-constrained environments and for writing fundamental system components that other languages rely upon.

Q2. Explain the basic structure of a C program.

A basic C program typically consists of several sections: preprocessor commands, global declarations, the `main()` function, and other user-defined functions. Preprocessor commands, like `#include`, instruct the compiler to include header files. Global declarations define variables or functions accessible throughout the program. The `main()` function is the entry point of every C program; execution begins here. User-defined functions encapsulate specific tasks, improving modularity and reusability. Each statement in C ends with a semicolon. Comments are used for documentation and are ignored by the compiler.
#include <stdio.h>

int main() {
    printf("Hello, C Program!\n"); // Output statement
    return 0; // Indicate successful execution
}

Q3. What are the fundamental data types in C? Give an example for each.

C provides several fundamental data types to handle different kinds of values. The primary ones include: - `int`: Used for whole numbers (e.g., `int age = 30;`) - `char`: Used for single characters (e.g., `char grade = 'A';`) - `float`: Used for single-precision floating-point numbers (e.g., `float price = 19.99f;`) - `double`: Used for double-precision floating-point numbers, offering more precision than `float` (e.g., `double pi = 3.1415926535;`) - `void`: An incomplete type, meaning it has no size or value. It's often used with pointers (e.g., `void *ptr;`) or for functions that return no value (e.g., `void func();`). These types determine the amount of memory allocated and the range of values they can store.

Q4. Differentiate between `printf()` and `scanf()` functions.

`printf()` and `scanf()` are standard library functions in C, declared in `<stdio.h>`, used for console I/O. `printf()` is used for outputting formatted data to the standard output device (usually the console). It takes a format string and a variable number of arguments, displaying their values. `scanf()` is used for reading formatted input from the standard input device (usually the keyboard). It also takes a format string and a variable number of address arguments (pointers to variables) where the input data will be stored. `scanf()` requires the address-of operator `&` for non-array variables.
int num;
printf("Enter a number: ");
scanf("%d", &num);
printf("You entered: %d\n", num);

Q5. Explain the concept of 'operators' in C and list different types of operators.

Operators are symbols that perform specific operations on operands (variables or values). C offers a rich set of operators for various purposes. Key types include: - **Arithmetic Operators**: `+`, `-`, `*`, `/`, `%` (modulo) - **Relational Operators**: `==`, `!=`, `<`, `>`, `<=`, `>=` - **Logical Operators**: `&&` (AND), `||` (OR), `!` (NOT) - **Bitwise Operators**: `&` (AND), `|` (OR), `^` (XOR), `~` (NOT), `<<` (left shift), `>>` (right shift) - **Assignment Operators**: `=`, `+=`, `-=`, `*=` etc. - **Increment/Decrement Operators**: `++`, `--` - **Conditional Operator**: `? :` (ternary operator) - **Special Operators**: `sizeof`, `&` (address-of), `*` (dereference), `.` (member access), `->` (member access through pointer). For example, `int sum = 5 + 3;` uses the `+` arithmetic operator to add `5` and `3`.

Q6. What is a 'pointer' in C? How do you declare and initialize it?

A pointer in C is a variable that stores the memory address of another variable. Instead of holding a direct value, it 'points' to the location where the actual data is stored. Pointers are fundamental for dynamic memory allocation, array manipulation, and passing arguments by reference. They allow for efficient memory management and direct access to memory locations. You declare a pointer by preceding its name with an asterisk `*`, and initialize it by assigning the address of another variable using the address-of operator `&`.
int num = 10;
int *ptr; // Declaration
ptr = &num; // Initialization

Q7. What is an 'array' in C? How do you declare and access its elements?

An array in C is a collection of elements of the same data type, stored in contiguous memory locations. It allows you to store multiple values under a single variable name, accessed using an index. Arrays are useful for managing lists of similar data. You declare an array by specifying its data type, name, and size within square brackets. Elements are accessed using a zero-based index. For an array of size `N`, valid indices range from `0` to `N-1`.
int numbers[5]; // Declares an integer array of size 5
numbers[0] = 10; // Assigns value to the first element
int x = numbers[0]; // Accesses the first element

Q8. Explain the difference between `call by value` and `call by reference` in C.

When a function is called, arguments can be passed in two main ways: `call by value` and `call by reference`. **Call by Value**: A copy of the actual argument's value is passed to the function. Changes made to the parameter inside the function do not affect the original argument in the calling function. This is the default way arguments are passed in C. **Call by Reference**: The memory address of the actual argument is passed to the function. The function then uses a pointer to access and modify the original argument's value. Any changes made inside the function directly affect the original argument. This is achieved using pointers.
#include <stdio.h>

void swap_val(int a, int b) { // Call by value
    int temp = a; a = b; b = temp; // Changes local copies
}

void swap_ref(int *a, int *b) { // Call by reference
    int temp = *a; *a = *b; *b = temp; // Changes original values
}

int main() {
    int x = 10, y = 20;
    swap_val(x, y); // x and y remain 10, 20
    printf("Call by Value: x=%d, y=%d\n", x, y);

    x = 10, y = 20;
    swap_ref(&x, &y); // x and y become 20, 10
    printf("Call by Reference: x=%d, y=%d\n", x, y);
    return 0;
}

Q9. What are 'storage classes' in C? List them.

Storage classes in C determine the scope, lifetime, initial value, and storage location of a variable. They instruct the compiler on how to store the variable and how long it should exist. The four primary storage classes are: 1. **`auto`**: Default for local variables. Stored on the stack, scope is local to the block, lifetime is within the block. 2. **`register`**: Suggests storing the variable in a CPU register for faster access. Scope is local to the block, lifetime is within the block. It's a hint to the compiler, not a guarantee. 3. **`static`**: Preserves the variable's value between function calls. For local variables, it makes them persistent. For global variables or functions, it limits their scope to the file they are declared in. 4. **`extern`**: Declares a variable or function that is defined in another file or elsewhere in the current file. It informs the compiler that the definition exists externally, preventing multiple definitions.
#include <stdio.h>
void func() {
    static int counter = 0; // Initialized once, retains value
    counter++;
    printf("Counter: %d\n", counter);
}
int main() { func(); func(); return 0; } // Output: Counter: 1, Counter: 2

Q10. What is the purpose of the `main()` function in C?

The `main()` function is the entry point of every C program. Program execution always begins from the first statement inside the `main()` function. When the program is run, the operating system loads the program into memory and transfers control to `main()`. It can optionally accept command-line arguments through its parameters `argc` (argument count) and `argv` (argument vector). The return value of `main()` (typically `int`) indicates the program's exit status to the operating system, with `0` usually signifying successful execution and non-zero values indicating errors.

Q11. What is a `string` in C? How is it represented and handled?

In C, a string is a sequence of characters terminated by a null character (`\0`). Unlike other languages, C does not have a built-in string data type; instead, strings are implemented as character arrays. The null terminator is crucial because it marks the end of the string, allowing functions to determine its length. Strings are typically declared as `char` arrays or `char` pointers. String manipulation often involves functions from the `<string.h>` library, such as `strlen()` for length, `strcpy()` for copying, `strcat()` for concatenation, and `strcmp()` for comparison.
char greeting[] = "Hello"; // String literal, null-terminated automatically
char name[20];
strcpy(name, "World"); // Copy "World" into name

Q12. What is the `sizeof` operator in C? Give an example.

The `sizeof` operator in C is a unary operator that returns the size, in bytes, of a variable or a data type. It is useful for understanding memory allocation and for portable programming, as data type sizes can vary across different systems and compilers. `sizeof` is often used when dealing with dynamic memory allocation, arrays, and structures to ensure correct memory sizing. It computes the size at compile time, except for Variable Length Arrays (VLAs) where it's runtime.
int a;
double b;
printf("Size of int: %zu bytes\n", sizeof(a));
printf("Size of double: %zu bytes\n", sizeof(double));

int arr[10];
printf("Size of array arr: %zu bytes\n", sizeof(arr));

Q13. Explain the concept of 'header files' in C.

Header files in C (with `.h` extension) contain function declarations, macro definitions, and type definitions that are shared across multiple source files. They act as an interface, allowing different parts of a program or different programs to use common functionalities without needing to know their full implementation details. When you use `#include <filename.h>` or `#include "filename.h"`, the preprocessor copies the content of the header file into the source file. Standard library functions like `printf` and `scanf` are declared in `<stdio.h>`, while math functions are in `<math.h>`, promoting modularity and code reuse.

Q14. What is the purpose of the `break` and `continue` statements in C?

`break` and `continue` are control flow statements used within loops. - **`break`**: Terminates the innermost loop (or `switch` statement) immediately. Program control resumes at the statement immediately following the loop. It's often used to exit a loop prematurely based on a certain condition. - **`continue`**: Skips the rest of the current iteration of the loop and proceeds to the next iteration. It causes the loop's condition to be re-evaluated immediately. This is useful when you want to bypass certain statements for specific iterations while continuing the loop.
for (int i = 0; i < 5; i++) {
    if (i == 2) continue; // Skips 2
    if (i == 4) break;    // Exits at 4
    printf("%d ", i);    // Output: 0 1 3
}

Q15. How are comments written in C? Why are they important?

Comments are explanatory notes embedded within the code that are ignored by the compiler. They are crucial for improving code readability and maintainability. C supports two types of comments: 1. **Single-line comments**: Start with `//` and extend to the end of the line. 2. **Multi-line comments**: Start with `/*` and end with `*/`. They can span multiple lines. Comments help other developers (and your future self) understand the logic, purpose, and intricacies of the code, especially for complex algorithms or non-obvious implementations. Good commenting practices are essential for collaborative projects and long-term code management.

Q16. What is the difference between including a header file with `< >` vs `" "`?

The choice between using angle brackets (`< >`) and double quotes (`" "`) for `#include` directives tells the preprocessor where to search for the specified header file: 1. **Angle Brackets (`#include <filename.h>`)**: This syntax is typically used for standard library header files (e.g., `<stdio.h>`, `<stdlib.h>`). The preprocessor searches for these files in a predefined set of directories configured by the compiler and operating system (e.g., `/usr/include`, compiler's own include paths). It usually doesn't search in the current directory. 2. **Double Quotes (`#include "filename.h"`)**: This syntax is primarily used for user-defined header files that are part of the current project. The preprocessor typically starts searching for the file in the directory containing the current source file. If not found there, it then proceeds to search in the standard include directories, similar to angle brackets (though this secondary search behavior can vary slightly by compiler). Using the correct syntax helps the compiler locate files efficiently and clearly distinguishes between standard and custom headers.

Intermediate Level

Q1. Explain the difference between `const int *p`, `int *const p`, and `const int *const p`.

These declarations specify different levels of `const` correctness for pointers: 1. `const int *p`: "Pointer to a constant integer." Here, the data pointed to by `p` cannot be modified through `p`. However, `p` itself can be changed to point to a different `int` variable. `*p = 20; // Error` `p = &other_num; // OK` 2. `int *const p`: "Constant pointer to an integer." Here, `p` is a constant pointer, meaning it must be initialized at declaration and cannot be changed to point to another location. The data it points to *can* be modified through `p`. `*p = 20; // OK` `p = &other_num; // Error` 3. `const int *const p`: "Constant pointer to a constant integer." Both the pointer `p` and the data it points to are constant. Neither `p` nor `*p` can be changed after initialization. `*p = 20; // Error` `p = &other_num; // Error`

Q2. What is dynamic memory allocation in C? Name the functions used for it.

Dynamic memory allocation is the process of allocating memory during program execution (at runtime) rather than at compile time. This is crucial when the exact memory requirements are unknown beforehand, such as when dealing with user input or variable-sized data structures. Memory allocated dynamically resides in the heap. C provides four standard library functions for dynamic memory management: 1. `malloc()`: Allocates a block of memory of a specified size in bytes and returns a `void` pointer to the beginning of the block. 2. `calloc()`: Allocates memory for an array of elements, initializes all bytes to zero, and returns a `void` pointer. 3. `realloc()`: Changes the size of a previously allocated memory block. It can expand or shrink the block. 4. `free()`: Deallocates the memory block previously allocated by `malloc()`, `calloc()`, or `realloc()`, returning it to the system.
#include <stdlib.h>
int *ptr = (int *)malloc(sizeof(int));
if (ptr != NULL) {
    *ptr = 100;
    free(ptr);
    ptr = NULL;
}

Q3. Differentiate between `malloc()` and `calloc()`.

Both `malloc()` and `calloc()` are used for dynamic memory allocation, but they have key differences: **`malloc()` (memory allocation)**: - Takes a single argument: the size in bytes to be allocated. - Returns a `void*` pointer to the allocated memory. - Does not initialize the allocated memory; its contents are garbage (indeterminate). **`calloc()` (contiguous allocation)**: - Takes two arguments: the number of elements and the size of each element. - Returns a `void*` pointer to the allocated memory. - Initializes all bytes of the allocated memory to zero. Use `calloc()` when you need a block of memory initialized to zero, often for arrays. Use `malloc()` when initialization is not required or will be handled manually, potentially offering a slight performance advantage if zeroing is unnecessary.
int *arr1 = (int *)malloc(5 * sizeof(int));
int *arr2 = (int *)calloc(5, sizeof(int));

Q4. What is a 'void pointer' in C? When would you use it?

A `void` pointer (or generic pointer) is a pointer that has no associated data type. It can point to any data type without type-casting the pointer. However, to dereference a `void` pointer, it must first be cast to a specific data type. This makes `void` pointers extremely flexible but also requires careful handling to avoid type-related errors. You would use `void` pointers in scenarios like: - **Generic functions**: Functions that can operate on data of any type (e.g., `qsort()`, `memcpy()`). - **Dynamic Memory Allocation**: Functions like `malloc()`, `calloc()`, `realloc()` return `void*` because they allocate raw memory, and it's up to the programmer to cast it to the desired type. - **Implementing generic data structures**: Like linked lists or trees that can store elements of various types.
int x = 10;
void *ptr = &x;
printf("Value: %d\n", *(int *)ptr); // Cast to int* before dereferencing

Q5. Explain 'function pointers' in C with an example.

A function pointer is a variable that stores the memory address of a function. It allows you to call a function indirectly through the pointer, pass functions as arguments to other functions, or store functions in data structures. This is a powerful feature for implementing callbacks, dispatch tables, and generic algorithms. Declaration involves specifying the return type, the pointer name (in parentheses with an asterisk), and the parameter list of the function it will point to. Initialization involves assigning the function's name (without parentheses) to the pointer.
int add(int a, int b) { return a + b; }

int main() {
    int (*func_ptr)(int, int); // Declare function pointer
    func_ptr = &add;           // Assign address of add function

    int result = func_ptr(5, 3); // Call function via pointer
    printf("Result: %d\n", result); // Output: Result: 8
    return 0;
}

Q6. What is the purpose of the `volatile` keyword in C?

The `volatile` keyword in C is a type qualifier that tells the compiler that a variable's value can be changed unexpectedly by something external to the program's flow (e.g., hardware, an interrupt service routine, or another thread). When a variable is declared `volatile`, the compiler is prevented from performing certain optimizations that might assume the variable's value remains constant between accesses. It ensures that every access to a `volatile` variable is a direct read from or write to memory, rather than using a cached value in a register. This is critical in embedded systems, multi-threaded applications, and when interacting with memory-mapped I/O.

Q7. Differentiate between a `structure` and a `union` in C.

Both structures and unions are user-defined data types in C that allow grouping of different data types under a single name. However, they differ significantly in memory allocation and usage: **Structure (`struct`)**: - Each member of a structure is allocated its own unique memory space. - All members can hold values simultaneously. - The total size of a structure is the sum of the sizes of its members (plus any padding). **Union (`union`)**: - All members of a union share the same memory location. - Only one member can hold a value at any given time. - The total size of a union is equal to the size of its largest member. Unions are memory-efficient when you need to store different types of data at different times in the same memory location, while structures are used when you need to store different types of related data simultaneously.

Q8. Explain the concept of 'Bitwise Operators' in C and list them.

Bitwise operators in C perform operations on individual bits of integer operands. They are commonly used in low-level programming, embedded systems, graphics, and cryptography for tasks like setting, clearing, toggling, or testing specific bits. These operations are very fast as they directly manipulate the binary representation of numbers. The bitwise operators are: - **`&` (Bitwise AND)**: Sets a bit if both corresponding bits are 1. - **`|` (Bitwise OR)**: Sets a bit if at least one of the corresponding bits is 1. - **`^` (Bitwise XOR)**: Sets a bit if the corresponding bits are different. - **`~` (Bitwise NOT/Complement)**: Inverts all bits. - **`<<` (Left Shift)**: Shifts bits to the left, filling with zeros on the right. Equivalent to multiplying by powers of 2. - **`>>` (Right Shift)**: Shifts bits to the right. For unsigned types, fills with zeros; for signed types, behavior is implementation-defined (arithmetic or logical shift). Equivalent to dividing by powers of 2.
unsigned char a = 5; // 00000101
unsigned char b = 3; // 00000011
unsigned char c = a & b; // 00000001 (1)

Q9. How do you handle file I/O operations in C?

File I/O in C is handled using functions from the `<stdio.h>` library, which operate on file pointers (`FILE *`). The basic steps for file operations are: 1. **Open the file**: Use `fopen()` to open a file. It returns a `FILE *` pointer or `NULL` on failure. You specify the filename and the mode (e.g., `"r"` for read, `"w"` for write, `"a"` for append, `"rb"` for binary read). 2. **Perform operations**: Use functions like `fprintf()`/`fscanf()` for formatted text I/O, `fputc()`/`fgetc()` for character I/O, or `fwrite()`/`fread()` for block (binary) I/O. 3. **Close the file**: Use `fclose()` to close the file, releasing the file resources and flushing any buffered data. This is crucial to prevent data loss and resource leaks.
FILE *fp = fopen("example.txt", "w");
if (fp != NULL) {
    fprintf(fp, "Hello, File!");
    fclose(fp);
}

Q10. What are command-line arguments in C? How do you access them?

Command-line arguments are parameters passed to a program when it is executed from the command line. They allow users to customize program behavior without modifying the source code. In C, these arguments are accessed through the `main()` function's parameters: `int main(int argc, char *argv[])` - `argc` (argument count): An integer representing the number of command-line arguments, including the program's name itself. - `argv` (argument vector): An array of character pointers (strings), where `argv[0]` is the program's name, `argv[1]` is the first argument, and so on. `argv[argc]` is always `NULL`.
#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("Program name: %s\n", argv[0]);
    for (int i = 1; i < argc; i++) {
        printf("Argument %d: %s\n", i, argv[i]);
    }
    return 0;
}

Q11. Explain 'type casting' in C with examples.

Type casting in C is the explicit conversion of a variable from one data type to another. It's used when implicit conversion (coercion) isn't desired or possible, or to ensure specific arithmetic behavior. Type casting can be either implicit (automatic, performed by the compiler) or explicit (programmer-defined). **Explicit Type Casting**: Done by placing the target type in parentheses before the variable or expression.
int num_int = 10;
double num_double = (double)num_int; // int to double

float pi = 3.14f;
int int_pi = (int)pi; // float to int (truncates decimal part)

int sum = 10, count = 3;
double average = (double)sum / count; // Ensures float division
It's important to be aware of potential data loss or unexpected behavior during type casting, especially when converting from a larger to a smaller data type.

Q12. What is `recursion` in C? Provide a simple example.

Recursion is a programming technique where a function calls itself, either directly or indirectly, to solve a problem. A recursive function typically has two parts: 1. **Base Case**: A condition that stops the recursion, preventing an infinite loop. Without a base case, the function would call itself indefinitely, leading to a stack overflow. 2. **Recursive Step**: The part where the function calls itself with a modified input, moving closer to the base case. Recursion is often used for problems that can be broken down into smaller, similar subproblems, such as traversing tree structures, calculating factorials, or generating Fibonacci sequences.
#include <stdio.h>

// Recursive function to calculate factorial
int factorial(int n) {
    if (n == 0 || n == 1) {
        return 1; // Base case
    } else {
        return n * factorial(n - 1); // Recursive step
    }
}

int main() {
    printf("Factorial of 5: %d\n", factorial(5)); // Output: 120
    return 0;
}

Q13. Explain the concept of 'enumerations' (`enum`) in C.

An enumeration (`enum`) in C is a user-defined data type that assigns names to integer constants. It makes the code more readable and maintainable by allowing meaningful names instead of 'magic numbers'. By default, the first enumerator has a value of 0, and subsequent enumerators are incremented by 1. You can explicitly assign integer values to enumerators.
enum Weekday {
    SUNDAY,    // 0 by default
    MONDAY,    // 1
    TUESDAY = 5, // Explicitly set to 5
    WEDNESDAY, // 6
    THURSDAY,
    FRIDAY,
    SATURDAY
};

enum Weekday today = MONDAY;
printf("Today is %d\n", today); // Output: Today is 1
Enums improve code clarity, especially when dealing with a fixed set of related options or states, making the code self-documenting.

Q14. What is the difference between `stack` and `heap` memory in C?

The `stack` and `heap` are two distinct regions of memory used by a C program, each with different characteristics for allocation and management: **Stack**: - **Allocation**: Automatic, managed by the compiler. Used for local variables, function parameters, and return addresses. - **Lifetime**: Variables exist only within their scope (e.g., function execution). - **Speed**: Very fast allocation/deallocation. - **Size**: Limited, typically smaller than heap. - **LIFO**: Follows Last-In, First-Out principle. **Heap**: - **Allocation**: Dynamic, managed by the programmer using `malloc()`, `calloc()`, `realloc()`, `free()`. - **Lifetime**: Variables persist until explicitly deallocated or program terminates. - **Speed**: Slower than stack allocation. - **Size**: Larger, limited by system memory. - **Flexibility**: Allows for variable-sized data structures whose size is not known at compile time. Mismanagement of heap memory (not freeing allocated memory) leads to memory leaks.

Q15. Explain the use of `static` keyword for global variables and functions.

The `static` keyword has different meanings depending on its context. For global variables and functions, it affects their linkage: 1. **`static` Global Variable**: A global variable declared with `static` has internal linkage. This means its scope is limited to the file in which it is declared. It cannot be accessed or linked from other source files using `extern`. This helps prevent naming conflicts and encapsulates data within a single compilation unit, promoting modularity.
    // file1.c
    static int count = 0; // Only visible in file1.c
    int global_var = 10; // Visible everywhere
    
2. **`static` Function**: A function declared with `static` also has internal linkage. It can only be called from within the same source file where it is defined. This is useful for helper functions that are not meant to be exposed to other parts of a multi-file project, reducing the global namespace pollution and allowing for functions with the same name in different files.
    // file1.c
    static void helper_function() { /* ... */ }
    

Q16. What is the purpose of the `extern` keyword in C?

The `extern` keyword in C is used to declare a global variable or function that is defined in another source file or elsewhere in the current source file. It acts as a declaration, not a definition, informing the compiler that the actual definition (memory allocation) for that variable or function exists externally. Its primary purpose is to allow variables and functions to be shared across multiple source files (translation units) in a multi-file project. Without `extern`, if you declared a global variable in a header file, including that header in multiple `.c` files would lead to multiple definition errors during linking. `extern` solves this by providing a declaration without definition, ensuring the linker finds a single definition.
// file1.c
int global_counter = 0; // Definition

// file2.c
extern int global_counter; // Declaration, refers to global_counter in file1.c

void increment_counter() {
    global_counter++;
}

Q17. How do you define a multi-dimensional array in C? Give an example.

A multi-dimensional array in C is an array of arrays. The most common form is a two-dimensional array, often visualized as a matrix or table. It's declared by specifying the data type, array name, and sizes for each dimension in separate square brackets. Elements are stored in row-major order in memory. While you can declare arrays with more than two dimensions, two-dimensional arrays are the most frequently used. **Declaration and Initialization**:
// 2D array: 3 rows, 4 columns
int matrix[3][4];

// Initialization during declaration
int board[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};

// Accessing an element (row 1, column 2)
int value = board[1][2]; // value will be 6

// Iterating through a 2D array
for (int i = 0; i < 2; i++) {
    for (int j = 0; j < 3; j++) {
        printf("%d ", board[i][j]);
    }
    printf("\n");
}
When passing multi-dimensional arrays to functions, all dimensions except the first must be specified.

Q18. Explain the concept of `Lvalues` and `Rvalues` in C.

`Lvalue` and `Rvalue` are fundamental concepts in C that describe the role of expressions in an assignment statement: - **Lvalue (Left-hand side value)**: An expression that refers to a memory location and can appear on the left-hand side of an assignment operator. It represents an object that has an identifiable memory address. Examples include variable names, dereferenced pointers (`*ptr`), and array elements (`arr[i]`). An lvalue can hold a value. - **Rvalue (Right-hand side value)**: An expression that represents a temporary value or data value. It can appear on the right-hand side of an assignment operator. Rvalues do not necessarily have a persistent memory address that can be taken. Examples include literals (`10`, `'a'`), results of expressions (`a + b`), and function return values.
int x = 10; // x is an Lvalue, 10 is an Rvalue
int *p = &x; // p is an Lvalue, &x is an Rvalue (address of x)
*p = 20;    // *p is an Lvalue, 20 is an Rvalue
(x + 5) = 15; // Error: (x + 5) is an Rvalue, cannot be assigned to
An lvalue can be used as an rvalue, but an rvalue cannot be used as an lvalue.

Q19. How do you read a line of text from a file in C, including spaces?

Reading an entire line from a file in C, including spaces, is typically done using `fgets()` or `getline()` (a POSIX standard function, not part of C standard before C23). `fgets()` is the more portable standard C option. **Using `fgets()`**: `char *fgets(char *str, int n, FILE *stream);` - Reads at most `n-1` characters from `stream` into `str`. - Stops when `n-1` characters are read, a newline character `\n` is encountered, or the end-of-file (EOF) is reached. - Includes the newline character in `str` if read. - Automatically appends a null terminator `\0` to `str`. - Returns `str` on success, `NULL` on error or EOF.
#include <stdio.h>
#define MAX_LINE_LENGTH 256

int main() {
    FILE *fp = fopen("data.txt", "r");
    if (fp == NULL) { /* error handling */ return 1; }

    char line[MAX_LINE_LENGTH];
    while (fgets(line, MAX_LINE_LENGTH, fp) != NULL) {
        printf("Read: %s", line); // line already contains \n
    }
    fclose(fp);
    return 0;
}
`getline()` is more flexible as it dynamically allocates memory, but is not universally available.

Q20. What is the preprocessor in C? List some common preprocessor directives.

The C preprocessor is the first phase of compilation. It processes directives (commands) embedded in the source code before the actual compilation begins. These directives start with a `#` symbol and are not C statements themselves. The preprocessor performs text substitutions, file inclusions, and conditional compilation, resulting in a modified source file (a 'translation unit') that is then passed to the compiler. **Common Preprocessor Directives**: - `#include`: Includes the content of another file (header file). - `#define`: Defines a macro, which performs text replacement. - `#undef`: Undefines a previously defined macro. - `#ifdef`, `#ifndef`, `#if`, `#else`, `#elif`, `#endif`: Used for conditional compilation, allowing parts of the code to be included or excluded based on conditions. - `#error`, `#warning`: Generates compilation errors or warnings. - `#pragma`: Provides special instructions to the compiler (implementation-defined).
#define PI 3.14159
#ifdef DEBUG
    #define LOG(msg) printf("DEBUG: %s\n", msg)
#else
    #define LOG(msg)
#endif

Q21. Explain the use of `typedef` in C.

`typedef` is a keyword in C used to create an alias (an alternative name) for an existing data type. It doesn't create a new data type but rather a synonym, which can significantly improve code readability, portability, and reduce typing. It is particularly useful for complex declarations, such as structures, unions, function pointers, and array types. **Common uses**: 1. **Simplifying complex declarations**: Making `struct` or `union` types easier to use without repeatedly typing `struct` or `union`. 2. **Improving readability**: Giving meaningful names to types, especially for function pointers. 3. **Portability**: Creating aliases for platform-dependent types (e.g., `uint32_t` from `<stdint.h>`).
typedef unsigned long ulong; // Alias for unsigned long
ulong my_id = 12345UL;

typedef struct Point {
    int x;
    int y;
} Point; // Alias for 'struct Point'
Point p1 = {10, 20};

typedef int (*Operation)(int, int); // Alias for a function pointer
int add(int a, int b) { return a + b; }
Operation op_ptr = add;

Q22. What is the `const` keyword used for in C?

The `const` keyword in C is a type qualifier that specifies that the value of a variable or data pointed to by a pointer cannot be modified after initialization. It's used for enforcing read-only access and improving code safety and clarity. **Uses of `const`**: 1. **Constant variables**: Declares a variable whose value cannot be changed. `const int MAX_VALUE = 100;` 2. **Function parameters**: Indicates that a function will not modify the argument passed to it, useful for passing large structures or arrays by pointer without making a copy. `void print_array(const int *arr, int size);` 3. **Pointers**: Can be used to make the data pointed to `const`, the pointer itself `const`, or both. `const char *ptr;` // Data is const `char *const ptr;` // Pointer is const `const char *const ptr;` // Both are const 4. **Return values**: A function can return a `const` value, indicating the caller cannot modify it. Using `const` helps the compiler optimize code and aids in catching programming errors at compile time, improving code robustness.

Q23. How are arrays passed to functions in C? Explain with an example.

In C, arrays are always passed to functions by reference, even though the syntax might look like call by value. When an array name is used as a function argument, it decays into a pointer to its first element. This means the function receives a copy of the base address of the array, not a copy of the entire array. Any modifications made to the array elements inside the function will affect the original array in the calling function. To pass an array, you typically provide the array name and its size (as the function cannot determine the array's size from the pointer).
#include <stdio.h>

void modifyArray(int *arr, int size) { // Receives a pointer
    for (int i = 0; i < size; i++) {
        arr[i] = arr[i] * 2; // Modifies original array elements
    }
}

int main() {
    int my_array[] = {1, 2, 3, 4, 5};
    int n = sizeof(my_array) / sizeof(my_array[0]);

    printf("Original array: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", my_array[i]);
    }
    printf("\n");

    modifyArray(my_array, n); // Pass array name (decays to pointer)

    printf("Modified array: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", my_array[i]);
    }
    printf("\n");

    return 0;
}
Output: Original array: 1 2 3 4 5 Modified array: 2 4 6 8 10

Q24. What is the difference between `NULL` and `void *0`?

`NULL` and `(void *)0` are essentially the same in C, representing a null pointer constant. However, there's a subtle distinction in how they are typically defined and perceived. - **`(void *)0`**: This is an explicit null pointer constant. It is a pointer to `void` with a value of 0. When assigned to any other pointer type, it implicitly converts to a null pointer of that type. - **`NULL`**: This is a macro defined in several standard headers (like `<stddef.h>`, `<stdio.h>`, `<stdlib.h>`). The C standard allows `NULL` to be defined as either `0` or `((void *)0)`. Historically, it was often `0` (an integer literal), but `(void *)0` is generally preferred as it explicitly indicates a pointer type, making it clearer and potentially helping with stricter compiler warnings. Regardless of its exact definition, `NULL` is guaranteed to be a null pointer constant, meaning it converts to a null pointer when assigned to any pointer type. In practice, they both serve the same purpose: to represent a pointer that doesn't point to any valid memory location. Using `NULL` is generally preferred for readability and portability, as it abstracts away the underlying implementation detail.

Q25. Explain the concept of `union` in C with a practical example.

A `union` in C is a user-defined data type that allows different members to share the same memory location. Unlike structures where each member gets its own memory, in a union, only one member can be active at a time, and the union's size is equal to the size of its largest member. This makes unions memory-efficient, especially when dealing with data that can take on different forms but only one form is relevant at any given moment. **Practical Example**: A message packet where the payload can be of different types (e.g., an integer, a float, or a string) but never all at once.
#include <stdio.h>
#include <string.h>

enum MessageType {
    INT_MSG,
    FLOAT_MSG,
    STRING_MSG
};

struct Message {
    enum MessageType type;
    union {
        int int_val;
        float float_val;
        char str_val[20];
    } payload;
};

int main() {
    struct Message msg;

    msg.type = INT_MSG;
    msg.payload.int_val = 123;
    printf("Int message: %d\n", msg.payload.int_val);

    msg.type = FLOAT_MSG;
    msg.payload.float_val = 3.14f;
    printf("Float message: %.2f\n", msg.payload.float_val);

    msg.type = STRING_MSG;
    strcpy(msg.payload.str_val, "Hello Union");
    printf("String message: %s\n", msg.payload.str_val);

    // Note: accessing int_val after setting str_val would be undefined behavior
    return 0;
}
This example demonstrates how a `union` combined with an `enum` (a 'tagged union') allows safe handling of different data types in a shared memory space.

Advanced Level

Q1. What is a `memory leak` in C and how can it be prevented?

A memory leak occurs in C when a program allocates memory dynamically using functions like `malloc()`, `calloc()`, or `realloc()` but fails to deallocate it using `free()` when it's no longer needed. This leads to a continuous consumption of memory, reducing available system resources and potentially causing the program or system to slow down or crash over time. Memory leaks are particularly problematic in long-running applications or embedded systems. **Prevention**: 1. **Always `free()` allocated memory**: Ensure every `malloc`/`calloc`/`realloc` call has a corresponding `free` call. 2. **Handle error conditions**: If `malloc` returns `NULL`, handle it gracefully and avoid using the invalid pointer. 3. **Ownership**: Clearly define which part of the code is responsible for freeing memory. 4. **Tools**: Use memory debuggers like Valgrind to detect leaks during development. 5. **Smart pointers (C++)**: While not native to C, similar concepts can be implemented with careful wrapper functions.

Q2. Explain the concept of a `dangling pointer` and how to avoid it.

A dangling pointer is a pointer that points to a memory location that has been deallocated or freed. When the memory is freed, the pointer still holds the address of that now-invalid memory block. Accessing or dereferencing a dangling pointer leads to undefined behavior, which can result in crashes, data corruption, or security vulnerabilities, as the memory might have been reallocated for other purposes. **How to avoid**: 1. **Set to `NULL` after `free()`**: After `free(ptr)`, immediately set `ptr = NULL`. This makes the pointer a null pointer, which is safe to check. 2. **Scope management**: Ensure pointers do not outlive the memory they point to. 3. **Avoid returning addresses of local variables**: Local variables reside on the stack and are destroyed when the function returns. 4. **Use smart pointers (conceptually)**: In C++, smart pointers automate memory management. In C, careful discipline is required.

Q3. What is `pointer arithmetic`? Can it be performed on `void` pointers?

Pointer arithmetic in C involves performing arithmetic operations (addition, subtraction) on pointers. When an integer is added to or subtracted from a pointer, the pointer's address is incremented or decremented by that integer multiplied by the `sizeof` the data type it points to. This allows efficient traversal of arrays and data structures. **Example**: If `int *p` points to `address X`, then `p + 1` points to `X + sizeof(int)`. **`void` pointers and arithmetic**: Standard C explicitly forbids pointer arithmetic on `void*` because the compiler doesn't know the size of the data type it points to, making it impossible to calculate the correct increment/decrement. To perform arithmetic, a `void*` must first be cast to a pointer of a specific type (e.g., `char*` for byte-by-byte movement) or a pointer to the actual data type it represents.
void *ptr = malloc(10);
// ptr++; // Error: invalid use of void expression
char *byte_ptr = (char *)ptr;
byte_ptr++; // OK: increments by 1 byte

Q4. Explain `structure padding` and how to minimize it.

Structure padding is a technique used by compilers to align structure members in memory on specific address boundaries (e.g., 2, 4, 8 bytes) to optimize memory access performance. CPUs often fetch data in chunks (words), and accessing misaligned data can be slower or even cause hardware exceptions. The compiler inserts unused 'padding' bytes between members or at the end of the structure to achieve this alignment, which can lead to a structure occupying more memory than the sum of its members' individual sizes. **Minimizing padding**: 1. **Order members by size**: Declare members from largest to smallest. This often groups smaller members together, reducing gaps. 2. **Use `__attribute__((packed))` (GCC/Clang) or `#pragma pack` (MSVC)**: These compiler-specific directives force members to be packed without padding. However, this can lead to performance degradation due to unaligned memory access and should be used cautiously, especially for embedded systems or specific hardware interfaces.
struct Example {
    char c;    // 1 byte
    int i;     // 4 bytes (might have 3 bytes padding after c)
    short s;   // 2 bytes (might have 2 bytes padding after i)
}; // Total size could be 12 or 16 bytes depending on alignment

struct OptimizedExample {
    int i;     // 4 bytes
    short s;   // 2 bytes
    char c;    // 1 byte (less padding overall)
}; // Total size likely 8 bytes

Q5. What is the `restrict` keyword in C? When is it used?

The `restrict` keyword in C is a type qualifier introduced in C99, primarily used for optimizing compiler performance. When applied to a pointer, it tells the compiler that for the lifetime of the pointer, only this pointer (or a pointer derived directly from it) will be used to access the object it points to. In other words, the pointer is the *only* way to access that memory region, guaranteeing that there are no other aliasing pointers that might modify the same memory. This promise allows the compiler to make aggressive optimizations, such as reordering memory accesses or caching values in registers, without worrying about external modifications. It's especially useful in functions dealing with arrays or buffers, like `memcpy()` or `strcat()` where source and destination memory regions are distinct.
void copy_data(int *restrict dest, const int *restrict src, int n) {
    for (int i = 0; i < n; i++) {
        dest[i] = src[i];
    }
}
If the `restrict` contract is violated (i.e., `dest` and `src` actually overlap), the behavior is undefined.

Q6. Describe `setjmp()` and `longjmp()` and their typical use cases.

`setjmp()` and `longjmp()` are non-local goto functions in C, declared in `<setjmp.h>`, providing a mechanism for transferring control to an arbitrary point in the program's execution flow, typically across multiple function calls, without returning through the call stack. They are useful for implementing error recovery or exception-like handling. - **`setjmp(jmp_buf env)`**: Saves the current execution environment (including the program counter and stack pointer) into a `jmp_buf` structure and returns 0 when called directly. If control is transferred back to this point by a `longjmp()`, `setjmp()` returns a non-zero value. - **`longjmp(jmp_buf env, int val)`**: Restores the execution environment saved in `env` by a previous `setjmp()`. Program execution then resumes as if `setjmp()` had just returned with the value `val`. **Use cases**: 1. **Error handling**: Jumping out of deeply nested function calls on error conditions. 2. **Exception handling**: Simulating try-catch blocks in C. 3. **Interrupt handlers**: Returning to a stable state after an interrupt. **Caveats**: They don't handle resource cleanup (like `free()` calls) automatically, requiring careful management to avoid memory leaks.

Q7. What is a `self-referential structure`? Give an example.

A self-referential structure is a structure that contains a pointer to a structure of the same type. This allows for the creation of dynamic data structures like linked lists, trees, and graphs, where nodes are connected to each other through pointers. Each node in such a structure typically holds some data and one or more pointers to other nodes of the same type. **Example: Linked List Node**
#include <stdio.h>
#include <stdlib.h>

struct Node {
    int data;
    struct Node *next; // Pointer to the next node of type Node
};

int main() {
    struct Node *head = (struct Node *)malloc(sizeof(struct Node));
    head->data = 10;
    head->next = NULL; // Initially, points to nothing

    struct Node *second = (struct Node *)malloc(sizeof(struct Node));
    second->data = 20;
    second->next = NULL;

    head->next = second; // Link head to second node

    printf("First node data: %d\n", head->data);
    printf("Second node data: %d\n", head->next->data);

    free(head);
    free(second);
    return 0;
}
This structure enables flexible memory usage and efficient insertion/deletion of elements.

Q8. How do you declare a pointer to an array versus an array of pointers?

These two constructs are distinct in C, affecting memory layout and usage: 1. **Pointer to an array**: This is a pointer that points to an entire array. The array itself is treated as a single entity. The declaration specifies the size of the array it points to.
    int (*ptr_to_array)[5]; // ptr_to_array is a pointer to an array of 5 integers
    int arr[5] = {1, 2, 3, 4, 5};
    ptr_to_array = &arr; // Assigns the address of the entire array
    printf("%d\n", (*ptr_to_array)[0]); // Dereference and access element
    
2. **Array of pointers**: This is an array where each element is a pointer. Each pointer in the array can point to a different memory location or array.
    int *arr_of_ptr[5]; // arr_of_ptr is an array of 5 integer pointers
    int a=10, b=20;
    arr_of_ptr[0] = &a; // Element 0 points to 'a'
    arr_of_ptr[1] = &b; // Element 1 points to 'b'
    printf("%d\n", *arr_of_ptr[0]); // Access via pointer in array
    
An array of pointers is often used to implement arrays of strings or jagged arrays, while a pointer to an array is less common but useful when manipulating whole arrays.

Q9. What are Interrupt Service Routines (ISRs)? What special considerations apply when writing them in C?

Interrupt Service Routines (ISRs), also known as interrupt handlers, are special functions executed by the CPU in response to an interrupt event (e.g., hardware signal, timer expiry, software interrupt). They are typically short, critical pieces of code that handle the immediate needs of the interrupt and then return control to the interrupted program. In C, ISRs are often written for embedded systems. **Special considerations when writing ISRs in C**: 1. **`volatile` keyword**: Variables shared between the ISR and the main program must be declared `volatile` to prevent compiler optimizations that might lead to incorrect values. 2. **Reentrancy**: ISRs should ideally be reentrant, meaning they can be safely interrupted and called again without corrupting data. Avoid non-reentrant functions (e.g., standard library functions that are not interrupt-safe). 3. **Minimal operations**: ISRs should be as short and fast as possible to minimize interrupt latency. Complex processing should be deferred to the main loop. 4. **No floating-point operations**: Many embedded processors do not save floating-point registers automatically during an interrupt, or doing so is costly. 5. **No `printf` or dynamic memory allocation**: These functions are typically not interrupt-safe and can introduce significant overhead or non-determinism. 6. **Stack usage**: ISRs use the stack; excessive stack usage can lead to stack overflow, especially in resource-constrained environments. 7. **Compiler-specific extensions**: Compilers often provide extensions (e.g., `__interrupt`, `__attribute__((interrupt))`) to declare functions as ISRs, ensuring proper context saving/restoring.
Prepared by iCampusLink. 50 C Programming interview questions.
Top 50 C Programming Interview Questions & Answers (2026) | iCampusLink