Pointer: Address Model and Indirect Access
R — Reference
"Because an address can be used to find the required variable unit, it can be said that the address points to that variable unit. … Therefore, addresses are figuratively called 'pointers.' The idea is that through it you can find the memory unit whose address it is." — Chapter 8 §8.1 What is a Pointer
"First, accurately understand the meaning of a pointer. … Once it is clear that 'a pointer is an address,' it becomes much easier to understand, and many problems solve themselves." — Chapter 8 §8.9 Summary on Pointers (1)
"Distinguish between a pointer and a pointer variable. A pointer is an address, while a pointer variable is a variable used to store an address." — Chapter 8 §8.9 Summary on Pointers (3)
"Note that a value of NULL for p and not assigning a value to p are two different concepts. The former has a value (value 0) and does not point to any variable; the latter, although p has not been assigned, does not mean p has no value—its value is just an unpredictable value. … Therefore, you should assign a value to a pointer variable before referencing it." — Chapter 8 §8.9 Summary on Pointers (8)
I — Self-Explanation
The reason pointers are difficult is that they involve two layers at the same time: the address (a location number) and what type of data is stored at that location. Separate these two layers, and the whole chapter becomes clear.
Layer 1: Address. Every variable in a program is allocated a few bytes of memory during compilation. Each byte has a number—this number is the address—like a room number in a hotel. Through the room number you can find the room, so "the address points to that unit," and the address is figuratively called a "pointer." This is the most core statement of the entire book: a pointer is an address.
There are two ways to access a variable: direct access means accessing by variable name (the compiler internally has a "variable name → address" mapping, so by name it finds the address); indirect access means first storing the address in another variable, then finding the target through it—the "variable that specifically stores addresses" is the pointer variable.
Layer 2: Type. Having just the address is not enough. Suppose you are told "take data from unit 2000"; you still do not know whether to take 1 byte (character), 2 bytes (short integer), or 4 bytes (integer), nor whether to interpret it as two's complement or floating-point format. Therefore, C addresses are typed addresses: they are not just a location but also include "what type of data is pointed to." That is why when defining a pointer variable you must specify the base type (the int in int *p): it determines how many bytes *p reads, how to interpret them, and whether p+1 moves forward by 1 byte or 4 bytes.
From this, two key points about dereferencing *p follow: *p represents "the variable that p points to," which can be both read and written (*p = 3 stores 3 into the unit p points to, not into p itself). The value of p itself is an address; changing p only changes "who it points to."
The three most easily confused concepts must be strictly separated:
- Pointer = address (a value, e.g., 2000);
- Pointer variable = a variable that stores an address (its value is that address);
- Base type = what type of data it points to.
So you can say "the pointer of variable i is 2000," but you cannot say "the pointer variable of i is 2000"—the pointer variable is p itself, not 2000.
There is only one iron rule for safe use: make it point to a valid object before using it. If a pointer variable is defined but not assigned, its value is residual garbage in memory and may point anywhere; then *p = 3 randomly corrupts unrelated memory, and symptoms may appear much later. Three states must be distinguished: ① unassigned (garbage value, dangerous); ② value is NULL (explicitly points to no object, a legal and checkable state); ③ points to a valid object (the only state in which dereferencing is allowed). NULL is defined in stdio.h as 0, and the system guarantees unit 0 is not used for other purposes.
Pointers have three true uses (summarized at the end of §8.9): improve program efficiency; allow functions to modify caller variables and return multiple results; implement dynamic storage allocation (linked lists, etc.). At the same time, the original book warns: pointers are "too flexible," errors are often hidden and can corrupt the entire program, so use them with great caution.
A1 — Book Examples
- Example 8.2 Swapping the targets of two pointers (original book shows the result): Input a=5, b=9, after
if(a<b){p=p1;p1=p2;p2=p;}the values of a and b are not swapped (still 5 and 9), but the targets ofp1andp2are swapped, so*p1and*p2output 9 and 5. The original book says: "The values of a and b are not swapped; they retain their original values, but the values of p1 and p2 have changed." This example precisely distinguishes "changing the pointer's target" from "changing the target variable's value." - Example 8.3 swap: pass pointers to swap target values (original book gives the result): The formal parameters are
int *p1, int *p2, and inside the functiontemp = *p1; *p1 = *p2; *p2 = temp;changes*p1and*p2, so a and b really are swapped. For the same "swap," Example 8.2 changes the targets, Example 8.3 changes values—this is the best contrast for understanding the difference betweenpand*p. - §8.1 Difference between address and content (emphasized in the original book): Suppose the int variable i is allocated at 2000
2003, j at 20042007, k at 2008~2011; thenk = i + jmeans "take i starting from 2000, take j starting from 2004, add them, send the result starting from 2008." If using scanf,scanf("%d", &i)sends the value to the unit starting at 2000. This passage thoroughly explains the entire process of "variable name → address → fetch data by type."
A2 — When to Trigger
The user may be in these situations:
- Stuck halfway through learning pointers, unable to explain what
*p,&a, andpeach are; - Wanting to modify a caller's variable in a function, told "you must pass a pointer" but does not understand why;
- Program crashes randomly, or some unrelated variable inexplicably changes value;
- Cannot distinguish the terms "pointer" and "pointer variable";
- Wondering whether the
*inint *pand the*in*pare the same thing; - Arrays, strings, linked lists are full of pointers and cannot see what is happening underneath;
- Used a pointer without initialization and got a hard-to-reproduce fault.
Language signals: "what is a pointer really", "how to understand pointer is address", "difference between *p and &a", "what is the difference between pointer variable and pointer", "why must I specify the base type", "why does p+1 add 4 not 1", "wild pointer / null pointer", "segmentation fault", "why passing a pointer to a function can modify the actual argument".
Distinction from adjacent capabilities: This capability manages "address model + pointer semantics + safety rules." If the problem is "how to traverse an array with a pointer" → first use array-and-string then return to this capability; if it's "how to build a linked list" → use the "Struct and Linked List" capability card within this package; if it's "how to write function parameters" → use function-and-recursion (parameter passing mechanism section complements this capability).
E — Execution Steps
Input contract: Requires ① the user's current point of confusion (conceptual unclearness / code behavior not as expected / crash); ② the relevant code snippet; ③ if involving a crash, reproduction conditions and error message. If only a vague description like "I can't use pointers well," first ask the user to give concrete code or a concrete problem; otherwise only concepts can be taught, not pinpointed.
Output contract: For conceptual scenarios, deliver "three-layer decomposition (address / pointer variable / base type) + one runnable comparative example"; for debugging scenarios, deliver "problem classification (wild pointer / type mismatch / integer used as address / confusion between pointer and pointer variable) → cause → fix."
Steps:
- Separate the two layers: First confirm whether the user is stuck on "unclear about the address layer" or "unclear about the type layer." Completion criteria: can articulate which specific sentence the user does not understand.
- Build the address model: Use metaphors like "room numbers" or "drawer key in another drawer" to explain direct vs. indirect access. Completion criteria: the user can independently say what
&ais and whatp = &adoes. - Distinguish the three concepts: Clarify "pointer (address, a value) / pointer variable (variable storing an address) / base type (what it points to)." Completion criteria: the user can correctly answer the three questions: "What is the value of p?", "What is *p?", "What is the type of p?"
- Explain the dual meaning of
*(stopping point): In a definition statementint *p;, the*is only a marker "this is a pointer variable"; in the expression*pit is "dereference." If the user confuses the two → go back to step 3 and reteach, do not proceed. - Check base type matching:
int *pcan only receive the address of anintvariable; assigning the address of afloattoint*will interpret the bytes under the wrong format. Completion criteria: the types on both sides of the assignment are consistent. - Safety check (mandatory): Verify that every dereferenced pointer ① has been assigned to point to a valid object, or ② is explicitly set to
NULLand checked for null. Completion criteria: no path where "a pointer is used with*pafter definition without initialization" is found. - Provide a runnable comparison: Use the "swap targets vs swap values" pair (Example 8.2 / Example 8.3) to demonstrate the difference between
pand*p. Completion criteria: the user can predict the respective outputs of the two code snippets.
B — Boundaries
- Not applicable: If the user only wants "traverse an array with a pointer" without caring about the theory, just give the code; no need to force the entire address model.
- Scope of dynamic memory: This card A2 mentions malloc/free, but depth has clear boundaries:
- Covered:
mallocallocation,freedeallocation, forgetting to free leads to memory leaks (program consumes more memory over time), the OS reclaims the entire process memory on program exit (so a small program's leak does not affect next run, but a long-running service can exhaust memory). - Not covered: Memory ownership models (who is responsible for freeing), dangling pointers (using the same pointer after free), double free, optimization issues from pointer aliasing,
restrict, strict aliasing rules, race conditions from multi-threaded shared pointers. - If the user asks "Can I still use this pointer after free?" → this card can only answer "No, the behavior is undefined; you must set it to NULL," but deeper ownership design must be explained as beyond the scope of this book.
- Covered:
- Author blind spot (Stage 0 critique): This book's explanation of pointers uses "address = room number" as the core metaphor—this is extremely effective for understanding
&/*, but it does not cover memory ownership, lifetime, dangling pointers, or optimization issues from pointer aliasing. If the user encounters problems like using a pointer after free or multi-threaded shared pointers in real engineering, this card can only give a basic judgment and must state that it is beyond scope. - Easily confused: ①
*has different meanings in definition statements vs. expressions; ②&means both "take address" and "bitwise AND"—distinguished by context; ③NULL(has a value, 0) ≠ unassigned (no value, garbage value); ④ An array name is the address of the first element (a pointer), but it is not a pointer variable—it cannot be assigned to (a = pis illegal). - Failure mode: Treating pointers as an "advanced technique" to show off, writing expressions like
*(*(p+i)+j)that others cannot understand. The original book's attitude is clear: at the beginner stage, "do not prematurely use details of C that easily cause errors"—priority should be writing clear, readable code. - Safety red line: Never suggest that the user perform a dereference or write operation without first confirming the pointer is valid; when involving system-sensitive addresses (like unit 0, hardware addresses), only explain the principle, do not provide runnable write-operation examples.