CMPSC 311 – Pointers, Arrays, and Function Pointers
Multidimensional Array Layout
C stores all multidimensional arrays in row-major order
Consecutive elements of the last (right-most) dimension sit next to each other in memory.
Example declaration and initializer
c int x[2][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}};
First row (index 0): 1\;2\;3\;4
Second row (index 1): 5\;6\;7\;8
3-D illustration
int x[3][2][4] = {
{{1, 2, 3, 4}, {5, 6, 7, 8}},
{{9, 10, 11, 12}, {13, 14, 15, 16}},
{{17, 18, 19, 20}, {21, 22, 23, 24}}
};
Memory stores 24 integers in one long contiguous block.
Access cost: compiler computes an offset using known row length(s). \text{addr}(x[i][j][k]) = \text{base} + ((i \cdot 2 + j) \cdot 4 + k) \times \text{sizeof(int)}
Bounds requirement: every dimension except the first must have an explicit size so the compiler can perform the above arithmetic at compile time.
Legal incomplete declarations (size inferred from initializer):
c int x[][4] = {...}; // 2-D int x[][2][4] = {...}; // 3-D
Practical tip: understanding this layout is crucial for interoperating with low-level I/O (e.g., writing images, matrices, etc.) and for pointer arithmetic tricks.
Arrays of Strings (two techniques)
1. True 2-D character array
char planets[][8] = {
"Mercury", "Venus", "Earth", "Mars",
"Jupiter", "Saturn", "Uranus", "Neptune"
};
Eight rows, each exactly 8 chars (7 visible + null terminator).
Memory looks like one contiguous block of 8 \times 8 = 64 bytes.
Pros: locality; cons: fixed, potentially wasted space (extra \0 padding).
2. Array of pointers to string literals
char *planets[] = {
"Mercury", "Venus", "Earth", "Mars",
"Jupiter", "Saturn", "Uranus", "Neptune"
};
planets
is an 8-element array; each element is a pointer to a read-only string literal stored elsewhere in the program image.Heterogeneous string lengths allowed; only 8 ×
sizeof(char*)
bytes in the table itself.
Traversal examples
for (int i = 0; i < 8; ++i)
if (planets[i][0] == 'M')
printf("%s\n", planets[i]);
for (char **p = planets; p < planets + 8; ++p)
if (**p == 'M')
printf("%s\n", *p);
First loop indexes; second loop uses pointer arithmetic on an array of pointers.
planets + 8
is one-past-the-end by the C standard (safe sentinel).
Command-Line Arguments (argc
, argv
)
Standard
main
signatures:
int main(void) // no arguments
int main(int argc, char *argv[])
int main(int argc, char **argv) // identical after decay
Semantics:
argc
= argument count (including program name).argv
= argument vector;argv[i]
points to a null-terminated string.C guarantees an extra
NULL
pointer atargv[argc]
→ can iterate until*p == NULL
.
Example echo program
int main(int argc, char **argv) {
for (int i = 0; i < argc; ++i)
printf("%s\n", argv[i]);
for (char **p = argv; *p != NULL; ++p)
printf("%s\n", *p);
}
Invocation image: compiler may lay out
g c c \0 - W a l l \0 foo.c \0 - o \0 foo \0\0
argv
points to each start address of the above strings.
Pointers to Pointers (double indirection)
Ordinary swap through pointers (review):
void swap(int *x, int *y) {
int tmp = *x; *x = *y; *y = tmp;
}
To swap two pointer variables themselves we need pointer to pointer:
void swapception(int **xp, int **yp) {
int *tmp = *xp; *xp = *yp; *yp = tmp;
}
int *xp = &x, *yp = &y; swapception(&xp, &yp);
Motivation: manipulating data-structure links (linked lists, trees) where the address of a pointer must be updated.
Triple-Pointer Puzzle Walk-Through
Code excerpt
char *c[] = {"ENTER", "NEW", "POINT", "FIRST"};
char **cp[] = {c+3, c+2, c+1, c};
char ***cpp = cp; // same as &cp[0]
Memory intuition:
c
: array of 4 pointers → each to a string literal.cp
: array of 4 pointers → each element points intoc
.cpp
: pointer to the first element ofcp
(i.e., to achar **
).
Effects of the four printf
statements (spaces show final output):
**++cpp
→ pre-incrementcpp
so it now points atcp[1]
(c+2
) → dereference twice to get string "POINT".*--*++cpp + 3
→ (a)++cpp
(nowcp[2]
=c+1
), (b)*cpp
givesc+1
;--*cpp
decrements that pointer toc+0
; dereference once → string "ENTER"; add 3 → skip first 3 chars → substring "ER".*cpp[-2] + 3
→cpp[-2]
backtracks two elements tocp[0]
(c+3
); deref once → string "FIRST"; add 3 → "ST".cpp[-1][-1] + 1
→cpp[-1]
=cp[1]
(c+2
);[-1]
on that pointer means*(cp[1] - 1)
=c+1
; deref once → "NEW"; +1 → "EW".
Final printed line:POINTERSTEW
followed by newline.
Function Pointers (basics)
void add(int a,int b); // prototype
void subtract(int a,int b);
void (*func_ptr)(int,int); // declaration of a pointer to function
func_ptr = &add; (*func_ptr)(10,5); // call → Addition: 15
func_ptr = subtract; // '&' optional
(*func_ptr)(10,5); // Subtraction: 5
Key points
Syntax:
ret_type (*name)(param_types)
.Can be stored in arrays, passed to functions, returned from functions → enables callbacks, strategy patterns, generic algorithms (
qsort
, event loops, etc.).
Standard Library Example: qsort
Header: #include <stdlib.h>
Prototype:
void qsort(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *));
Usage demo
int values[] = {88, 56, 100, 2, 25};
int cmpfunc(const void *a, const void *b) {
return *(const int*)a - *(const int*)b; // ascending order
}
qsort(values, 5, sizeof(int), cmpfunc);
qsort
treats the array as raw bytes and relies on user-supplied comparator.Shows power of function pointers + void pointers for generic data processing in C.
Philosophical & Practical Preludes to Assignment 3
Edsger Dijkstra quote:
"Program testing can be used to show the presence of bugs, but never to show their absence."
Implication: exhaustive reasoning or formal proofs are necessary for absolute correctness; testing alone uncovers bugs but cannot guarantee none remain.Alan Perlis reflection on complexity:
"Fools ignore complexity. Pragmatists suffer it. Some can avoid it. Geniuses remove it. Simplicity does not precede complexity, but follows it."Encourages iterative refinement—first grapple with complexity, then extract simplicity.Relevant to assignment design: strive for clear abstractions, minimal state, and well-defined pointer ownership.
Cross-Lecture Connections & Real-World Relevance
Pointer arithmetic rules underpin serialization, networking, graphics, OS kernels, where raw memory layout matters.
Command-line argument handling generalizes to environment variables (
extern char **environ
) and scripting interfaces.Function pointers are the basis for:
Callbacks in GUIs (
gtk_signal_connect
),Interrupt vector tables in embedded systems,
Dynamic dispatch in object-oriented patterns (v-tables in C++ compiled down to arrays of function pointers).
Double/triple pointers appear in linkers, allocators, and garbage collectors that must update roots to moveable objects.
Quick Reference Equations & Patterns
2-D array address formula (row-major): \text{addr} = \text{base} + (i \cdot N + j) \times \text{sizeof(elem)}
Generic swap for any pointer type using void*/size_t and
memcpy
.Comparator contract for
qsort
: must return<0
,0
,>0
according as a < b, a == b, a > b.
End of study notes. Replace slides for review, augment with hands-on coding to cement understanding.