C Programming
Pointers, arrays, structures, memory
C programming is a procedural, general-purpose programming language developed by Dennis Ritchie at Bell Labs in 1972. It is widely used for system programming, embedded systems, and developing operating systems due to its efficiency and direct hardware control capabilities. C provides low-level memory access through pointers, rich set of built-in operators, and a comprehensive standard library.
Key formulas and rules
Key concepts
Pointers and Pointer Arithmetic
A pointer is a variable that stores the memory address of another variable. In C, pointers are declared using the * operator. Key operations include: 1) Address-of (&): Gets the memory address of a variable, 2) Dereference (*): Accesses the value at a pointer's address, 3) Pointer arithmetic: Adding/subtracting integers to pointers moves by multiples of the data type size. For example, if ptr points to an int (4 bytes), ptr+1 actually adds 4 to the address. Void pointers can point to any data type but cannot be dereferenced directly. Function pointers store addresses of functions and enable callbacks. NULL pointer has value 0 and points to nothing.
Arrays and Strings
Arrays in C are contiguous memory blocks storing elements of the same type. Array name acts as a pointer to the first element, so arr[i] is equivalent to *(arr+i). Multi-dimensional arrays are stored in row-major order. Strings are character arrays terminated by '' (null character). The %s format specifier reads until whitespace, while %c reads a single character. Common string functions include: strcpy(), strcat(), strlen(), strcmp(), strstr(), strtok(). Important: String literals are stored in read-only memory, so modifying them causes undefined behavior. Character arrays must have space for the null terminator. fgets() is safer than gets() for string input.
Structures and Unions
A structure (struct) is a user-defined data type that groups variables of different types. Memory layout includes padding bytes for alignment optimization. The sizeof a struct may be larger than the sum of its members. Access members using dot operator (.) for structs and arrow operator (->) for struct pointers. A union is similar but all members share the same memory location - only one member can hold a value at a time. The union size equals its largest member. Typedef creates aliases for data types, making code cleaner. Nested structures allow structures within structures. Self-referential structures (containing pointers to same type) are essential for linked lists and trees.
Dynamic Memory Management
C provides four functions for dynamic memory: malloc() allocates uninitialized memory, calloc() allocates and zero-initializes, realloc() resizes existing allocation, and free() releases memory. These functions operate on the heap. Always check if allocation returned NULL. Memory leaks occur when allocated memory is not freed. Dangling pointers point to freed memory. Double-free errors cause undefined behavior. Wild pointers are uninitialized pointers. Buffer overflow happens when writing beyond allocated memory. Valgrind and similar tools help detect memory issues. Good practice: Set pointers to NULL after freeing.
Bit Manipulation
C provides six bitwise operators: & (AND), | (OR), ^ (XOR), ~ (NOT), << (left shift), >> (right shift). Left shift by n multiplies by 2^n. Right shift divides by 2^n for positive numbers (implementation-defined for negatives). Common patterns: Setting a bit (x | (1<<n)), Clearing a bit (x & ~(1<<n)), Toggling a bit (x ^ (1<<n)), Checking a bit (x & (1<<n)). Bit fields in structures allow compact storage. Bitwise operations are faster than arithmetic and essential for embedded programming, device drivers, and low-level hardware control.
Worked examples
Example 1
What is the output of:
int arr[] = {10, 20, 30, 40, 50};
int *ptr = arr;
printf("%d", *(ptr++));
printf("%d", *++ptr);
printf("%d", (*ptr)++);
Step 1: ptr is initialized to point to arr[0] (value 10)
Step 2: *(ptr++) uses post-increment, so it prints *ptr (10) first, then ptr moves to arr[1]
Step 3: *++ptr uses pre-increment, so ptr moves to arr[2] first, then prints 30
Step 4: (*ptr)++ uses post-increment on the value, so it prints 30 first, then arr[2] becomes 31
Output: 10 30 30
Final array: {10, 20, 31, 40, 50}
Example 2
Find the output:
int x = 5, y = 10;
if (x++ > 5 && ++y > 10)
printf("True");
else
printf("False, x=%d, y=%d", x, y);
Step 1: x++ > 5 evaluates to (5 > 5) which is false, then x becomes 6
Step 2: Due to short-circuit evaluation, ++y > 10 is NOT evaluated because the first condition was false
Step 3: The if condition is false, so the else block executes
Step 4: x is now 6 (incremented), y remains 10 (not incremented)
Output: False, x=6, y=10
Key concept: && short-circuits - if left operand is false, right operand is not evaluated.
Example 3
What does this code print?
char str[] = "Hello";
char *p = str;
while (*p) {
printf("%c", *p++);
if (*(p-1) == 'l') break;
}
Step 1: p points to 'H' (str[0])
Step 2: Loop 1: *p is 'H' (non-zero, so true), print 'H', then p increments to 'e'
Step 3: Check *(p-1) which is 'H', not 'l', continue
Step 4: Loop 2: *p is 'e', print 'e', p increments to 'l'
Step 5: Check *(p-1) which is 'e', not 'l', continue
Step 6: Loop 3: *p is 'l', print 'l', p increments to 'l'
Step 7: Check *(p-1) which is 'l', matches 'l', break
Output: Hel
The loop prints characters until 'l' is encountered and printed.
Example 4
Analyze the output:
struct Point {int x; char y;};
printf("%lu", sizeof(struct Point));
// Assuming: int=4 bytes, char=1 byte
Step 1: struct Point has two members: int x (4 bytes) and char y (1 byte)
Step 2: Raw size would be 4 + 1 = 5 bytes
Step 3: However, padding is added for alignment. After char y, 3 padding bytes are added
Step 4: This ensures that if the struct were in an array, each Point starts at a 4-byte boundary
Step 5: Total size = 4 (x) + 1 (y) + 3 (padding) = 8 bytes
Output: 8 (on most systems)
Note: Actual padding depends on compiler and platform, but typically 8 on 32/64-bit systems.
Representative solved questions
See the kind of question in this topic before opening the full practice set.
Question 1
What is the output of:
#include <stdio.h>
int main() {
int x = 5;
printf("%d", ++x + x++);
return 0;
}
11
12
13
Undefined behavior
Answer: D. Undefined behavior
ExplanationStep 1: The expression ++x + x++ has undefined behavior in C.
Step 2: The reason is that x is modified twice between sequence points without an intervening sequence point.
Step 3: The pre-increment (++x) and post-increment (x++) both modify x, and their order of evaluation is unspecified.
Answer: Undefined behavior
Sources and review notes
This is an AISEA-authored practice question.
Review status: accepted · Reviewed 2026-08-13 · structure and answer-key checks, editorial quality checks, duplicate screening
Question 2
What is the output of:
#include <stdio.h>
int main() {
int arr[] = {1, 2, 3, 4, 5};
int *p = arr;
printf("%d", *(p + 2));
return 0;
}
1
2
3
4
Answer: C. 3
ExplanationStep 1: arr is initialized with values {1, 2, 3, 4, 5}.
Step 2: p points to the first element of arr (arr[0] = 1).
Step 3: *(p + 2) dereferences the pointer at offset 2 from p.
Step 4: p + 2 points to arr[2], which contains the value 3.
Answer: 3
Sources and review notes
This is an AISEA-authored practice question.
Review status: verified · Reviewed 2026-08-13 · structure and answer-key checks, worked-answer review, human editorial review, duplicate screening
Question 3
What is the output of:
#include <stdio.h>
int main() {
int a = 10, b = 20;
printf("%d", a+++++b);
return 0;
}
30
31
Compilation error
Undefined behavior
Answer: C. Compilation error
ExplanationThe expression a+++++b cannot be parsed as a valid C expression after tokenization, so the program fails to compile. Answer: Compilation error.
Sources and review notes
Common mistakes and useful habits
- Always initialize pointers to NULL or a valid address before use to avoid undefined behavior from wild pointers.
- Use sizeof() instead of hardcoding sizes - it makes code portable across different architectures.
- Check return value of malloc/calloc - if NULL, the allocation failed (out of memory).
- Remember that array parameters in functions decay to pointers - sizeof(array) won't work as expected inside functions.
- Use strncpy instead of strcpy when dealing with user input to prevent buffer overflows.
- When using bit manipulation with signed integers, be aware that right shift of negative numbers is implementation-defined.
- Use parentheses liberally in macros to avoid operator precedence bugs, especially with arithmetic expressions.
- Free dynamically allocated memory in the reverse order of allocation when possible - it helps catch double-free errors.
- Remember that sizeof(char) is always 1 by definition, but sizeof(int) can vary between platforms.
- In printf, %d is for int, %ld for long, %f for float/double, %c for char, %s for strings, %p for pointers.
Ready to test your understanding?
Work through 100 questions with explanations after each answer.