Learn C pointers through memory visualization. Addresses and dereferencing explained with diagrams.
&x gets address, *p dereferencesint *p = &x;& (take the address) with * (dereference). In a declaration, * marks "pointer type." In an expression, *p means "what p points to." Nail that distinction.
int takes 4 bytes, the next variable lands 4 addresses later. Memory is allocated contiguously, with sizing determined by each variable's type.
int is 4 bytes. Type sizes are implementation-defined, so check with sizeof(int) on your own system. Real addresses also vary between OSes and across runs, and variables aren't always laid out this neatly.
& operator to get an address: writing &n gives you the address of the variable n (something like 0x1000). You can even print it with printf("%p\n", &n);.
n in the box.type *name;, where the type is the type of the thing being pointed to. So int *p means "p is a pointer to an int variable."& you've been using in scanf.n and pointer p relate.p will alternate between pointing to n and pointing to m.p = &n; and p = &m; redirect the arrow to a different target.*p = value; rewrites whatever sits at the other end of the arrow.p itself holds an address (0x1000 or 0x1004) β you need * to reach the actual value.& in scanfscanf("%d", &a); all along. That & is the very same operator β it's passing an address.& needed here?scanf needs to know where to write the input value, so you pass the address of the variable.scanf uses a pointer to write the input value to that address.
& and write scanf("%d", a);, scanf will treat a's (indeterminate) contents as an address and write to some random location in memory β a reliable way to crash your program.
void swap(int a, int b){ ... } won't affect the caller's variables β only the local copies inside the function get swapped.
int arr[5] = {10, 20, 30, 40, 50}; int *p = arr; // points to arr[0] printf("%d\n", *p); // 10 (arr[0]) printf("%d\n", *(p + 1)); // 20 (arr[1]) printf("%d\n", *(p + 3)); // 40 (arr[3]) p += 2; // p now points to arr[2] printf("%d\n", *p); // 30
p + 1 means "the next element," not "the next byte."int pointer (4 bytes), p + 1 advances the address by 4 bytes.| Array notation | Pointer notation | Meaning |
|---|---|---|
arr[0] | *arr | First element |
arr[i] | *(arr + i) | i-th element |
&arr[i] | arr + i | Address of i-th element |
arr[i] and *(arr+i) refer to the exact same element. Drag the slider and watch both notations light up the same cell.arr[2]*(arr + 2)arr[i] is just syntactic sugar for *(arr + i). Funnily enough, this means i[arr] (!) also compiles β it's *(i + arr) = *(arr + i). Don't actually write that, but it nicely demonstrates that array access is really just pointer arithmetic under the hood.int arr[5] = {10, 20, 30, 40, 50}; int *p1 = &arr[1]; int *p2 = &arr[4]; printf("%ld\n", p2 - p1); // 3 (difference in number of elements)
const tells the compiler and your fellow developers that a variable won't be modified. It prevents bugs and makes your intent explicit.const int MAX = 100; printf("%d\n", MAX); // OK: reading is allowed MAX = 200; // β Compile error: cannot modify const
#define is that you get type checking.const is notoriously confusing. The meaning depends on which side of the asterisk the const sits.| Declaration | What can change | Meaning |
|---|---|---|
const int *por int const *p |
β
p can changeβ *p cannot change |
A pointer to a constant value. |
int * const p |
β p cannot changeβ *p can change |
A constant pointer β can't point elsewhere. |
const int * const p |
β p cannot changeβ *p cannot change |
Fully locked β nothing can change. |
int a = 10, b = 20; // β const int *p : pointed-to value is read-only const int *p1 = &a; p1 = &b; // β OK: pointer itself can change *p1 = 30; // β Error: pointed-to value is immutable // β‘ int * const p : pointer itself is read-only int * const p2 = &a; *p2 = 30; // β OK: can modify 'a' through p2 p2 = &b; // β Error: p2 can't be redirected // β’ const int * const p : everything locked const int * const p3 = &a; *p3 = 30; // β Error p3 = &b; // β Error
const int *p = "p is a pointer to a const int"int * const p = "p is a const pointer to int"const to make that intent clear.// Just reads the string β mark as const int my_strlen(const char *s) { int n = 0; while (*s) { s++; n++; } return n; } // Sums array elements, doesn't modify them β const int sum(const int arr[], int n) { int total = 0; for (int i = 0; i < n; i++) total += arr[i]; return total; }
const when the function doesn't modify them.strlen and strcmp follow this convention.char *s1 = "Hello"; // β οΈ Discouraged (some compilers warn) s1[0] = 'J'; // β Undefined behavior! const char *s2 = "Hello"; // β Correct way // s2[0] = 'J'; β caught at compile time char s3[] = "Hello"; // β Array: modification OK s3[0] = 'J'; // β "Jello"
"Hello") live in read-only memory. Always receive them through a const char *.const acts as a bug-prevention barrier. Your code still works without it, but using it catches mistakes at compile time and makes your intent crystal clear.if (p != NULL)p + 1*(&x)*p = 99; rewrites the value of n. That's what it means to change a value indirectly, through a pointer.A. Pointers let you do things that are simply impossible otherwise: 1. modify a caller's variable from inside a function by passing its address, 2. allocate memory dynamically, 3. build complex data structures like lists and trees, and 4. work with strings. Many C features don't work without them.
& (address-of) gives you the address β "where the variable lives." * (dereference) goes to that address and looks at "what's stored there." In a pointer declaration like int *p;, the * just means "p is a pointer to int."
A. It helps to distinguish three states:
β NULL pointer β a pointer intentionally set to NULL (the value 0), meaning "points to nothing." Dereferencing it is undefined behavior and usually crashes, which makes it a detectable sentinel.
β‘ Uninitialized pointer β declared but never assigned. Its value is indeterminate (it could be anywhere in memory), and dereferencing it is undefined behavior.
β’ Dangling pointer β a pointer that once referred to a valid object, but that object is no longer valid (freed, went out of scope, and so on). The bits may still look valid, but any access is undefined behavior.
Good defensive habits: initialize pointers at declaration (int *p = NULL;), set p = NULL; right after free(p), and never return the address of a local variable.
A. A pointer is just a variable too, so the pointer variable itself lives in memory β which means you can make "a pointer to a pointer": int **pp = &p;. Multi-level pointers look tricky, but you're just applying the same principle repeatedly. In practice, they're rarely needed.
Check your understanding of this lesson!
int *p;, what does p hold?int *p is a pointer to int, so it stores the memory address of an int variable.
*p dereferences the pointer. Since p points to x, *p is 10.
Dereferencing a NULL pointer causes a runtime error like a segmentation fault. Always check for NULL before you use a pointer.