Pointers are central to C, serving as variables that store the memory address of another value. They are declared with the star operator, as in int *ptr, and the address-of operator & produces a variable's address to assign to a pointer. Dereferencing with * accesses the value at the address a pointer holds. A NULL pointer is one that points to no valid memory location, and it is essential to check for NULL before dereferencing to avoid segmentation faults. Beyond ordinary pointers, C supports void pointers, which are generic and can point to any data type but require a cast before dereferencing, and double pointers, which store the address of another pointer and are useful for modifying a pointer inside a function or for arrays of strings like char **argv.
Pointer arithmetic automatically scales by the size of the pointed-to type. Incrementing an int * advances by sizeof(int) bytes rather than by one, which is why expressions like *(p + i) work correctly for any array regardless of element size. Subtracting two pointers yields the number of elements between them, not the raw byte difference. The const keyword has different effects depending on placement: const int *p means the value cannot be changed through p, int *const p means p itself cannot be reassigned, and const int *const p locks down both. The sizeof operator returns the size in bytes of a type or variable at compile time, and the idiom sizeof(arr) / sizeof(arr[0]) recovers an array's length; for pointers, however, sizeof returns the pointer size rather than the size of the pointed-to data.
C manages memory in two main regions: the stack, which automatically holds local variables and is fast but limited, and the heap, which is larger but requires manual management. malloc(size) allocates uninitialized memory on the heap and returns NULL on failure, while calloc(count, size) zero-initializes all bytes and additionally checks for overflow in count * size, making it safer than computing that product yourself. realloc(ptr, new_size) resizes an existing allocation and may relocate it, so its result should always be assigned to a temporary pointer to avoid losing the original block on failure. Every dynamically allocated block must be released with free; forgetting causes a memory leak, and after freeing you should set the pointer to NULL to prevent a dangling pointer from causing undefined behavior on later use. Tools like Valgrind help detect leaks during development.