AR

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 at argv[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 into c.

  • cpp: pointer to the first element of cp (i.e., to a char **).

Effects of the four printf statements (spaces show final output):

  1. **++cpp → pre-increment cpp so it now points at cp[1] (c+2) → dereference twice to get string "POINT".

  2. *--*++cpp + 3 → (a) ++cpp (now cp[2] = c+1), (b) *cpp gives c+1; --*cpp decrements that pointer to c+0; dereference once → string "ENTER"; add 3 → skip first 3 chars → substring "ER".

  3. *cpp[-2] + 3cpp[-2] backtracks two elements to cp[0] (c+3); deref once → string "FIRST"; add 3 → "ST".

  4. cpp[-1][-1] + 1cpp[-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.