Toolmingo
Guides28 min read

50 C Programming Interview Questions (With Answers)

Top C language interview questions with clear answers and code examples — covering pointers, memory management, strings, structs, preprocessor, and systems programming.

C interviews test low-level thinking: pointers, memory management, the preprocessor, bit manipulation, and systems programming. This guide covers the 50 most common questions — with clear answers and runnable examples.

Quick reference

Topic Most asked questions
Pointers Pointer arithmetic, double pointers, function pointers, void*
Memory malloc/calloc/realloc/free, stack vs heap, memory leaks
Strings string.h functions, buffer overflows, null terminator
Structs padding, bit fields, typedef, flexible arrays
Preprocessor #define vs const, macros, include guards, #pragma
Bit manipulation AND/OR/XOR/NOT, shift operators, common tricks
Functions pass by value vs pointer, variadic functions, recursion
Standard library stdio, stdlib, string.h, math.h

Pointers

1. What is a pointer in C?

A pointer is a variable that stores the memory address of another variable. Pointers enable dynamic memory allocation, pass-by-reference semantics, and efficient array/string manipulation.

int x = 10;
int *p = &x;   // p holds the address of x

printf("%d\n", x);   // 10 — value of x
printf("%p\n", p);   // 0x7fff... — address stored in p
printf("%d\n", *p);  // 10 — dereferencing: value at address

*p = 20;             // modifies x through pointer
printf("%d\n", x);   // 20

Key operators:

  • & — address-of operator
  • * — dereference operator (in expression context) or pointer declaration (in type context)

2. What is the difference between *p++, (*p)++, and *(p++)?

Operator precedence determines the result:

int arr[] = {10, 20, 30};
int *p = arr;

// *p++  — post-increment the pointer, dereference original address
int a = *p++;  // a = 10, p now points to arr[1]

// (*p)++ — dereference first, then increment the value
p = arr;
int b = (*p)++;  // b = 10, arr[0] is now 11, p unchanged

// *(p++) — same as *p++ (post-increment has same precedence as *)
p = arr;
int c = *(p++);  // c = 10, p now points to arr[1]
Expression What increments Value returned
*p++ Pointer *p before increment
(*p)++ Pointed-to value *p before increment
++*p Pointed-to value *p after increment
*++p Pointer *p after increment

3. What is a double pointer (pointer to pointer)?

A double pointer stores the address of another pointer. Common uses: dynamic 2D arrays, modifying a pointer from a function.

int x = 5;
int *p = &x;
int **pp = &p;   // pointer to pointer

printf("%d\n", **pp);  // 5 — double dereference

// Modifying a pointer from a function
void allocate(int **ptr) {
    *ptr = malloc(sizeof(int));
    **ptr = 42;
}

int *q = NULL;
allocate(&q);
printf("%d\n", *q);  // 42
free(q);

4. What is a void pointer (void *)?

A void * is a generic pointer that can hold the address of any type. It cannot be dereferenced directly — you must cast it first.

void *vp;
int x = 10;
double d = 3.14;

vp = &x;
printf("%d\n", *(int *)vp);    // 10

vp = &d;
printf("%f\n", *(double *)vp); // 3.140000

// malloc returns void *
int *arr = malloc(5 * sizeof(int));  // implicit cast in C (explicit in C++)

void * is how malloc, memcpy, and qsort achieve generic behavior in C.


5. What is the difference between a pointer to a function and a function pointer array?

// Function pointer declaration
int (*add)(int, int);

int sum(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }

add = sum;
printf("%d\n", add(2, 3));  // 5

// Array of function pointers (dispatch table)
int (*ops[2])(int, int) = {sum, mul};
printf("%d\n", ops[0](2, 3));  // 5
printf("%d\n", ops[1](2, 3));  // 6

Function pointer arrays are the C equivalent of virtual dispatch — used in embedded systems, state machines, and plugin architectures.


6. What is pointer arithmetic?

You can add/subtract integers from pointers. The result moves the pointer by n * sizeof(pointed_type) bytes.

int arr[] = {10, 20, 30, 40};
int *p = arr;

printf("%d\n", *(p + 1));  // 20 (moves 4 bytes forward)
printf("%d\n", *(p + 2));  // 30

// Pointer difference
int *q = &arr[3];
ptrdiff_t diff = q - p;    // 3 (not 12 bytes — element count)
printf("%td\n", diff);

