Pointers in C
Pointers store memory addresses. They enable dynamic memory, efficient array passing, and direct hardware access — and cause most C bugs when misused.
Basics
int x = 42;
int *p = &x; /* p holds address of x */
printf("value: %d\n", *p);
*p = 100; /* modifies x */
printf("x: %d\n", x);
Pointer Arithmetic
int arr[] = {10, 20, 30, 40};
int *p = arr; /* points to arr[0] */
printf("%d\n", *(p + 1)); /* 20 */
printf("%d\n", p[2]); /* 30 — equivalent syntax */
/* Difference of pointers: number of elements between them */
int *end = arr + 4;
printf("%td\n", end - p); /* 4 */
Pointers and Functions
void scale(int *arr, size_t n, int factor) {
for (size_t i = 0; i < n; i++)
arr[i] *= factor;
}
int main(void) {
int data[] = {1, 2, 3};
scale(data, 3, 10); /* data becomes {10, 20, 30} */
}
Null and Void Pointers
int *p = NULL;
if (p != NULL)
printf("%d\n", *p);
void *generic = malloc(64);
int *ip = (int *)generic; /* cast before use */
free(generic);
const with Pointers
const int *p1; /* data read-only, pointer movable */
int *const p2 = &x; /* pointer fixed, data writable */
const int *const p3; /* both fixed */
/* const char * = pointer to const char (common for strings) */
void print(const char *s) { puts(s); }
Common Pitfalls
- Treating compiler warnings as optional rather than actionable feedback.
- Skipping error checks on library and system calls.
- Copy-pasting examples without adapting to your project’s conventions.
Best Practices
- Enable strict compiler warnings and fix them before merging.
- Write small, testable units with clear input/output contracts.
- Document non-obvious invariants and preconditions.
- Use version control and code review for every change.
Memory and Performance Notes
Pointer size matches address bus width (4 bytes on 32-bit, 8 on 64-bit). Dereferencing invalid pointers is undefined behavior.
Exercise
Implement void *memset_custom(void *s, int c, size_t n) that sets n bytes to value c without using string.h.
Hint: Cast void* to unsigned char* for byte-level access.
Summary
Apply these concepts in small programs before moving to larger projects. Combine with adjacent topics in the learning path for deeper mastery.
Real-World Application
These concepts appear in production codebases — from operating system kernels to embedded firmware. Study open-source projects that use this topic extensively to see idiomatic patterns at scale.
Debugging Checklist
- Reproduce the issue with the smallest possible input.
- Enable compiler warnings and sanitizers.
- Use a debugger to inspect state at the failure point.
- Verify assumptions about types, sizes, and return values.
- Compare working and broken code paths side by side.
- Write a regression test once the bug is fixed.
Further Reading
Consult the ISO C standard, Effective C by Robert C. Seacord, and your compiler documentation for platform-specific behavior.
Quick Reference
Review the code examples on this page before starting the exercise. Type them manually to build muscle memory.
Additional Examples
Consider how this topic applies in a larger project:
// Break the problem into smaller functions
// Test each function independently
// Integrate incrementally
Working through variations of the examples above builds deeper understanding than reading alone.
Interview and Review Questions
- Explain the core concept of this topic in your own words.
- What happens when this code runs with edge-case input (empty, null, zero, max value)?
- How would you debug a bug related to this topic in production?
- What are the performance implications of the approach shown here?
- How does this feature compare to the equivalent in another language you know?