FAQ
Pointers FAQ
What is the difference between & and * in C?
& takes the address of a variable; * dereferences a pointer to access the value at that address. int x = 42; int *p = &x; takes the address of x and stores it in p. *p reads the value at that address (42). They are inverses: *(&x) == x and &(*p) == p for any valid x and p. In declarations, int *p means "p is a pointer to int". In expressions, *p means "the int at the address stored in p" and &x means "the address of x".
Why does my program segfault when I dereference a pointer?
Three common causes. First: dereferencing NULL. Always check the return value of malloc, fopen, strchr before dereferencing. Second: dereferencing a dangling pointer (pointer to freed or out-of-scope memory). Set pointers to NULL after free and never return pointers to local variables. Third: pointer arithmetic past the end of an array (buffer overflow). Check the array bounds before subscripting. Run under gdb to get the exact line of the dereference; run under Valgrind or with AddressSanitizer (-fsanitize=address) to find the source of the bad pointer.
What does pointer arithmetic actually do?
Pointer arithmetic moves a pointer by units of the pointed-to type, not by bytes. For int *p (4-byte int), p + 1 advances by 4 bytes. For char *p, p + 1 advances by 1 byte. For struct foo *p with sizeof(struct foo) = 24, p + 1 advances by 24 bytes. This is why arr[i] is equivalent to *(arr + i): the compiler multiplies i by the element size before adding to the base address. Pointer arithmetic is bounded by the original allocation; going past the end is undefined behavior.
How do I avoid memory leaks and dangling pointers?
In C: every malloc must have a matching free, and free must happen exactly once. Set pointers to NULL after free to prevent double-free. Use Valgrind (valgrind --leak-check=full ./prog) to catch leaks and AddressSanitizer (gcc -fsanitize=address) to catch use-after-free. In C++: prefer std::unique_ptr and std::shared_ptr over raw new and delete. unique_ptr automatically deletes the pointee when going out of scope; shared_ptr uses reference counting. Avoid raw new in modern C++; use std::make_unique and std::make_shared instead.
What is the difference between unique_ptr and shared_ptr?
unique_ptr is exclusive ownership: only one unique_ptr at a time owns the pointee, and copying is a compile error. Move semantics transfer ownership. Zero runtime overhead compared to raw new and delete. shared_ptr is reference-counted joint ownership: multiple shared_ptrs can own the same pointee, and the pointee is destroyed when the last shared_ptr is destroyed. Costs one heap allocation for the control block plus atomic increment and decrement on every copy. Default to unique_ptr unless ownership is genuinely shared; use shared_ptr for graph nodes referenced by multiple edges or shared cache entries.
How do I read a complex pointer declaration like int (*p)[10]?
Read inside-out from the variable name following operator precedence. int (*p)[10]: p is a pointer (because of *), to an array of 10 (because of [10]) of int. Compare with int *p[10]: p is an array of 10 pointers to int (because [] binds tighter than *). Compare with int (*f)(int): f is a pointer to a function taking int and returning int. Use cdecl (https://cdecl.org) to translate complex declarations. The typedef trick: typedef int (*IntArrayPtr)[10]; then IntArrayPtr p; reads cleanly. Use typedefs aggressively for function pointer types and nested array types.
How does Java references differ from C pointers?
Java references behave like restricted pointers. Object o = new Object(); o is a reference to a heap object. Object o2 = o; copies the reference, both pointing to the same object. No address-of (&), no dereference (*), no pointer arithmetic, no manual free. Garbage collection reclaims unreferenced objects automatically. The null literal is the equivalent of NULL; dereferencing null throws NullPointerException instead of segfaulting. The == operator compares references; .equals compares values. Java references cannot be cast to integers, cannot point to elements within a struct, cannot do anything that requires arbitrary memory access. The tradeoff: simpler reasoning, no manual lifetime management, GC pauses instead of explicit free.
Can you debug a double-free or use-after-free with Valgrind?
Yes. valgrind --leak-check=full --show-leak-kinds=all ./prog produces output identifying invalid frees, use-after-free, and memory leaks. Example double-free output: Invalid free() / delete / delete[] / realloc() at 0x4848B3F: free, Address 0x4a91040 is 0 bytes inside a block of size 4 free'd at the previous free site, original allocated at malloc. Read the output backwards: the second free, the first free, the allocation. The path between the 3 sites is the bug. Standard tool for CS50 image manipulation psets, CS61C linked-list project, CMU 15-213 malloc lab. Our tutors include the Valgrind report and the 3-line fix in every C and C++ submission.
When should I use a pointer vs a reference in C++?
Use a reference (T&) when the parameter is required (cannot be null) and the function does not take ownership. Use a pointer (T*) when the parameter is optional (nullptr signals absence) or when the function takes ownership and will store or delete it. Use std::unique_ptr when the function takes exclusive ownership; std::shared_ptr when ownership is shared. Use const T& for read-only large parameters to avoid copying. Use T& for read-write parameters where reassignment is needed. References cannot be reseated to a different object after initialization; pointers can be.
Can you help with CS50, CS61C, or CMU 15-213 pointer assignments?
Yes. CS50 problem sets 4 (Memory) and 5 (Speller) cover image manipulation in C with manual malloc and free, plus a trie implementation. CS61C project 1 is a linked-list project in C with Valgrind grading for memory correctness. CMU 15-213 malloc lab implements a custom heap allocator (implicit free list, explicit free list, or segregated fits); the attacklab exploits buffer overflows via crafted pointer arithmetic. Our tutors deliver implementations with explicit pointer-rewiring ASCII diagrams for linked-list operations, Valgrind reports proving zero leaks, and gdb session logs for the buffer-overflow attacks.