Pointer arithmetic is only valid within a single array object (or one past the end). Arithmetic on unrelated pointers is undefined behavior.


Memory management

7. What is the difference between malloc, calloc, realloc, and free?

Function Purpose Initialization
malloc(size) Allocate size bytes Uninitialized (garbage)
calloc(n, size) Allocate n * size bytes Zero-initialized
realloc(ptr, size) Resize allocation New bytes uninitialized
free(ptr) Release memory
// malloc
int *a = malloc(5 * sizeof(int));
if (!a) { perror("malloc"); exit(1); }

// calloc — elements are zeroed
int *b = calloc(5, sizeof(int));   // b[0]..b[4] == 0

// realloc
a = realloc(a, 10 * sizeof(int));
if (!a) { /* original memory NOT freed on failure */ exit(1); }

free(a);
free(b);

Common mistakes:

  • Not checking for NULL return
  • Using memory after free (use-after-free)
  • Double-freeing a pointer
  • Storing realloc result in the original pointer (leaks on failure)

8. What is the difference between stack and heap memory?

Property Stack Heap
Allocation Automatic (function entry/exit) Manual (malloc/free)
Size Limited (~1–8 MB typical) Limited by RAM
Speed Very fast (just moves stack pointer) Slower (OS allocation)
Scope Local to function Persists until freed
Fragmentation None Possible
Overflow risk Stack overflow (deep recursion) Memory leak / OOM
void example() {
    int stack_var = 10;          // stack
    int *heap_var = malloc(4);   // heap
    *heap_var = 20;
    free(heap_var);              // required
}                                // stack_var freed automatically

9. What is a memory leak? How do you detect and prevent it?

A memory leak occurs when allocated heap memory is never freed, causing the program's memory usage to grow indefinitely.

// Leak: ptr reassigned without freeing
int *ptr = malloc(100);
ptr = malloc(200);  // first allocation lost — LEAK

// Fix
int *ptr = malloc(100);
free(ptr);
ptr = malloc(200);
free(ptr);

Detection tools:

  • Valgrind (valgrind --leak-check=full ./prog) — reports all leaks
  • AddressSanitizer (gcc -fsanitize=address) — runtime detection
  • Electric Fence — catches buffer overruns and use-after-free

Prevention patterns:

  • Every malloc has a corresponding free
  • Use goto/label cleanup pattern in C for multiple exit paths
  • RAII-equivalent: wrapper macros that auto-free on scope exit (GLib style)

10. What is a dangling pointer?

A dangling pointer points to memory that has been freed or gone out of scope.

int *p = malloc(sizeof(int));
*p = 10;
free(p);
printf("%d\n", *p);  // UNDEFINED BEHAVIOR — dangling pointer

// Fix: set to NULL after free
free(p);
p = NULL;
if (p) { /* safe */ }
Type Cause
Freed heap free(p) then use p
Out-of-scope stack Return pointer to local variable
Reallocated realloc moved the block

Strings

11. How are strings represented in C?

C strings are null-terminated arrays of char. The null terminator ('\0', value 0) marks the end.

char s1[] = "hello";          // {'h','e','l','l','o','\0'} — 6 bytes
char *s2 = "hello";           // string literal — read-only in most implementations
char s3[10] = "hello";        // 10 bytes, null-padded

printf("%zu\n", strlen(s1));  // 5 (excludes '\0')
printf("%zu\n", sizeof(s1));  // 6 (includes '\0')
printf("%zu\n", sizeof(s2));  // pointer size (4 or 8)

Key distinction: sizeof gives allocation size; strlen gives string length (scanning for '\0').


12. What are the common string functions in <string.h>?

Function Purpose Safe alternative
strlen(s) Length (excl. \0)
strcpy(dst, src) Copy string strncpy / strlcpy
strcat(dst, src) Concatenate strncat / strlcat
strcmp(a, b) Compare (0=equal, <0, >0) strncmp
strchr(s, c) Find first char
strstr(s, sub) Find substring
memcpy(dst, src, n) Copy n bytes
memset(dst, c, n) Fill n bytes
snprintf(buf, n, fmt, ...) Safe formatted write Prefer over sprintf
char src[] = "world";
char dst[20];

strcpy(dst, "hello ");
strcat(dst, src);
printf("%s\n", dst);  // hello world

