Pointers and Memory Addresses in C
&, read and change values through pointers, and pass variables to functions by reference. Walk through arrays and strings with pointer arithmetic, use pointers to pointers for command-line arguments and for functions that move a caller's pointer, and call functions through function pointers, including comparison callbacks for the standard qsort.IntermediateLesson 4 of 8 in Programming in C Course
What you will learn
- Picture how a C program's variables occupy bytes in memory, store addresses in pointer variables, and use those pointers to read and change the variables they point to.
- Picture how variables are laid out in memoryDescribe memory as numbered bytes, find the size and address of variables and array elements, and explain how long each kind of variable exists.
- Declare pointers and take addresses with &Declare pointer variables of the right type, store a variable's address with the address-of operator, and print, redirect or reset a pointer safely.
- Read and change values through pointersUse the dereference operator to read and write the object a pointer points to, trace code with memory diagrams and avoid dereferencing invalid pointers.
- Picture how variables are laid out in memory
- Write functions that change their caller's variables through pointer parameters, protect read-only data with
constand return pointers that stay valid.- Pass variables by reference with pointer parametersExplain why C functions receive copies of their arguments, and use pointer parameters to change a caller's variables, swap values, return several results and read input with
scanf. - Protect data with const and return pointers safelyMark read-only data with
const, tell a pointer to a constant from a constant pointer, and return pointers that remain valid after the function ends.
- Pass variables by reference with pointer parameters
- Move pointers through arrays and strings, explain how array names relate to pointers, and keep every pointer inside the array it belongs to.
- Do arithmetic with pointersAdd integers to pointers, subtract and compare pointers into the same array, and know which results are valid.
- Explain how arrays and pointers are relatedUse the conversion of an array name to a pointer, the identity between
a[i]and*(a + i), and the real differences between an array and a pointer. - Walk through arrays and strings with pointersWrite loops that traverse arrays and null-terminated strings with pointers, use idioms such as
*p++correctly and stay within bounds.
- Do arithmetic with pointers
- Follow several levels of indirection, let functions update a caller's pointer, and use arrays of pointers such as the command-line arguments.
- Use pointers to pointersDeclare and dereference a pointer to a pointer, trace it in a memory diagram and use it to let a function change a caller's pointer.
- Use arrays of pointers and command-line argumentsBuild arrays of string pointers, process
argcandargvas pointers, and tell an array of pointers from a two-dimensional array.
- Use pointers to pointers
- Store and call functions through pointers, pass functions to other functions, and sort data of any type with the standard
qsortand a comparison callback.- Declare, assign and call function pointersWrite and read function pointer declarations, call functions through them and build tables of functions that choose behavior while the program runs.
- Write callbacks and sort with qsortPass functions as arguments, use
voidpointers to handle data of any type, and write correct comparison functions forqsort.
- Declare, assign and call function pointers
Pointers in C study guide
1Memory, addresses, and pointed-to values
A variable has a value, occupies storage, and has a lifetime. A pointer lets you reach that variable through its address, so reading code now means tracking both the address held in a pointer and the value found there. Start with byte layout, then follow the effects of taking an address, redirecting a pointer, and writing through it. Small memory diagrams make these different operations visible before they appear inside functions and loops.
1.1 Bytes, sizes, and lifetimes
Memory can be pictured as a sequence of numbered bytes. Each number is an address. An object such as an int occupies consecutive bytes, and its address identifies its first byte. The name you use in source code lets the compiler find that object; the language does not require a name label to be stored beside its value.
Measure storage with sizeof
sizeof reports bytes and produces a size_t value. Use %zu to print it:
int scores[4] = {12, 18, 15, 21};
printf("%zu %zu\n", sizeof scores, sizeof scores[0]);
With 4-byte ints, this prints 16 4. The first result measures all four elements; the second measures one. sizeof(char) is always 1. A common 64-bit desktop layout uses 4-byte ints, 8-byte doubles, and 8-byte object pointers. Other targets can differ. Even among 64-bit systems, long commonly occupies 8 bytes on Linux and macOS but 4 on Windows.
Storage size and text length answer different questions. For char word[20] = "hi";, sizeof word is 20, while strlen(word) is 2. The array still contains 20 elements even though its string uses only two letters and a terminator. Except for variable-length arrays, these object sizes are fixed at compile time.
Follow an array's addresses
Hexadecimal addresses use digits 0 through 9, then a through f. The prefix 0x marks base 16. For an illustrative array beginning at 0x1000, four 4-byte elements start at:
| Element | First byte | Last byte |
|---|---|---|
scores[0] | 0x1000 | 0x1003 |
scores[1] | 0x1004 | 0x1007 |
scores[2] | 0x1008 | 0x100B |
scores[3] | 0x100C | 0x100F |
0x100C is decimal 4108, twelve bytes beyond 0x1000. Array elements have no gaps. Separately declared variables have no guaranteed order or spacing; real printed addresses can also change between runs. A compiler can keep a value in a processor register when no addressable storage is needed.
Alignment and byte order
Alignment restricts where a type's objects can begin. A common layout places 4-byte ints at multiples of 4 and 8-byte doubles at multiples of 8. The compiler supplies suitable alignment for declared objects.
Endianness is the order of bytes within a multibyte value. For a 4-byte unsigned int containing 0x12345678, little-endian storage has bytes 78 56 34 12 from low to high addresses; big-endian storage has 12 34 56 78. An unsigned char * can inspect an object's representation one byte at a time. Byte order does not change the array's index order.
Keep the object alive
An ordinary local variable has automatic storage duration: its lifetime lasts through its block's execution. Typical implementations keep such objects in a call's stack frame and reuse that storage afterward. Globals, static variables, and string literals last throughout the program. Saving an address does not extend the lifetime of its object.
Key Takeaways
sizeofmeasures bytes;strlencounts characters before a string terminator.- Array elements occupy consecutive storage in index order.
- Separate variables have no guaranteed relative addresses.
- Alignment controls suitable starting addresses; endianness controls byte order inside a value.
- A saved address remains usable only while its object exists.
1.2 Store and display an address
A pointer stores an address and has a type describing the object it can access. Declaring an int * gives you storage for a pointer; it does not create an int for that pointer to use. Give the pointer a known value when you declare it.
Take an object's address
int count = 42;
int *p = &count;
The unary address-of operator & obtains the address of count. If count begins at the illustrative address 0x1000, p holds 0x1000, while count holds 42. The pointer is also an object: &p is the address of the separate storage holding that pointer.
You can take the address of an array element, such as &scores[2]. A numeric constant or calculation result is not a stored object you can address this way: &42 and &(count + 1) are invalid. The binary expression a & b uses the same symbol for bitwise AND; the number of operands tells you which operation is present.
Read each declarator
| Declaration | Meaning |
|---|---|
int *p, q; | One pointer and one ordinary int |
int *p, *q; | Two pointers to int |
double *r; | One pointer to double |
Whitespace changes none of these types. int* p and int *p are equivalent. Declaring one pointer per line makes its star harder to miss. On common 64-bit desktops an object pointer occupies 8 bytes, but its own size does not tell you the size of the object it points to.
The target type matters. For double price = 9.5;, use double *r = &price;. Initializing an int * with &price produces an incompatible-pointer diagnostic. A cast that hides the diagnostic does not make an int access to that double valid: the type controls how the stored bytes are accessed.
Redirect a pointer
int a = 5, b = 8;
int *p = &a;
p = &b;
The final assignment replaces the address in p. It does not move either integer or copy one integer's value to the other. Now p points to b; a is still 5 and b is still 8. Several pointer variables can also store the same address.
Use a known empty state
An uninitialized local pointer has an indeterminate value, sometimes called a wild pointer. Do not inspect or dereference it. Initialize it with a valid object's address or with NULL, available from headers including <stddef.h> and <stdio.h>:
int *pending = NULL;
if (pending == NULL) {
pending = &count;
}
A null pointer designates no object. !pending is another null test. These tests make sense only for initialized pointers; they do not validate an arbitrary address or a pointer whose object has died.
Print an object pointer
printf("%p\n", (void *)p);
%p expects void *, so supply the cast when using a typed object pointer. Its printed representation is chosen by the implementation and is commonly hexadecimal. %d and %x expect integer arguments of particular types and cannot be used as substitutes for %p.
Key Takeaways
- Initialize a pointer with a valid address or
NULL. &countis an address;countis the stored integer.- Each pointer name needs its own
*in a declaration. - Pointer types must suit the objects accessed through them.
p = &bchanges the pointer, leaving the integers alone.- Print an object pointer with
%pand avoid *argument.
1.3 Follow a pointer to read or write
The unary dereference operator * follows a pointer to its object. If p points to x, *p designates x itself. Reading *p reads that object; assigning to *p changes it.
Trace a write through an address
int x = 10;
int *p = &x;
*p = *p + 5;
int y = *p * 2;
printf("%d %d %d\n", x, *p, y);
First p stores the address of x. On the assignment's right side, *p reads 10. Adding 5 gives 15, and the left-side *p stores 15 in x. The next line multiplies the updated value by 2, so y becomes 30. Output is 15 15 30.
Context distinguishes the stars: int *p declares a pointer, unary *p accesses an object, and *p * 2 also contains a binary multiplication. For a valid object x, *&x designates x; for a pointer to a valid object, &*p gives back the pointer value.
Separate addresses from contents
Start each trace with two rows of information: what address each pointer holds and what value each integer holds. Suppose p points to a, containing 3, and q points to b, containing 8.
| Statement considered from that starting state | Effect |
|---|---|
*p = *q; | Copy 8 into a; pointers keep their targets |
p = q; | Make p point to b; integers keep their values |
A memory diagram uses a box for each object. Label a box with its name and address, put its current value inside, and draw an arrow from a pointer to its target. An address assignment moves an arrow; a pointed-to assignment changes a target box's contents.
After p = q, the pointers are aliases for b. If you then execute *p = 20;, reading *q or b gives 20. Copying an address creates another route to the same object, not another integer.
Check that a dereference is valid
A pointer used for access must designate a live object of a suitable type, and writing also requires a modifiable object. Null pointers designate no object. Uninitialized pointers have no usable known target. A pointer becomes dangling when its target's lifetime ends.
Access through an invalid pointer is undefined behavior: C imposes no requirements on the outcome. The program may stop with a segmentation fault, corrupt data, or appear to run normally. An apparently successful run cannot establish that the access was valid.
For a pointer that can legitimately be null, test before accessing it:
if (p != NULL) {
*p = 0;
}
Assigning NULL to a pointer you no longer need can prevent later accidental use of that variable. It does not reset other aliases. Keep track of the object's lifetime separately.
Put parentheses around the value you mean
Postfix ++ binds more tightly than unary *. Thus *p++ means *(p++): it uses the old pointer to reach a value and advances the pointer. (*p)++ increments the pointed-to value and leaves the pointer in place. *p + 1 reads the value and adds 1 without storing the result. These are three different actions.
Key Takeaways
*pdesignates the object reached throughp.- Copying an address creates aliases; copying
*pcopies a value. - A dereference requires a live object and a suitable access type.
- Undefined behavior has no promised result, including no promised crash.
*p++advances the pointer;(*p)++increments its target.
2Let functions work on caller-owned data
A function receives a copy of each argument, including a pointer argument. That copied address can still reach an object owned by the caller, which makes swaps, input functions, and multiple outputs possible. The function interface should also show whether it changes the data. Follow the address into the call, distinguish local changes from writes through it, and check which objects will still exist when a returned pointer is used.
2.1 Change caller variables through parameters
C passes every argument by value. A parameter is a local variable initialized from the argument. Passing an address does not change that rule: the function receives a copy of the address, and that copy can reach the caller's object.
Compare two reset functions
void reset_copy(int n) {
n = 0;
}
void reset(int *n) {
*n = 0;
}
For int score = 12;, reset_copy(score) changes only its local n. score stays 12. The call reset(&score) copies score's address into pointer parameter n; assigning to *n then changes score to 0. This technique is often called pass by reference, although the language's actual argument passing remains by value.
The address in the call and the dereference in the body do different jobs. &score tells the function where the object is. *n = 0 tells it what to store there. Calling reset(score) supplies an integer where a pointer is required and triggers a compiler diagnostic.
Work through a swap
void swap(int *a, int *b) {
int t = *a;
*a = *b;
*b = t;
}
/* Inside a calling function: */
int x = 3, y = 8;
swap(&x, &y);
apoints toxandbpoints toy.tsaves the originalx, which is 3.*a = *bwrites 8 intox.*b = twrites the saved 3 intoy.
Both caller variables change. A version taking plain int a, int b exchanges only copies. A pointer version that does int *t = a; a = b; b = t; also fails to exchange the integers: it redirects only the local pointer parameters.
Deliver more than one result
A return statement supplies one value, but pointer parameters can carry additional outputs:
void divide(int total, int size, int *q, int *r) {
*q = total / size;
*r = total % size;
}
/* size must be positive for these example inputs. */
int q, r;
divide(17, 5, &q, &r);
Integer division gives q equal to 3 and r equal to 2. The caller supplies two distinct writable objects. Another useful interface returns a success status and writes an output only on success; callers check that status before using the output.
Recognize the same pattern in library calls
scanf("%d", &age) needs age's address because it stores a parsed integer there. Passing age supplies a value instead of the required int *, producing undefined behavior. Check that scanf returns 1 before using the newly read value.
For text, char name[20]; scanf("%19s", name); passes a pointer to the array's first character. The width allows at most 19 input characters, leaving room for '\0'. The array expression already provides the required character pointer.
An array parameter likewise receives an address, not a copied block of elements. In void zero(int *a, size_t n), assignments to a[i] affect the caller's array. Passing the address copies only a pointer, which also lets read-only functions inspect large arrays without copying every element.
Key Takeaways
- C copies every argument, including addresses.
- A write through a pointer parameter can change the caller’s object.
- A working integer swap exchanges
*aand*busing a saved value. - Output parameters need valid writable objects supplied by the caller.
scanfreceives addresses and reports how many conversions succeeded.- Array parameters share access to caller-owned elements.
2.2 Const access and safe return values
A pointer interface should make two things clear: whether the function may write through it, and how long the pointed-to storage remains valid. const answers the first question. The object's lifetime answers the second.
Locate the const restriction
| Declaration | May redirect p? | May assign through *p? |
|---|---|---|
const int *p = &x; | Yes | No |
int *const p = &x; | No | Yes |
const int *const p = &x; | No | No |
A pointer to const limits this access path. If x was defined as ordinary int x = 5;, x = 6; is still allowed even while a const int * points to it. If the object itself was defined const, attempting to modify it through any pointer is undefined behavior.
A const pointer keeps one address. It must be initialized, and the address cannot later be replaced. The object at that address can still change when its type permits it. Read outward from the name: int *const p makes p constant; const int *p protects the int reached through p.
Use a read-only parameter such as const char *s when a function only examines a string. An ordinary char * can be passed to that parameter without a cast. The function can advance its local s, but assigning through *s is rejected. Converting back to a writable type requires more than removing a compiler warning: the underlying object must actually be modifiable.
Return a position in caller-owned storage
int *find(int *a, size_t n, int target) {
for (size_t i = 0; i < n; i++) {
if (a[i] == target) return &a[i];
}
return NULL;
}
For int v[] = {4, 9, 2, 9};, find(v, 4, 9) returns &v[1]. The function ends, but v belongs to the caller and remains alive there. After checking the result against NULL, the caller can compute found - v to obtain index 1 or assign *found = 0 to update the element. A search for 7 returns NULL, which must not be dereferenced. strchr and strstr use the same null-for-not-found convention.
Reject addresses of expired locals
int *bad(void) {
int result = 42;
return &result;
}
result has automatic storage duration. Its lifetime ends when the call returns, leaving the caller with a dangling pointer. The compiler may warn, and any later access through that pointer has undefined behavior. The old bytes being visible on one run does not keep the object alive.
The same problem can occur at an inner block's closing brace. An outer pointer cannot extend the lifetime of an inner variable whose address it saved.
Choose storage that lasts long enough
Return an int value when the caller needs only an integer result. For several results, let the caller supply storage to fill. A returned pointer can also refer to the caller's array, a string literal exposed through const char *, or a static object.
A static local is initialized once and lasts throughout the program, but all calls share it. If a later call stores a new value there, every earlier returned pointer sees that change. Static storage solves the lifetime problem without giving each call its own independent result object.
Key Takeaways
const int *restricts writes through that pointer, whileint *constfixes the address.- An object defined
constcannot legally be modified. - Pointers into the caller’s live array can remain valid after a function returns.
- Check a possibly null search result before access or subtraction.
- Returning an automatic local’s address leaves a dangling pointer.
- A static result object is shared by all calls.
3Move through arrays and strings
Pointer arithmetic connects an element’s address to its position in an array. The pointed-to type supplies the step size, while the array supplies the legal range. Use that model to read subscripts, pass a slice to a function, and build loops that stop before an invalid access. Strings add a second stopping rule: their terminating null character marks the end of the text, even when the storage contains more bytes.
3.1 Element steps and array bounds
Adding an integer to a pointer moves by elements of the pointed-to type. If p points to array element i, p + n points to element i + n, provided the resulting position is within the array or one past it.
Scale the address once
At an illustrative address 0x1000, adding 3 gives these results:
| Pointer type | Size of one element | Address after p + 3 |
|---|---|---|
int * | 4 bytes | 0x100C |
double * | 8 bytes | 0x1018 |
char * | 1 byte | 0x1003 |
The compiler performs the scaling. Writing p + 3 * sizeof *p scales twice and generally goes to the wrong element. Use unsigned char * when the intended step really is one byte of an object's representation.
For int a[6] = {10, 20, 30, 40, 50, 60}; and int *p = &a[1];, *(p + 2) reads a[3], which is 40. The assignment p += 3; moves the pointer itself to a[4], containing 50. From there *(p - 1) reads 40. p++, p--, p += n, and p -= n all use element steps.
Subtract positions to count elements
&a[5] - &a[1] is 4, and reversing the operands gives -4. The result already counts elements; do not divide it by sizeof(int). Its signed type is ptrdiff_t, declared in <stddef.h>, and %td prints it.
Subtract only pointers into the same array, including its one-past position, with a result representable in ptrdiff_t. This operation describes positions within that array. Subtracting addresses of unrelated variables is undefined behavior even if the printed numbers happen to be close.
Use the one-past position as a boundary
For six elements, a + 6 is a valid one-past-the-end pointer. It can serve as a bound and participate in valid same-array subtraction, but it cannot be dereferenced. a + 7 and a - 1 already have undefined behavior when formed; avoiding a later dereference does not repair them.
for (int *p = a; p < a + 6; p++) {
printf("%d\n", *p);
}
The body runs for indices 0 through 5. After the final increment, p equals a + 6, and the next test stops the loop. Using <= would allow the body to access that non-element.
For arithmetic rules, a single object acts like a one-element array. You may form &x + 1; you may not use it to read a supposed next variable.
Compare only meaningful positions
Ordering operators such as < compare positions within one array. Do not order pointers to unrelated objects. Equality and inequality can compare compatible pointer types and can test against a null pointer.
You cannot add two pointers, multiply pointers, or do standard C arithmetic on a void * or a function pointer. To find the midpoint of a same-array range [p, q), use p + (q - p) / 2: subtract positions to obtain a count, divide that count, then advance from p.
Key Takeaways
- Integer addition moves a pointer by elements of its target type.
- A same-array pointer difference already counts elements and has type
ptrdiff_t. - One-past pointers may mark a boundary but cannot be dereferenced.
- Forming a pointer before the array or beyond one-past is invalid.
- Use same-array positions for ordering comparisons.
- Subtract positions before computing a midpoint.
3.2 Array expressions and pointer parameters
An array is a block of elements. A pointer is a separate object holding an address. Their close relationship comes from an automatic conversion that happens in most expressions, not from the two objects being the same kind of thing.
Convert an array expression
int a[5] = {3, 1, 4, 1, 5};
int *p = a;
Here a converts to a pointer to a[0]. This array-to-pointer conversion, often called decay, makes the initializer equivalent to &a[0]. Both *p and a[0] read 3.
C defines subscripting through arithmetic: a[i] means *(a + i). Therefore p[3] works too and reads 1. &a[i] and a + i identify the same element. Even 3[a] has the same defined meaning as a[3], although the usual spelling is much clearer.
Know when the whole array stays an array
The conversion does not occur for operands of sizeof or unary &. It also does not occur for C23 typeof and typeof_unqual, or when a string literal initializes a character array.
Expression, with 4-byte ints and an 8-byte pointer | Result |
|---|---|
sizeof a | 20 bytes for all five elements |
sizeof p | 8 bytes for the pointer object |
sizeof a / sizeof a[0] | 5 elements |
You cannot assign a new address to an array or increment its name: a = p; and a++; are invalid. You can redirect p, such as with p = a + 2;. Copy array contents with an element loop or an appropriate memcpy call.
Follow the adjusted parameter type
int sum(const int *values, size_t n) {
int total = 0;
for (size_t i = 0; i < n; i++) total += values[i];
return total;
}
The caller supplies both an address and a count. For the array above, sum(a + 2, 3) visits original indices 2, 3, and 4, adding 4, 1, and 5 to return 10. Inside the function, index 0 is relative to the supplied starting pointer.
In a function parameter list, int a[], int a[10], and int *a all declare a pointer parameter. The plain [10] spelling supplies no checked bound and transfers no length. Inside such a function, sizeof a measures a pointer. The expression sizeof a / sizeof a[0] would give 2 on an 8-byte-pointer, 4-byte-int system, regardless of the caller's real array length.
Distinguish an element pointer from an array pointer
a converts to int *, while &a has type int (*)[5], a pointer to the whole five-element array. Both identify the beginning of the same storage, but arithmetic follows their different types. a + 1 advances one int, or 4 bytes in this layout. &a + 1 advances one entire array, or 20 bytes, to its one-past position.
A printed starting address cannot reveal that type difference. Read the declaration and keep the pointed-to type beside the pointer when tracing an expression.
Key Takeaways
- Most array expressions convert to a pointer to the first element.
a[i]is defined as*(a + i).sizeof ameasures the whole array only whereareally is an array.- An array parameter is adjusted to a pointer; pass its usable length separately.
&apoints to an entire array, so its arithmetic has a larger step.
3.3 Pointer loops over numbers and text
A pointer loop needs a current position and a stopping rule. A counted array uses an end pointer; a string uses its terminating '\0'. Choose the form that makes the visited elements easiest to see.
Traverse a counted range
int a[] = {3, 1, 4, 1, 5};
int total = 0;
for (const int *p = a; p < a + 5; p++) {
total += *p;
}
The running totals are 3, 4, 8, 9, and 14. An indexed loop adding a[i] for indices below 5 gives the same result. Modern optimizing compilers often generate similar code for these forms; pointer syntax alone promises no speed advantage.
Read before or after moving
With p at the 3 in {3, 1, 4}, int v = *p++; sets v to 3 and leaves p at the 1. Postfix increment supplies the old pointer value to * and updates the pointer. *++p first moves to the next element and then reads it. (*p)++ changes the current element and does not move the pointer.
Stop on a string terminator
size_t my_strlen(const char *s) {
const char *p = s;
while (*p != '\0') p++;
return (size_t)(p - s);
}
For "pointer", p stops on the terminator, seven positions from the start. The result excludes that terminator. A character count uses the same walk: scanning "mississippi" and incrementing a counter whenever *p == 's' counts four matches among eleven letters.
A pointer into the middle of a string gives access to its suffix. With const char *msg = "hello world";, puts(msg + 6) prints "world". The earlier characters stay where they were.
Copy the terminator too
char destination[4];
const char *src = "cat";
char *dst = destination;
while ((*dst++ = *src++) != '\0') {
}
Each iteration assigns one character, tests the assigned value, and advances both pointers. The final iteration copies '\0', so the destination is a complete string. Both pointers then stand just beyond their terminators. The storage needs the text length plus one byte; omitting that extra byte causes a buffer overflow. This copy pattern also requires nonoverlapping source and destination ranges.
Reverse from both ends
For a writable string, first obtain its length. If the length is zero, stop before forming a last-character pointer. Otherwise set left = s and right = s + length - 1. While left < right, exchange the characters, increment left, and decrement right. For "stressed", four swaps produce "desserts"; the terminator remains at the original end.
The empty-string check matters because subtracting 1 from its starting pointer would form a pointer before the array.
Choose writable character storage
char word[] = "hello"; creates a modifiable six-byte array, including the terminator. const char *word = "hello"; points to a string literal with static storage duration. Modifying a string literal is undefined behavior, even if a declaration uses char * without const. Use const char * to make accidental writes a compiler error.
An array loop must stop before its one-past position, and a string walk must actually encounter a terminator within accessible storage. A pointer carries neither a length nor a guarantee that the bytes form a string.
Key Takeaways
- A counted range ends at a one-past pointer; a string ends at its null character.
*p++uses the old position and advances the pointer.- String copying includes the terminator and needs length plus one bytes.
- Check an empty string before forming its last-character pointer.
- Character arrays initialized from text are writable; string literals are not.
- A pointer alone carries no length or termination guarantee.
4Follow and update another pointer
A pointer can itself be the object reached through an address. Following two levels lets a function replace the caller’s pointer and lets a program walk an array of string addresses. Keep each level’s type visible: a pointer to an integer, a pointer to that pointer, and a pointer to a whole row represent different layouts. Trace which object each dereference reaches before deciding what an assignment will change.
4.1 Two levels of indirection
A pointer is a stored object, so it has an address that another pointer can hold. A pointer to a pointer adds one more step between the expression and the final data.
Follow the arrows one at a time
int x = 7;
int *p = &x;
int **pp = &p;
Use illustrative addresses: x at 0x2000 holds 7; p at 0x3000 holds 0x2000; pp at 0x4000 holds 0x3000. Evaluating *pp reaches p, whose value has type int *. Evaluating **pp follows that pointer onward to x, an int.
| Expression | Type | Object or value reached |
|---|---|---|
pp | int ** | Address of p |
*pp | int * | The pointer stored in p |
**pp | int | The integer stored in x |
&p has one more pointer level than p; each dereference removes one level. Initializing int **pp with &x is a mismatch because &x has type int *.
Choose the assignment level
With another live integer y, **pp = 9; changes x. The assignment *pp = &y; changes the pointer p, so future uses of **pp reach y. It leaves x at its current value. If q is another int *, pp = &q; changes only the outer pointer, making its first arrow reach q instead of p.
Do not count stars without also checking the object at each level. Before evaluating **pp, both the route to the pointer object and that object's route to the integer must be valid.
Move a caller's string pointer
void skip_spaces(const char **s) {
while (**s == ' ') {
(*s)++;
}
}
/* In the caller: */
const char *text = " hi";
skip_spaces(&text);
puts(text);
The caller passes the address of its pointer variable. *s therefore designates text; **s reads the current character. Each (*s)++ advances text by one character. After three iterations it points to 'h', and puts prints "hi". The function requires a valid pointer to a pointer into a null-terminated string.
A version taking const char *s and incrementing s changes only a local copy. The parentheses in (*s)++ are also essential: *s++ increments the outer local pointer instead of the caller's character pointer. In const char **s, the characters are read-only through this access path, but the caller's pointer may move.
Read a library output pointer
strtol from <stdlib.h> uses an output parameter of type char **:
char *end;
long number = strtol("42abc", &end, 10);
It returns 42 and stores a pointer to the first unused character in end, which now reaches "abc". If the end pointer equals the starting pointer, no digits were converted. A fully checked numeric parser also handles range errors; the pointer's job here is to report the stopping position.
Key Takeaways
int **ppcan hold the address of anint *object.- Each dereference follows one pointer level.
**ppchanges the final integer;*ppcan change the intermediate pointer.- Updating a caller’s pointer requires that pointer’s address.
strtoluseschar **to return the first unused character’s position.
4.2 String-pointer arrays and rectangular arrays
An array can store pointers as its elements. A two-dimensional array instead stores complete rows as its elements. Both support two subscripts, but their storage and converted pointer types differ.
Store addresses of separate strings
const char *days[] = {"Mon", "Tue", "Wed"};
days holds three character pointers. Each points to a separate string literal. days[1] is the pointer to "Tue", and days[1][0] reads 'T'. With 8-byte object pointers, sizeof days is 24, regardless of the strings' lengths. The strings themselves occupy storage elsewhere.
The lengths can differ, producing a ragged collection. Reordering the list can exchange pointer elements without copying the text. Assigning days[0] = "Thursday"; is allowed: the pointer element can change even though characters reached through it are read-only.
In most expressions, days converts to a pointer to its first element. Because an element is a const char *, the converted type is const char **:
for (const char **d = days; d < days + 3; d++) {
puts(*d);
}
The loop advances by one pointer element per iteration. *d retrieves the character pointer that puts needs.
Read command-line strings
int main(int argc, char *argv[]) receives the same parameter type as char **argv. argc is nonnegative and counts the available strings. When it is positive, argv[0] represents the program name or an empty string if that name is unavailable. User arguments occupy indices 1 through argc - 1, and argv[argc] is a null pointer.
For a typical invocation ./greet Ada 42, argc is 3, argv[1] points to "Ada", and argv[2] points to "42". The last argument is text until a conversion such as strtol reads it. argv[1][0] is 'A'. Check argc > 2 before using argv[2].
A loop that prints user arguments can use for (int i = 1; i < argc; i++) puts(argv[i]);. To walk from argv + 1 until a null pointer instead, first check argc > 0; if argc is zero, there is no user-argument range to start that way. Argument strings are modifiable, unlike string literals.
Follow complete rows
int grid[3][4] contains twelve integers in row-major order: all of row 0, then row 1, then row 2. With 4-byte ints, each row occupies 16 bytes. grid[2][1] lies 36 bytes after grid[0][0]: two complete rows contribute 32 bytes and the column contributes 4.
| Declaration or expression | Meaning |
|---|---|
int (*rows)[4] | Pointer to a row of four integers |
int *ptrs[4] | Array of four integer pointers |
Converted grid | int (*)[4] |
grid + 1 advances one row. A function accepting it can use int g[][4] or int (*g)[4], with a separate row count. It cannot use int **: the grid has no stored row pointers to dereference. A cast cannot manufacture those missing pointers.
Similarly, char names[3][6] owns eighteen character slots in fixed-size rows. const char *names[3] owns three pointer slots that can refer to strings of different lengths.
Key Takeaways
- An array of string pointers stores addresses, with the text elsewhere.
- An array of pointers converts to a pointer to its first pointer element.
argv[argc]is null; check the argument count before reading a particular string.- A two-dimensional array stores contiguous rows and contains no row-pointer objects.
int (*)[4]andint **describe different storage layouts.- Two subscripts alone do not establish that two objects have the same type.
5Choose functions and supply callbacks
A function pointer lets a program choose an operation while it runs. Its type records the call that is allowed, and a callback parameter lets another function request that operation when needed. Use those ideas first in a small table of arithmetic functions, then in qsort, where the callback must interpret element addresses correctly and return a consistent ordering. Sorting string pointers brings together both function pointers and two levels of object pointers.
5.1 Function pointer declarations and calls
A function pointer stores a reference to a function that can be called later. Its type includes the return type and parameter types. Those types must match the function used through the pointer.
Put parentheses around the pointer name
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int (*op)(int, int) = add;
Read from op: it is a pointer, the pointed-to thing is a function taking two ints, and that function returns an int. Without the parentheses, int *op(int, int); declares a function returning an integer pointer. The parentheses change the declaration's meaning.
A function name converts to a function pointer in this initializer. op = &add; is equivalent to op = add;. Both op(6, 3) and (*op)(6, 3) call add and return 9. Assigning op = sub; makes the same call return 3. Nothing about the call expression has changed except the function stored in the pointer.
Keep the signature compatible
A pointer of type int (*)(int, int) cannot safely call a function taking and returning double. For example, assigning sqrt to it produces an incompatible-type diagnostic. Casting the function pointer to silence a diagnostic does not make a mismatched call valid; the behavior is undefined.
Standard C treats object pointers and function pointers separately. void * is the generic object-pointer type, not a portable container for a function pointer. Function pointers support equality and inequality tests, but no pointer arithmetic.
A null function pointer means there is no callable function. When an interface permits that state, check it:
if (op != NULL) {
int result = op(6, 3);
printf("%d\n", result);
}
Calling through a null function pointer is undefined behavior.
Name the type once
typedef int (*binop)(int, int);
binop ops[] = {add, sub, mul};
The typedef names a pointer type. binop selected = mul; declares one pointer; binop pick(char symbol); declares a function returning one. Without the typedef, that return declaration would be int (*pick(char symbol))(int, int);.
An array of function pointers is a dispatch table. ops[2](6, 3) selects mul and returns 18. For a signed selection index i, check i >= 0 && i < 3 before evaluating ops[i]. Indexing outside the table is invalid even if every stored function is otherwise callable.
Read larger declarations outward
Start with the name and resolve parentheses and postfix parts before moving farther outward. In int (*table[3])(int, int), table[3] makes an array of three elements, * makes each element a pointer, and (int, int) makes its target a function returning int.
In char *(*f)(const char *), f is a pointer to a function taking a const char *; that function returns a char *. Keep the returned pointer's lifetime requirements separate from the function pointer's compatible-call requirements.
Key Takeaways
- Parentheses distinguish a function pointer from a function returning a pointer.
- A function pointer records the return and parameter types of valid calls.
- A function name can initialize a compatible function pointer without
&. - A typedef can make function pointer tables and return types easier to read.
- Validate the table index and any optional null pointer before calling.
5.2 Callbacks and correct qsort comparisons
A callback is a function supplied to another function, which calls it as part of its work. This separates the general traversal or sorting operation from the particular action or ordering you choose.
Pass an operation without calling it
void twice(int *x) { *x *= 2; }
void for_each(int *a, size_t n, void (*fn)(int *)) {
for (size_t i = 0; i < n; i++) fn(&a[i]);
}
For int v[] = {1, 2, 3};, for_each(v, 3, twice); produces {2, 4, 6}. The function name supplies a pointer. Writing twice() would try to call it immediately, with no required argument, instead of supplying an operation for later calls.
Recover a type from a generic object pointer
A void * can carry an object pointer and convert back to the original type. It cannot be dereferenced or used in standard C pointer arithmetic because it supplies no element type. Preserve qualifiers: read-only data travels through const void *, then through an appropriate pointer to const.
qsort from <stdlib.h> takes a base address, an element count, an element size in bytes, and a comparison callback. The callback has type int (*)(const void *, const void *). Each argument points to an array element, and the returned sign says where the first element belongs:
| Return sign | Ordering |
|---|---|
| Negative | First before second |
| Zero | Equivalent for this ordering |
| Positive | First after second |
Sort integers without overflow
int cmp_int(const void *a, const void *b) {
int x = *(const int *)a;
int y = *(const int *)b;
return (x > y) - (x < y);
}
/* In the caller: */
int v[] = {42, -7, 19, 0, 3};
qsort(v, 5, sizeof v[0], cmp_int);
The array becomes {-7, 0, 3, 19, 42}. Comparisons produce 0 or 1, so the return expression produces only -1, 0, or 1. Reversing the ordering expression to (y > x) - (y < x) sorts in descending order.
Do not use return x - y; for arbitrary signed integers. For 32-bit int, subtracting -2000000000 from 2000000000 exceeds INT_MAX. Signed overflow has undefined behavior, so there is no promised wrapped result or reliable comparison sign.
Sort pointers to strings
int cmp_str(const void *a, const void *b) {
return strcmp(*(const char *const *)a,
*(const char *const *)b);
}
const char *names[] = {"Lin", "Ada", "Grace"};
qsort(names, 3, sizeof names[0], cmp_str);
Include <string.h> for strcmp. An array element here is itself a pointer. The callback receives an address of that pointer element, so dereferencing once retrieves the string address. The added inner const prevents changing the element through this access path. After sorting, the order is "Ada", "Grace", "Lin".
strcmp(a, b) would read the bytes of the pointer slots as text. Also use sizeof names[0], the size of a pointer element, not sizeof(char) or the length of a name.
Comparators must leave elements unchanged and answer consistently, including transitive ordering. qsort does not promise to preserve the order of elements that compare equal. bsearch accepts the same comparator shape to search an array already sorted according to that ordering.
Key Takeaways
- Pass a function name to supply a callback; parentheses call a function immediately.
- Recover the element type from each generic comparison argument before access.
qsortneeds a count, one element’s size, and a compatible comparator.- Use comparison signs without potentially overflowing subtraction.
- String-pointer comparators receive addresses of pointer slots.
- Equal comparison results do not guarantee stable order after
qsort.
Pointers in C examples
1Track an address and the value it reaches
13 steps
- 1IntroductionKeep three things separate
A stored integer has a value, a location, and a lifetime. A pointer adds another object whose value is an address. Follow a small program by recording both kinds of values: integers such as 10, and addresses such as
0x1000.The useful question at each line is concrete: does this statement change an address, change the object reached through that address, or simply read a value?
- 2ExplanationLay out four integers
Imagine
int scores[4]beginning at0x1000on a system with 4-byte integers. The first element uses bytes0x1000through0x1003. The next starts at0x1004, followed by0x1008and0x100C.The elements have consecutive storage. This layout rule belongs to the array; it gives no promised order for two separately declared integers.
- 3ExampleMeasure the array, then its contents
For
char word[20] = "hi";, two measurements give different results:sizeof wordis 20 bytes of array storage.strlen(word)is 2 characters before'\0'.
The terminator occupies another array element, and the remaining elements still belong to the array.
sizeofreturnssize_t, printed with%zu; it does not count how many elements the program currently uses. - 4ConnectionSeparate layout from lifetime
Alignment controls suitable starting addresses, while endianness controls the order of bytes inside a value. Neither tells you how long an object exists.
An ordinary local lasts through its block’s execution. A static local or global lasts throughout the program. Saving an address records a route to storage; it cannot keep an automatic local alive after the block ends.
- 5ChallengeFind an element’s address
A six-element integer array starts at
0x1800, with 4 bytes per element. Where does element 3 begin, and what is the whole array’s size?---reveal---
Element 3 starts twelve bytes after the beginning, at
0x180C. Six 4-byte elements occupy 24 bytes. The index gives an element offset; multiplying by the element size gives the byte offset. - 6ExplanationGive the pointer a target
Consider
int count = 42; int *p = &count;. The&operator obtains the address ofcount, andpstores that address. Ifcountis at illustrative address0x2000, the pointer’s value is0x2000; the integer’s value stays 42.The pointer also occupies storage of its own. Its address is
&p, which is different information from the address held inp. - 7TipRead the star beside each name
int *p, q;declares one pointer and one ordinary integer. To declare two pointers, useint *p, *q;, or put the declarations on separate lines.Initialize a pointer with a suitable object address or
NULL. A null pointer is a known empty state; an uninitialized local pointer has an indeterminate value and cannot safely be tested as though it were already null. - 8ChallengeRedirect without changing data
Start with
int a = 9, b = 16; int *p = &a;. Then executep = &b;. Which integer values change, and what doespnow hold?---reveal---
Neither integer changes:
aremains 9 andbremains 16. Onlypchanges, storing the address ofb. Pointer assignment replaces an address; it does not copy the contents of the old or new target. - 9ExampleRead, add, and write through p
Continue with
int x = 10; int *p = &x;and execute:*p = *p + 5;The right-side
*preads 10 fromx. Adding 5 gives 15. The left-side*pdesignates the same object, so the assignment writes 15 intox. The address inphas not changed. Next,int y = *p * 2;reads 15 and creates a separate integerycontaining 30. - 10Key InsightAn address copy creates another route
For
int n = 6; int *p = &n; int *q = p;, both pointers reachn. The assignment copied an address, not the integer object.After
*q = 14;, readingn,*p, or*qgives 14. Draw one integer box and two arrows to it. Creating two integer boxes would incorrectly suggest independent data. - 11TipCheck the target before access
Access needs a pointer to a live object of a suitable type. A null pointer designates no object, and a dangling pointer has lost its live target. A null test handles an initialized pointer’s optional empty state; it does not establish that a non-null target remains alive.
Invalid access has undefined behavior. C promises neither a particular value nor a crash, even when one run appears normal.
- 12ChallengeFollow a copied address
What are the final values of
aandb?int a = 6, b = 19; int *p = &a, *q = p; p = &b; *q = *p + 1;---reveal---
abecomes 20 andbstays 19. Redirectingpleavesqpointing toa. The final line readsbthroughpand writes the sum intoathroughq. - 13RecapTrace the object named by the left side
Read the left side before predicting a change. Assigning to
pchanges an address; assigning to*pchanges its target. Keep copied pointers as separate arrows, check the target’s lifetime, and use the pointed-to type to interpret each access.
2Make a function change caller-owned data
11 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionFollow the address into a call
- 2ExplanationA copied address still reaches the caller
- 3ExampleSave the value before replacing it
- 4ExampleWrite the second value into the first slot
- 5ExampleFinish the exchange
- 6ChallengeDeliver two outputs
- 7ExplanationPut const at the intended level
- 8ExampleReturn a live position
- 9ChallengeExplain the lifetime failure
- 10ChallengeChoose a writable search interface
- 11RecapCheck the write and the lifetime
3Walk an array without crossing its boundary
15 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionFind the start, step, and stop
- 2ExplanationMove by elements
- 3ExampleSubtract two positions
- 4Key InsightUse a boundary that is not an element
- 5ChallengeLocate the midpoint
- 6ExplanationAn array expression supplies a starting pointer
- 7ExampleKeep sizeof at the correct boundary
- 8ChallengePass a smaller range
- 9ExampleRead then advance, or advance then read
- 10ExplanationFind a string’s end
- 11ExampleCopy one character at a time
- 12TipReserve the terminator’s slot
- 13ChallengeFix the final loop visit
- 14ChallengeChoose a safe reversal setup
- 15RecapMake every visited position explicit
4Change a pointer and read a pointer array
12 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionMake the pointer itself a target
- 2ExampleDraw two arrows
- 3ExplanationChoose which level changes
- 4ExamplePass the caller’s position by address
- 5ChallengePredict a pointer-only change
- 6ConnectionRead a conversion’s stopping position
- 7ExplanationAn array can own pointer slots
- 8ExampleAdvance through addresses
- 9TipCheck argument counts before string access
- 10ChallengeRead a whole row type
- 11ChallengeMatch a function to the supplied storage
- 12RecapName the object at every level
5Choose an operation and sort through a callback
15 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionSeparate a general job from its chosen operation
- 2ExplanationRead the callable type
- 3ExampleSelect a different function
- 4TipName a pointer type for a table
- 5ChallengeRead a related declaration
- 6ExplanationSupply an action callback
- 7ExampleSupply the four sort arguments
- 8ExplanationRecover the integer element type
- 9ExampleReturn the sign of the ordering
- 10ChallengeAvoid an overflowing shortcut
- 11ConnectionSort string pointers, not character rows
- 12ChallengeChoose the correct sorting size
- 13Key InsightEquality does not promise stability
- 14ChallengeTrace a selected comparison callback
- 15RecapMatch the call, layout, and ordering
Pointers in C practice questions
Set 1: Trace addresses and function calls10 Practice Questions
Question 1 of 10
int occupies 4 bytes. An array int levels[7] starts at illustrative address 0x2400. At what address does levels[4] begin?Show answerHide answer
0x10, and the address is 0x2410. Adding only 4 to the address would treat the index as a byte count and reach the second element instead.Question 2 of 10
int items = 16;, what declaration creates a pointer to items?Show answerHide answer
&items obtains the address of the integer, and int *p declares the matching pointer type. Initializing the pointer with items would supply the stored integer 16 instead of an address of an integer object. Taking an address leaves the value of items unchanged.Question 3 of 10
What value does n hold after this code?
int n = 14;
int *p = &n;
*p = 23;
Show answerHide answer
p holds the address of n, so *p designates that same integer. Assigning 23 through *p replaces the value in n. Keeping 14 would confuse a write through the pointer with a change to the pointer’s own stored address.Question 4 of 10
What is score after clear(score) returns?
void clear(int value) { value = 0; }
/* In the caller: */
int score = 27;
clear(score);
Show answerHide answer
value to zero changes that parameter, so the caller’s score stays 27. Expecting zero would require a function that receives &score through an integer pointer and writes to the pointed-to object.Question 5 of 10
On a system with 8-byte object pointers, what does this print?
char label[12] = "oak";
char *p = label;
printf("%zu %zu
", sizeof label, sizeof p);
Show answerHide answer
label is an array of twelve characters, so sizeof label is 12 even though the stored text has only three letters. p is a separate pointer object and occupies 8 bytes on this system. Counting the terminator gives the string’s occupied prefix, not the declared array size.Question 6 of 10
What are left and right after the call?
void exchange(int *a, int *b) {
int *t = a;
a = b;
b = t;
}
/* In the caller: */
int left = 11, right = 29;
exchange(&left, &right);
Show answerHide answer
*a or *b. No integer object is written, so left stays 11 and right stays 29. Expecting the values to swap confuses moving local address values with copying the integers reached through those addresses.Question 7 of 10
int read_count(int *out);. It returns 1 on success and writes the count only on success. What caller code safely prints a newly read count?Show answerHide answer
n when the function fails without writing. An uninitialized pointer supplies no valid output object for the function to fill.Question 8 of 10
int x = 6, y = 17; const int *p = &x;, what assignment is allowed?Show answerHide answer
p is a pointer to const integer data, so it may be redirected to y. Assigning through *p is rejected because that access path is read-only. The restriction is on access through this pointer; x itself was defined without const and can still be changed by its own name.Question 9 of 10
What are the final values of a and b?
int a = 4, b = 13;
int *p = &a, *q = p;
*p += 3;
p = &b;
*q = *p - 2;
*p = *q + 5;
Show answerHide answer
a equal 7, and redirecting p leaves q pointing to a. The next assignment sets a to 11 using b’s value 13, then the final write sets b to 16. Keeping a at 7 incorrectly moves q when only p was redirected.Question 10 of 10
What pair does the final line print?
int *next_value(void) {
static int n = 20;
n += 3;
return &n;
}
/* In the caller: */
int *first = next_value();
int saved = *first;
int *second = next_value();
printf("%d %d
", saved, *first + *second);
Show answerHide answer
saved. The second call changes the same static integer to 26, and both returned pointers reach it, so their values sum to 52. The result 49 treats the first pointer as a preserved snapshot, but only saved holds a separate copied integer.Set 2: Reason about array ranges and strings10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
double values[8];, what is &values[6] - &values[2]?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
puts(message + 5) print for const char *message = "blue sky";?Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
What is the value of *(p - 2) after these statements?
int bins[] = {6, 12, 18, 24, 30, 36, 42};
int *p = bins + 1;
p += 4;
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
What does total(data + 1, 3) return?
int total(const int *p, size_t n) {
int s = 0;
for (size_t i = 0; i < n; i++) s += p[i];
return s;
}
/* In the caller: */
int data[] = {9, 4, 7, 2, 8};
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
On a system with 8-byte pointers and 4-byte ints, what does count(values) return?
size_t count(int a[9]) {
return sizeof a / sizeof a[0];
}
/* In the caller: */
int values[9] = {0};
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
What are taken and a[0] afterward?
int a[] = {8, 15, 22};
int *p = a;
int taken = *p++;
(*p)++;
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
After this code, what is the greatest nonnegative integer k for which p + k can be formed within the permitted pointer range?
int a[8];
int *p = a + 1, *end = a + 8;
p += (end - p) / 2;
end -= 2;
p += end - p;
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
A writable null-terminated string is reversed with this setup. What input exposes an invalid pointer calculation before the loop begins?
size_t n = strlen(s);
char *left = s;
char *right = s + n - 1;
while (left < right) {
char t = *left; *left++ = *right; *right-- = t;
}
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
int a[6] begins at illustrative address 0x3000, and each integer occupies 4 bytes. What addresses do a + 1 and &a + 1 represent?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
What are count and p - text after this loop?
char text[] = "abaca";
char *p = text;
int count = 0;
while (*p != '\0') {
if (*p++ == 'a') count++;
}
Show answerHide answer
Save this lesson to see this answer.
Set 3: Follow pointer levels and storage layouts10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
With 8-byte object pointers, what is sizeof words?
const char *words[] = {"red", "violet", "gold", "teal"};
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
What is *p afterward?
int a = 18, b = 25;
int *p = &a;
int **pp = &p;
*pp = &b;
**pp -= 4;
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
int n = 31; int *p = &n; int **pp = &p;, what is the type of *pp?Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
char *end; long n = strtol("57kg", &end, 10);, what does end point to after the conversion?Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
./paint amber 6 in the usual hosted layout. What are argc and argv[2][0]?Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
int board[2][5] after its usual array conversion?Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
What are a, b, and *p after the call?
void shift(int **slot, int *next) {
**slot += 2;
*slot = next;
**slot += 3;
}
/* In the caller: */
int a = 5, b = 12;
int *p = &a;
shift(&p, &b);
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
map[2][3] from map[0][0], and what is the type of map + 1, for int map[4][6] with 4-byte integers?Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
What are word and the index of p afterward?
char word[] = "abcd";
char *p = word;
char saved = *p++;
*p = saved;
p += 2;
*p = 'z';
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Set 4: Choose operations and build sorting callbacks10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
int (*filter)(int); declare?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
void visit(int *a, size_t n, void (*action)(int *));, and void mark(int *x); is available. How does a caller supply mark for a five-element array data?Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
What value is assigned to result?
int difference(int a, int b) { return a - b; }
int product(int a, int b) { return a * b; }
int (*op)(int, int) = difference;
op = product;
int result = (*op)(7, 4);
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
int readings[8];, what call sorts all elements using a compatible compare callback?Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
What are result and calls after this code?
int calls = 0;
int add_one(int x) { calls++; return x + 1; }
int twice(int x) { calls++; return x * 2; }
/* In a function: */
int (*ops[])(int) = {twice, add_one};
int result = ops[1](ops[0](5));
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
int in ascending order. Which return expression meets that requirement after reading the elements into x and y?Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
const char *labels[], what retrieves the string passed as comparator parameter const void *a before calling strcmp?Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
int (*ops[3])(int, int) has valid functions in slots 0 and 2, and NULL in slot 1. For a signed integer choice, what guard safely permits ops[choice](8, 2)?Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
{4, -4, 2, -2} with qsort. What result is guaranteed?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
A list is sorted by increasing string length using this callback body. What is the defect?
const char *x = *(const char *const *)a;
const char *y = *(const char *const *)b;
size_t nx = strlen(x), ny = strlen(y);
return nx < ny;
Show answerHide answer
Save this lesson to see this answer.
Stuck on your own question? Homework Help
Key Terms
A
Address-of operator
& obtains the address of an addressable object or a function, producing a matching pointer type.&values[2] supplies the address of the third element without changing its contents.Aliasing
int *q = p;, both pointers reach the same integer until a later assignment redirects one of them.Alignment
argc
argc is the nonnegative argument count passed to hosted main, counting the strings supplied through argv, including a program name when present.argv[2], test that argc > 2 so the requested argument string actually exists.argv
argv is the character-pointer list passed to hosted main, with a null pointer at index argc and user argument strings starting at index 1../sum 8 13 invocation, argv[2] reaches "13"; the argument is text before numeric conversion.Array bounds
a - 1 for a pointer at an array’s start is invalid even if the program never reads through it.Array of pointers
Array parameter adjustment
int a[10] is a pointer inside the function, so sizeof a measures that pointer.Array slice
data + 2 with a count of 3 selects original indices 2 through 4 without copying them.Array-to-pointer conversion
int *p = values; initializes a pointer to values[0]; the original array still owns all its elements.Automatic storage duration
B
Buffer overflow
Byte
sizeof; a C char always occupies one byte, though other type sizes depend on the system.C
Callback
Comparator
(x > y) - (x < y) orders integers in ascending order without subtracting the values themselves.Const pointer
int *const p = &x;, *p = 3; is allowed, but p = &y; cannot redirect the pointer.D
Dangling pointer
Dereferencing
* to a pointer to designate the pointed-to object, allowing a suitable read or write through that route.int *p = &score;, assigning *p = 28; changes the same integer later read through score.Dispatch table
E
End pointer
"73ms" with strtol can return 73 and set the caller’s end pointer to the m.Endianness
0x12345678 has bytes 78 56 34 12 in low-to-high address order on a little-endian target.F
Function pointer
int (*op)(int, int) = add; stores an addition function that can later be called as op(4, 7).Function signature
G
Generic object pointer
void *, or a qualified form, to carry an object address that must regain a suitable type before access.const void * to const int * before reading the addressed array element.I
Indeterminate pointer value
int *p; alone does not let the program safely test, follow, or write through that pointer.Indirection level
int **pp, the first dereference gives an int *, and the second reaches an integer object.M
Memory address
0x2000, its remaining bytes follow through 0x2003.N
Null character
'\0', that terminates a C string within its accessible character storage.Null pointer
NULL when no match exists, so its caller tests the result before dereferencing it.O
One-past-the-end pointer
int a[5], a traversal can stop when its current pointer equals a + 5, before accessing another element.Operator precedence
++ binds more tightly than unary *, so *p++ groups as *(p++), not as (*p)++.Output parameter
P
Pass by reference
&x and &y changes caller variables by writing through the two local pointer parameters.Pass by value
int parameter leaves the caller’s integer unchanged; writing through a copied address can change it.Pointer
int *p = &count;, the pointer variable stores an address while the integer retains its own value.Pointer arithmetic
sizeof.Pointer declarator
* with an individual declared name to say that the named object has a pointer type.int *left, right;, only left is a pointer; declaring right as a pointer needs another star.Pointer difference
&a[5] - &a[2] gives 3 elements regardless of how many bytes one element occupies on that system.Pointer parameter
&score through int *out and writes zero into the caller’s integer using *out.Pointer to a pointer
int **pp = &p;, assigning *pp = &y; redirects p, while assigning **pp = 8; changes its target.Pointer to an array
int a[4], &a has type int (*)[4]; its arithmetic step differs from that of &a[0].Pointer to const
const int *p = &x; allows p = &y;, while an assignment such as *p = 3; is rejected.Postfix increment
*p++.int x = *p++; reads the element at the old position and leaves p pointing to the next position.ptrdiff_t
ptrdiff_t is the signed integer type used for pointer subtraction; a defined subtraction must have a result representable in that type.<stddef.h> for ptrdiff_t and use %td to print a difference between valid same-array positions.Q
qsort
qsort is the standard library function that rearranges an array using its element count, element size, and a supplied comparison callback.sizeof names[0] for the slot size, not a string’s character count.R
Ragged array
"red" and "violet" without reserving equal-sized character rows for both words.Row-major order
int a[3][4], adding 1 to converted a advances a complete four-integer row, not one integer.S
Segmentation fault
Signed integer overflow
x - y can overflow even when each input separately fits in an int.sizeof
sizeof operator reports a type’s or object’s storage size in bytes and returns a value of the unsigned integer type size_t.char code[10] = "go";, sizeof code reports 10 even though the text has only two characters.Stable sort
qsort provides no such guarantee.Stack frame
Static storage duration
Storage duration
String literal
const char *p = "oak"; exposes the literal through a read-only path, while char a[] = "oak"; creates a writable copy.strtol
strtol is the standard library function that converts leading numeric text to long and can report its stopping position through char **.&end lets strtol replace the caller’s character pointer with the first unused character’s address.Subscript operator
a[i] as *(a + i), using the pointer’s element type to scale the position.p points to original element 2, then p[1] accesses original element 3 of the same array.T
Two-dimensional array
int grid[2][5] converts to int (*)[5]; passing it as int ** would describe a different layout.typedef
typedef declaration introduces a name for an existing type, making a complex function-pointer type easier to reuse in declarations.typedef int (*operation)(int);, operation chosen; declares a function pointer, not a function body.U
Undefined behavior
About this lesson
Make your own Study Materials
Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.