Programming in C
Arrays, Structures, Unions and Strings
A C array's name, in almost every expression context, DECAYS to a pointer to its first element — arr[i] is defined as *(arr + i), which is why array indexing and pointer arithmetic give identical results and sizeof(arr) (the whole array's byte size) differs sharply from sizeof(ptr) once arr has decayed into a function parameter (there it is just a pointer's own size). A structure groups named, independently-stored fields — its total size is generally at least the sum of its members' sizes, and often more, because of padding the compiler inserts for each member's own alignment requirement. A string in C is not a distinct type at all: it is a char array terminated by a '\0' (NUL) byte, so every string function (strlen, strcpy, strcat) scans until it finds that terminator, and forgetting to leave room for it is a standard buffer-overflow source.
int arr[10] has sizeof(arr) == 40 (on a 4-byte int). Once passed as a function PARAMETER, void f(int arr[10]) is silently rewritten by the compiler to void f(int *arr), and sizeof(arr) inside f gives the pointer's own size (commonly 8 on a 64-bit system) — the array's original length is simply gone unless passed separately.Pointers and Functions
- A pointer variable holds an address;
*pdereferences it (accesses the pointed-to value),&xtakes the address of x. Pointer arithmetic is scaled by the pointed-to type's size:p + 1on anint*advances bysizeof(int)bytes, not by 1 byte — this is what makes array indexing via pointers correct regardless of element size. - A function pointer holds the address of a function's code and can be called through, assigned or passed as a callback — the mechanism behind qsort's comparator argument and, more generally, behind any C-style plug-in dispatch table.
- Dynamic memory (
malloc/calloc/realloc/free) is allocated on the heap and outlives the function that allocated it — the classic bugs are a memory leak (forgettingfree), a dangling pointer (using memory after it is freed) and a double free (freeing the same block twice).
File Handling, Command-Line Arguments and the Preprocessor
- File handling:
fopenreturns aFILE*(NULL on failure — always checked before use);fread/fwritedo binary I/O,fscanf/fprintfdo formatted text I/O;fcloseflushes buffered output and releases the handle, and forgetting it can lose buffered data that was never actually written to disk. - Command-line arguments arrive in
main's parametersint argc, char *argv[]— argc is the count including the program name itself (so argv[0] is the program's own name, and the first real user argument is argv[1]). - Preprocessor directives (
#define,#include,#ifdef/#ifndef, macros with parameters) run BEFORE compilation proper, doing pure textual substitution — a macro is not type-checked and not scoped like a function, which is why a parenthesised macro#define SQ(x) ((x)*(x))needs those parentheses to avoid silently wrong expansions likeSQ(a+b)without them.
Key takeaways
- Storage class (auto/static/extern/register) and scope are independent —
staticinside a function extends lifetime to the whole program while keeping scope local. - C is strictly call-by-value; affecting a caller's variable requires passing its address and dereferencing inside the callee.
- An array parameter silently becomes a pointer — sizeof gives the array's true size only inside the function it was declared in, never after being passed as a parameter.
- A C string is a char array terminated by NUL, not a distinct type; forgetting room for the terminator is a standard buffer-overflow source.
- The preprocessor does pure textual substitution before compilation — an unparenthesised macro parameter can silently produce a wrong expansion.
Practice questions (8)
Attempt each one before opening the answer. Every explanation names the tempting wrong option as well as the right one, because that is where marks are lost.
A variable declared
staticinside a C function has:Show answer
Answer: A — Local scope but a lifetime extending across the whole program
static changes lifetime (the variable retains its value across calls, allocated once for the program's whole run) without changing scope, which stays confined to the function it is declared in.In C, to make a function change the value of a caller's variable, the function must:
Show answer
Answer: A — Receive the variable's address (a pointer) as a parameter and dereference it
C is strictly call-by-value; an ordinary parameter is a copy. Passing the variable's address explicitly and writing through the dereferenced pointer inside the callee is the only way to affect the caller's own storage.Given
int arr[10];declared and used within the SAME function,sizeof(arr)(with a 4-byte int) equals:Show answer
Answer: A — 40
Within its own declaring function, an array has not yet decayed to a pointer, so sizeof gives the array's TOTAL byte size: 10 elements × 4 bytes = 40. This changes to a pointer's own size (commonly 8) only once arr is passed as a function parameter.A union in C, compared to a structure with the same fields:
Show answer
Answer: A — Overlays all fields on the same memory, sized to its largest member
A union's size is that of its largest member, since every field shares the same storage; a structure's size is at least the sum of all its members' sizes (plus any alignment padding).In C, a string is terminated by:
Show answer
Answer: A — A NUL byte ('\0')
Unlike a length-prefixed string representation, C represents a string purely as a char array ending in the NUL byte, and every standard string function scans for that byte to know where the string ends.In
main(int argc, char *argv[]), argv[0] holds:Show answer
Answer: A — The program's own name/invocation path
argc counts the program's own name as one of the arguments, so argv[0] is that name/path and the first genuine user-supplied argument is argv[1] — a common off-by-one trap in loops that walk argv.Which of the following are classic dynamic-memory bugs in C? (More than one option may be correct.)
Show answer
Answer: A — Memory leak — forgetting to free allocated memory; B — Dangling pointer — using memory after it has been freed; C — Double free — freeing the same block twice
Memory leaks, dangling pointers and double frees are the three classic manual-memory-management bugs; static typing is an unrelated, ordinary and correct feature of C's type system, not a bug.The preprocessor directive used to conditionally compile code only if a macro is NOT already defined is #ifndef or, alternatively, #if !defined(...). Name the shorter of these two directive keywords (without the leading #).
Numerical answer — type the value.
Show answer
Answer: ifndef
#ifndef("if not defined") is the standard directive used for header guards, preventing a header's contents from being included more than once in the same translation unit.