// Safe version with size limit
char buf[10];
snprintf(buf, sizeof(buf), "%s", "longer than ten characters");
printf("%s\n", buf);  // "longer th" — truncated safely

13. What is a buffer overflow and how do you prevent it?

A buffer overflow writes past the end of a buffer, corrupting adjacent memory or enabling exploits.

// UNSAFE
char buf[8];
scanf("%s", buf);            // reads unlimited input — overflow possible
gets(buf);                   // NEVER USE gets() — removed in C11

// SAFE
char buf[8];
scanf("%7s", buf);           // limit to 7 chars + null terminator
fgets(buf, sizeof(buf), stdin);  // preferred: includes newline, null-safe

Prevention:

  • Always specify buffer size in format strings or function arguments
  • Use snprintf instead of sprintf
  • Use strncpy/strncat (or strlcpy/strlcat on BSD/macOS)
  • Enable stack canaries (-fstack-protector) and ASLR
  • Use static analysis (Clang-Tidy, PVS-Studio)

Structs and data types

14. What is structure padding?

The compiler inserts padding bytes between struct members to satisfy alignment requirements, which can make a struct larger than the sum of its members.

struct A {
    char  c;   // 1 byte
    // 3 bytes padding
    int   i;   // 4 bytes
    char  d;   // 1 byte
    // 3 bytes padding
};
printf("%zu\n", sizeof(struct A));  // 12

// Reorder to minimize padding
struct B {
    int   i;   // 4 bytes
    char  c;   // 1 byte
    char  d;   // 1 byte
    // 2 bytes padding
};
printf("%zu\n", sizeof(struct B));  // 8

Use #pragma pack(1) or __attribute__((packed)) to remove padding (risk: unaligned access penalty on some CPUs).


15. What are bit fields in C?

Bit fields allow packing multiple boolean/small-integer values into a single word.

struct Flags {
    unsigned int read    : 1;
    unsigned int write   : 1;
    unsigned int execute : 1;
    unsigned int unused  : 5;  // padding to byte boundary
};

struct Flags f = {1, 0, 1, 0};
printf("%d %d %d\n", f.read, f.write, f.execute);  // 1 0 1
printf("%zu\n", sizeof(f));  // 4 (implementation-defined)

Use cases: hardware register maps, network protocol headers, compact flag storage.


16. What is the difference between struct, union, and enum?

// struct: all members exist, total size = sum of members + padding
struct Point { int x; int y; };           // 8 bytes

// union: members share memory, size = largest member
union Data { int i; float f; char c; };   // 4 bytes
union Data d;
d.i = 42;  // OK
d.f = 1.5; // overwrites d.i — only last written is valid

// enum: named integer constants
enum Color { RED = 0, GREEN = 1, BLUE = 2 };
enum Color c = GREEN;

Unions are commonly used for type-punning (with caveats about strict aliasing) and tagged unions (variant types).


Preprocessor

17. What is the difference between #define and const?

Aspect #define const
Type checking None — text substitution Yes — type-safe
Debugger visibility No (replaced before compile) Yes
Scope From definition to end of file Block/file scope
Memory No storage Storage (optimized away often)
Expressions Can define macros Values only
#define MAX 100           // text replacement: MAX → 100
const int MAX = 100;      // typed constant

// Prefer const for simple values
// Use #define for token manipulation, include guards, conditional compilation

18. What are include guards and why are they needed?

Include guards prevent a header file from being included multiple times in the same translation unit, which would cause redefinition errors.

// myheader.h
#ifndef MYHEADER_H
#define MYHEADER_H

struct Point { int x; int y; };
void print_point(struct Point p);

#endif /* MYHEADER_H */

Modern alternative: #pragma once (not standard, but supported by all major compilers):

#pragma once
struct Point { int x; int y; };

19. What are variadic macros?

Macros that accept a variable number of arguments using ... and __VA_ARGS__.

#include <stdio.h>

