C Programming

Pointers, arrays, structures, memory

Practice →

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.

Complexity / Key Facts

Pointer Arithmetic: *(ptr + i) = ptr + i * sizeof(data_type)\text{Pointer Arithmetic: *(ptr + i) = ptr + i * sizeof(data\_type)}
Array Indexing: arr[i] = *(arr + i) = *(i + arr)\text{Array Indexing: arr[i] = *(arr + i) = *(i + arr)}
Sizeof Array: sizeof(array) / sizeof(array[0]) = number of elements\text{Sizeof Array: sizeof(array) / sizeof(array[0]) = number of elements}
Structure Padding: sizeof(struct) >= sum of individual members (may include padding)\text{Structure Padding: sizeof(struct) >= sum of individual members (may include padding)}
Bitwise AND: a & b = 1 if both bits are 1\text{Bitwise AND: a \& b = 1 if both bits are 1}
Bitwise OR: a | b = 1 if at least one bit is 1\text{Bitwise OR: a | b = 1 if at least one bit is 1}
Bitwise XOR: a ^ b = 1 if bits are different\text{Bitwise XOR: a \textasciicircum{} b = 1 if bits are different}
Left Shift: a << n = a * 2^n\text{Left Shift: a << n = a * 2\textasciicircum{}n}
Right Shift: a >> n = a / 2^n (for positive numbers)\text{Right Shift: a >> n = a / 2\textasciicircum{}n (for positive numbers)}
Two’s Complement: ~x + 1 = negative of x\text{Two's Complement: \textasciitilde{}x + 1 = negative of x}
Memory Allocation: malloc(n * sizeof(type)) allocates n elements\text{Memory Allocation: malloc(n * sizeof(type)) allocates n elements}
\text{String Length: strlen(s) counts characters until ''}
Precedence: Postfix > Unary > Multiplicative > Additive > Shift > Relational > Equality > Bitwise > Logical > Ternary > Assignment\text{Precedence: Postfix > Unary > Multiplicative > Additive > Shift > Relational > Equality > Bitwise > Logical > Ternary > Assignment}

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.

Preprocessor Directives

The C preprocessor runs before compilation. #include inserts header files. #define creates macros - object-like (constants) or function-like (with parameters). Use parentheses around macro parameters to avoid precedence issues. #ifdef, #ifndef, #if, #else, #elif, #endif provide conditional compilation. #pragma gives compiler-specific instructions. Predefined macros: __FILE__, __LINE__, __DATE__, __TIME__, __func__. Header guards (#ifndef/#define/#endif) prevent multiple inclusion. The # operator converts to string, ## concatenates tokens. Macros don't have type checking and can cause multiple evaluation of arguments.

Tips

  • 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.

Practice with questions

Real placement-style technical questions.

Start Exercise →