#define DEBUG(fmt, ...) \
    fprintf(stderr, "[DEBUG] %s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)

DEBUG("value = %d", 42);
// Output: [DEBUG] main.c:10: value = 42

##__VA_ARGS__ removes the preceding comma when no extra args are passed (GCC extension, widely supported).


Bit manipulation

20. What are the bitwise operators in C?

Operator Name Example
& AND 5 & 3 = 1 (0101 & 0011 = 0001)
| OR 5 | 3 = 7 (0101 | 0011 = 0111)
^ XOR 5 ^ 3 = 6 (0101 ^ 0011 = 0110)
~ NOT ~5 = -6 (two's complement)
<< Left shift 5 << 1 = 10
>> Right shift 20 >> 2 = 5
// Common bit tricks
int x = 0b1010;

// Set bit n
x |= (1 << 2);       // set bit 2: 0b1010 | 0b0100 = 0b1110

// Clear bit n
x &= ~(1 << 1);      // clear bit 1: 0b1110 & 0b1101 = 0b1100

// Toggle bit n
x ^= (1 << 3);       // toggle bit 3

// Test bit n
int is_set = (x >> 2) & 1;

// Check power of two
int is_pow2 = n > 0 && (n & (n - 1)) == 0;

// Swap without temp
a ^= b; b ^= a; a ^= b;

21. How do you count set bits (popcount)?

// Naive O(n) per bit
int count_bits(unsigned int n) {
    int count = 0;
    while (n) {
        count += n & 1;
        n >>= 1;
    }
    return count;
}

// Brian Kernighan's trick: O(set bits)
int count_bits_fast(unsigned int n) {
    int count = 0;
    while (n) {
        n &= n - 1;  // clears lowest set bit
        count++;
    }
    return count;
}

// GCC/Clang built-in (maps to single CPU instruction)
int c = __builtin_popcount(42);

Functions and scope

22. What is the difference between pass by value and pass by pointer in C?

C is strictly pass-by-value — a copy of the argument is made. To "pass by reference," you pass a pointer.

// Pass by value — original unchanged
void double_val(int x) { x *= 2; }

// Pass by pointer — modifies original
void double_ptr(int *x) { *x *= 2; }

int n = 5;
double_val(n);   printf("%d\n", n);   // 5 — unchanged
double_ptr(&n);  printf("%d\n", n);   // 10 — modified

For large structs, passing by pointer is more efficient even when modification isn't needed (pass const struct Foo *).


23. What are static variables and functions?

The static keyword has two distinct meanings:

// 1. static local variable: persists across function calls
void counter() {
    static int count = 0;  // initialized once; retains value
    count++;
    printf("%d\n", count);
}
counter();  // 1
counter();  // 2
counter();  // 3

// 2. static at file scope: limits linkage to current file
// (only visible within the same .c file)
static int helper_var = 42;
static void helper_func(void) { /* internal */ }

Static functions are the C equivalent of private in C++/Java.


24. What is extern in C?

extern declares that a variable or function is defined in another translation unit.

// file1.c
int global_var = 10;

// file2.c
extern int global_var;  // declaration — no new storage allocated
void use_it() {
    printf("%d\n", global_var);  // accesses file1.c's global_var
}
Keyword Purpose
extern Declare without defining (use from another file)
static (file scope) Define with internal linkage (hide from other files)
(no keyword) Define with external linkage (visible to all files)

25. What is a recursive function? What are its risks?

// Fibonacci (exponential — O(2^n))
int fib(int n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2);
}

// Factorial — O(n) recursive
long long fact(int n) {
    if (n <= 1) return 1;
    return n * fact(n - 1);
}

Risks:

  • Stack overflow — each call consumes stack frame (~100–1000 bytes); deep recursion crashes
  • No tail-call optimization guaranteed in C (GCC optimizes -O2 with -foptimize-sibling-calls)
  • Performance — function call overhead vs iteration

Rule of thumb: Prefer iteration for >1000 depth; use dynamic programming to memoize recursive solutions.


Storage classes and qualifiers

26. What are the four storage classes in C?

Storage class Scope Lifetime Default init
auto Block Block duration Garbage
register Block Block duration Garbage (hint to compiler)
static Block or file Program duration Zero
extern File Program duration Zero

auto is the default for local variables. register is a hint to store in a CPU register (ignored by modern compilers).


27. What does the volatile qualifier do?

volatile tells the compiler not to optimize reads/writes to a variable because it may change unexpectedly (hardware register, ISR, shared memory).

// Without volatile, compiler may cache value in register
volatile int *hardware_reg = (volatile int *)0xDEADBEEF;

// Reads always go to memory
while (*hardware_reg == 0) { /* wait for hardware */ }

Common uses:

  • Memory-mapped I/O registers
  • Variables modified by interrupt service routines
  • Variables in signal handlers
  • Shared memory (though prefer _Atomic or pthread primitives for concurrent code)

28. What does the const qualifier do?

const int x = 10;          // x is read-only

// Pointer and const combinations
const int *p1 = &x;        // pointer to const int — *p1 can't change
int * const p2 = &x;       // const pointer to int — p2 can't change (address)
const int * const p3 = &x; // both pointer and value are const

// In function parameters: communicate "won't modify"
void print_str(const char *s) { /* s content is read-only */ }

const is about the programmer's intent and enables compiler warnings + optimizations. It does not make data immutable at runtime.


Arrays and memory layout

29. What is the relationship between arrays and pointers?

An array name decays to a pointer to its first element in most expressions. However, arrays are not pointers.

int arr[] = {1, 2, 3, 4, 5};
int *p = arr;     // arr decays to &arr[0]

// Equivalent access
arr[2] == *(arr + 2) == p[2] == *(p + 2)  // all true

// Key differences
sizeof(arr) == 20   // 5 * 4 — full array size
sizeof(p)   == 8    // pointer size only

// Array of arrays (2D)
int mat[3][4];
// mat[i][j] == *(*(mat + i) + j)

Array decay is why passing an array to a function loses size information — always pass the length separately or use a struct wrapper.


30. How do you dynamically allocate a 2D array?

int rows = 3, cols = 4;

// Method 1: Array of pointers (non-contiguous rows)
int **mat = malloc(rows * sizeof(int *));
for (int i = 0; i < rows; i++)
    mat[i] = malloc(cols * sizeof(int));

mat[1][2] = 42;

for (int i = 0; i < rows; i++) free(mat[i]);
free(mat);

// Method 2: Single contiguous block (cache-friendly)
int (*mat2)[cols] = malloc(rows * cols * sizeof(int));
mat2[1][2] = 42;
free(mat2);

Method 2 (pointer to VLA) is cache-friendlier and requires only one free. Method 1 is more flexible for jagged arrays.


Type system and conversions

31. What is implicit type conversion (promotion) in C?

char c = 65;
int i = c;        // char promoted to int — 65
float f = i;      // int converted to float — 65.0
double d = f;     // float promoted to double

// Integer arithmetic promotion
short a = 10, b = 20;
int result = a + b;   // a and b both promoted to int before addition

// Potential pitfall
unsigned int u = 10;
int s = -1;
if (s < u) { ... }   // WRONG — s converted to unsigned, becomes huge positive number

Integer promotion rules (simplified):

  1. char/short/boolint
  2. In mixed signed/unsigned: unsigned wins
  3. In mixed float/int: float wins

32. What is the difference between int, long, long long, and their sizes?

Sizes are implementation-defined but have minimums:

Type Minimum bits Typical LP64 (Linux/macOS 64-bit)
char 8 8
short 16 16
int 16 32
long 32 64
long long 64 64
size_t 64
ptrdiff_t 64

Use <stdint.h> for exact widths: int8_t, int16_t, int32_t, int64_t, uint32_t, etc.

#include <stdint.h>
int32_t a = 2147483647;
uint64_t b = 18446744073709551615ULL;

Standard library and I/O

33. What is the difference between printf and fprintf?

#include <stdio.h>

printf("Hello\n");                    // prints to stdout
fprintf(stdout, "Hello\n");          // same as printf
fprintf(stderr, "Error: %s\n", msg); // prints to stderr (for errors/diagnostics)
fprintf(file_ptr, "Value: %d\n", x); // prints to any FILE*

Format specifiers:

Specifier Type
%d, %i int
%u unsigned int
%ld long
%lld long long
%f float/double
%lf double (in scanf)
%c char
%s char * (string)
%p pointer
%zu size_t
%x hex (lowercase)

34. How does scanf differ from fgets?

// scanf — stops at whitespace, unsafe for strings
char name[50];
scanf("%s", name);           // reads one word only, no width check
scanf("%49s", name);         // safer: limits to 49 chars

// fgets — reads a full line including spaces, null-terminates
fgets(name, sizeof(name), stdin);
// Note: fgets includes the '\n' in the buffer if buffer is large enough
name[strcspn(name, "\n")] = '\0';  // strip newline

Prefer fgets + sscanf for parsing user input — it handles whitespace correctly and avoids buffer overflows.


Compilation and linking

35. What are the stages of C compilation?

Source (.c) → Preprocessor → Compiler → Assembler → Linker → Executable
Stage Tool Input → Output Flag to stop
Preprocessing cpp .c.i gcc -E
Compilation cc1 .i.s gcc -S
Assembly as .s.o gcc -c
Linking ld .o → executable (none)
gcc -E main.c -o main.i    # preprocessed
gcc -S main.c -o main.s    # assembly
gcc -c main.c -o main.o    # object file
gcc main.o -o main         # link
gcc -Wall -O2 main.c -o main  # typical full build

36. What is the difference between a header file and a source file?

Header (.h)                    Source (.c)
──────────────────────         ──────────────────────
Declarations                   Definitions
Function prototypes            Function bodies
Type definitions (struct)      Variable storage
Macros                         malloc/logic
extern variable declarations   extern variable definitions

Headers are #included into multiple .c files. Definitions must appear exactly once across all translation units (One Definition Rule). Guard against including definitions in headers to avoid multiple-definition linker errors.


Common patterns and algorithms

37. How do you reverse a string in C?

#include <string.h>

void reverse_string(char *s) {
    int left = 0, right = strlen(s) - 1;
    while (left < right) {
        char tmp = s[left];
        s[left] = s[right];
        s[right] = tmp;
        left++;
        right--;
    }
}

char s[] = "hello";
reverse_string(s);
printf("%s\n", s);  // olleh

38. How do you implement a linked list in C?

#include <stdlib.h>

typedef struct Node {
    int data;
    struct Node *next;
} Node;

// Prepend to list
Node *push(Node *head, int val) {
    Node *node = malloc(sizeof(Node));
    node->data = val;
    node->next = head;
    return node;
}

// Print list
void print_list(Node *head) {
    for (Node *cur = head; cur; cur = cur->next)
        printf("%d -> ", cur->data);
    printf("NULL\n");
}

// Free list
void free_list(Node *head) {
    while (head) {
        Node *next = head->next;
        free(head);
        head = next;
    }
}

// Usage
Node *list = NULL;
list = push(list, 3);
list = push(list, 2);
list = push(list, 1);
print_list(list);  // 1 -> 2 -> 3 -> NULL
free_list(list);

39. How do you implement a stack using an array?

#define MAX 100

typedef struct {
    int data[MAX];
    int top;
} Stack;

void init(Stack *s) { s->top = -1; }
int is_full(Stack *s)  { return s->top == MAX - 1; }
int is_empty(Stack *s) { return s->top == -1; }

void push(Stack *s, int val) {
    if (!is_full(s)) s->data[++(s->top)] = val;
}
int pop(Stack *s) {
    if (!is_empty(s)) return s->data[(s->top)--];
    return -1;  // error
}
int peek(Stack *s) { return s->data[s->top]; }

40. What is qsort and how do you use it?

qsort from <stdlib.h> sorts any array using a user-supplied comparator.

#include <stdlib.h>

int cmp_int(const void *a, const void *b) {
    return (*(int *)a - *(int *)b);   // ascending
}

int cmp_str(const void *a, const void *b) {
    return strcmp(*(char **)a, *(char **)b);
}

int arr[] = {5, 2, 8, 1, 9};
qsort(arr, 5, sizeof(int), cmp_int);
// arr is now {1, 2, 5, 8, 9}

// String array sort
char *words[] = {"banana", "apple", "cherry"};
qsort(words, 3, sizeof(char *), cmp_str);
// words is now {"apple", "banana", "cherry"}

The comparator must return negative/zero/positive for less/equal/greater.


Advanced topics

41. What is typedef and when should you use it?

// Without typedef
struct Point { int x; int y; };
struct Point p;    // must write "struct" every time

// With typedef
typedef struct Point { int x; int y; } Point;
Point p;           // cleaner

// Function pointer typedef
typedef int (*Comparator)(const void *, const void *);
Comparator cmp = cmp_int;

// Common idiom: opaque type
typedef struct _Handle Handle;  // forward declaration in header
// users never see the struct internals

42. What is the restrict keyword?

restrict (C99) tells the compiler that a pointer is the only way to access the object it points to, enabling optimizations by eliminating aliasing concerns.

// Without restrict: compiler must assume a and b might alias
void add(int *a, const int *b, int n) {
    for (int i = 0; i < n; i++) a[i] += b[i];
}

// With restrict: no aliasing — compiler can use SIMD/vectorize
void add_r(int * restrict a, const int * restrict b, int n) {
    for (int i = 0; i < n; i++) a[i] += b[i];
}

Misusing restrict (passing overlapping pointers) is undefined behavior. Standard library functions like memcpy use restrict; memmove does not.


43. What is inline in C?

inline is a hint to the compiler to expand the function body at the call site, eliminating function call overhead.

inline int max(int a, int b) {
    return a > b ? a : b;
}

Modern compilers (-O2) inline automatically based on heuristics. __attribute__((always_inline)) forces inlining; __attribute__((noinline)) prevents it.

Note: In C99/C11, inline functions with external linkage need one non-inline definition in a .c file, or use static inline.


44. What are flexible array members?

C99 allows the last member of a struct to be an incomplete array, enabling variable-length trailing data with a single malloc.

typedef struct {
    int length;
    int data[];  // flexible array member — no size specified
} IntArray;

// Allocate for length=5
IntArray *arr = malloc(sizeof(IntArray) + 5 * sizeof(int));
arr->length = 5;
arr->data[0] = 10;
arr->data[4] = 50;
free(arr);

This is the idiomatic C pattern for variable-length structures, used extensively in the Linux kernel.


45. What is setjmp/longjmp and when is it used?

Non-local jumps that transfer control across function boundaries — C's way to implement exceptions.

#include <setjmp.h>

jmp_buf buf;

void risky() {
    // something went wrong
    longjmp(buf, 1);  // jump back to setjmp, returning 1
}

int main() {
    int ret = setjmp(buf);  // returns 0 first time, non-zero on longjmp
    if (ret == 0) {
        risky();
    } else {
        printf("caught error: %d\n", ret);
    }
}

Caution: longjmp bypasses destructors and free — use carefully to avoid leaks. Not safe across signal handlers in all implementations.


C standard versions

46. What are the major differences between C89, C99, and C11?

Feature C89/ANSI C C99 C11
// comments No Yes Yes
bool No <stdbool.h> Yes
Variable declarations Top of block only Anywhere Anywhere
VLAs (variable-length arrays) No Yes (optional in C11) Optional
restrict No Yes Yes
stdint.h No Yes Yes
__VA_ARGS__ macros No Yes Yes
inline No Yes Yes
_Bool, _Complex No Yes Yes
_Generic (type selection) No No Yes
_Static_assert No No Yes
_Atomic No No Yes
Threads (<threads.h>) No No Yes (optional)

Use gcc -std=c11 -Wall -Wextra -pedantic for modern portable C.


Error handling

47. How is error handling done in C?

C has no exceptions. Conventions:

// Convention 1: return error code, output via pointer
int divide(int a, int b, int *result) {
    if (b == 0) return -1;  // error
    *result = a / b;
    return 0;               // success
}

// Convention 2: return -1/NULL, check errno
#include <errno.h>
FILE *f = fopen("file.txt", "r");
if (!f) {
    perror("fopen");   // prints: fopen: No such file or directory
    return 1;
}

// Convention 3: global errno for POSIX functions
int fd = open("file.txt", O_RDONLY);
if (fd < 0) {
    fprintf(stderr, "open failed: %s\n", strerror(errno));
}

Always check return values of memory allocation and I/O functions.


Debugging and tools

48. What debugging tools do C programmers use?

Tool Purpose Usage
gdb Interactive debugger gdb ./prog then run, bt, print
valgrind Memory error detection valgrind --leak-check=full ./prog
AddressSanitizer Fast memory error detection gcc -fsanitize=address,undefined
UndefinedBehaviorSanitizer UB detection gcc -fsanitize=undefined
strace System call trace strace ./prog
ltrace Library call trace ltrace ./prog
gprof Profiling gcc -pg then gprof
perf Linux performance counters perf stat ./prog
clang-tidy Static analysis clang-tidy main.c
cppcheck Static analysis cppcheck main.c
# Typical debugging build
gcc -g -O0 -Wall -Wextra -fsanitize=address,undefined -o prog main.c
gdb ./prog
(gdb) run
(gdb) backtrace
(gdb) print variable_name

Anti-patterns

49. What are common C programming mistakes?

Anti-pattern Problem Fix
gets(buf) Buffer overflow, removed in C11 Use fgets
Not checking malloc return Null dereference Always check if (!ptr)
int i = strlen(s) Signed/unsigned mismatch Use size_t i
Forgetting to free Memory leak Every malloc needs free
free(ptr) without ptr = NULL Dangling pointer double-free Set ptr = NULL after free
Returning pointer to local Dangling pointer Use heap allocation
scanf("%s", buf) No size limit Use scanf("%99s", buf)
char *s = "literal"; s[0] = 'x' Write to read-only segment (UB) Use char s[] = "literal"
if (ptr = NULL) Assignment in condition Use if (ptr == NULL) or if (!ptr)
Signed integer overflow Undefined behavior Use unsigned or check before op

Comparison

50. How does C compare to C++ and other languages?

Feature C C++ Rust Python
Memory management Manual Manual + RAII Ownership/borrow GC
OOP No (struct + fn pointers) Yes (classes, vtable) Traits Yes
Generics Macros / void* Templates Generics Duck typing
Error handling Return codes / errno Exceptions + codes Result<T,E> Exceptions
Runtime overhead None Minimal None Interpreter
Standard library Small (libc) Large (STL) Large (std) Very large
Compilation speed Fast Slow (templates) Slow N/A
Learning curve Medium High High Low
Best for OS/embedded/systems Systems + applications Safe systems Scripting/ML

C remains the lingua franca of systems programming — the Linux kernel, CPython, SQLite, Redis, and most embedded firmware are written in C.


Common mistakes

Mistake Explanation
Off-by-one in arrays arr[n] is out of bounds; valid range is arr[0]..arr[n-1]
Comparing char with EOF char c; c = getchar()EOF is -1, may be truncated to 0xFF; use int c
String comparison with == == compares pointers, not content; use strcmp
Modifying string literals char *s = "hello"; s[0] = 'H'; is UB; use char s[] = "hello"
Integer division truncation 5 / 2 == 2 (not 2.5); cast to float first
Implicit function declaration In C99+ all functions must be declared before use
Missing break in switch Falls through to next case silently
sizeof on decayed array void f(int a[]) { sizeof(a); } gives pointer size, not array size

C vs related languages

Language Relation to C
C++ Superset of C (mostly); adds classes, templates, RAII
Objective-C Strict superset of C; used for Apple OS before Swift
Go Designed by C authors; GC, goroutines, no manual memory
Rust Systems language like C; no GC; compile-time memory safety
Zig Modern C replacement; comptime, no macros, better error handling
Python (CPython) Interpreter written in C; C extensions via Python/C API

FAQ

Q: Is C still worth learning in 2025? A: Yes — it's required for OS development, embedded systems, game engines, database internals, compilers, and understanding how higher-level languages work. C is also the FFI base for almost every language runtime.

Q: What is undefined behavior (UB) in C? A: Operations where the C standard imposes no requirements on the result — the compiler may emit anything. Common UBs: signed integer overflow, dereferencing null/dangling pointers, accessing out-of-bounds array elements, use-after-free, reading uninitialized variables.

Q: What is the difference between char *s = "hello" and char s[] = "hello"? A: char *s = "hello" is a pointer to a string literal (usually read-only). char s[] = "hello" copies the string into a local array (writable). Modifying through a char * literal pointer is undefined behavior.

Q: What is two's complement and why does it matter? A: Two's complement is the standard way CPUs represent signed integers. In two's complement, -1 is 0xFFFFFFFF for a 32-bit int. C11 mandates two's complement for all signed integers. It makes addition/subtraction hardware the same for signed and unsigned, but overflow of signed integers is still UB in C.

Q: Should I use int or size_t for loop counters over arrays? A: Use size_t (from <stddef.h> or <stdlib.h>) when iterating over arrays or dealing with object sizes — it matches sizeof/strlen return types and avoids signed/unsigned comparison warnings. Use int for mathematical loops where negative values are meaningful.

Q: What is the difference between malloc(0) and a null pointer? A: malloc(0) is implementation-defined — it may return either NULL or a unique non-null pointer that must not be dereferenced. Always check the return and treat a zero-size allocation as an edge case to handle explicitly.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools