Part of C in 100s

C in 100 Seconds: Arrays of Pointers | Episode 28

Celest KimCelest Kim

Video: C in 100 Seconds: Arrays of Pointers — String Lists and Pointer Tables | Episode 28 by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

C Arrays of Pointers: String Lists and Pointer Tables

char *colors[] = {"red", "green", "blue"}; — array of pointers to strings. Each element is a pointer; each pointer points at a string literal. The standard way to hold a list of strings.

C doesn't have a "list of strings" type. The equivalent is an array of char * — pointers to the actual string data.

The basic shape

#include <stdio.h>

int main() {
  char *colors[] = {"red", "green", "blue"};
  int len = sizeof(colors) / sizeof(colors[0]);

  for (int i = 0; i < len; i++) {
    printf("colors[%d] = %s\n", i, colors[i]);
  }

  int a = 10, b = 20, c = 30;
  int *nums[] = {&a, &b, &c};

  for (int i = 0; i < 3; i++) {
    printf("*nums[%d] = %d\n", i, *nums[i]);
  }

  return 0;
}

char *[] vs char [][]

Two ways to hold multiple strings:

// Option A: array of pointers
char *names[] = {"Alice", "Bob", "Charlie"};

// Option B: 2D array of chars
char names[3][20] = {"Alice", "Bob", "Charlie"};
Aspect char *[] char [][]
Memory Pointers + read-only literals Fixed buffer per slot
Modifiable? No (literals are read-only) Yes
Per-string size Variable Fixed (must fit longest)
Memory used Smallest possible Worst case × N

For constant strings — menu items, error messages — char *[] is cleaner. For modifiable strings, char [N] (fixed-size) or dynamically allocated char ** (heap of strings).

Iterating

for (int i = 0; i < len; i++) {
  printf("%s\n", colors[i]);
}

colors[i] is the i-th char *. printf("%s", ...) follows the pointer to print the string.

To iterate while looking for a sentinel:

char *args[] = {"a", "b", "c", NULL};
for (int i = 0; args[i] != NULL; i++) {
  printf("%s\n", args[i]);
}

NULL-terminated arrays are common (mimicking how strings end with \0). The classic example is char **argv in main — terminated with argv[argc] == NULL.

Pointer to a non-string

int a = 10, b = 20, c = 30;
int *nums[] = {&a, &b, &c};

for (int i = 0; i < 3; i++) {
  printf("*nums[%d] = %d\n", i, *nums[i]);
}

int *nums[] is "array of pointers to int." Each element points to a different int. Useful when the ints are scattered in memory or have different lifetimes.

For a contiguous array of ints, just use int nums[3] — no pointers needed.

String literals are read-only

char *colors[] = {"red", "green", "blue"};
colors[0][0] = 'R';   // UNDEFINED — string literals are in read-only memory

Each "red" is a string literal — the compiler stores it in a read-only section. Modifying it crashes (or worse, silently does nothing).

For modifiable strings:

char colors_storage[3][10] = {"red", "green", "blue"};
char *colors[3];
for (int i = 0; i < 3; i++) {
  colors[i] = colors_storage[i];
}
colors[0][0] = 'R';   // OK now

Or heap-allocate each:

char *colors[3];
colors[0] = strdup("red");
colors[1] = strdup("green");
colors[2] = strdup("blue");
// remember to free each
for (int i = 0; i < 3; i++) free(colors[i]);

char **argv in main

int main(int argc, char *argv[]) {
  for (int i = 0; i < argc; i++) {
    printf("argv[%d] = %s\n", i, argv[i]);
  }
}

argv is an array of pointers to strings — the command-line arguments. argv[0] is the program name; argv[1...argc-1] are the args; argv[argc] is NULL.

The signature char *argv[] and char **argv are equivalent (arrays decay to pointers).

Passing to functions

void print_strings(char *strs[], int n) {
  for (int i = 0; i < n; i++) {
    printf("%s\n", strs[i]);
  }
}

print_strings(colors, 3);

char *strs[] is the parameter — equivalent to char **strs. The array decays to a pointer-to-pointer.

For variable-length lists (NULL-terminated):

void print_strings_null(char *strs[]) {
  for (int i = 0; strs[i] != NULL; i++) {
    printf("%s\n", strs[i]);
  }
}

No length parameter needed.

Sorting

#include <stdlib.h>
#include <string.h>

int strcmp_ptr(const void *a, const void *b) {
  return strcmp(*(const char **)a, *(const char **)b);
}

char *colors[] = {"red", "green", "blue"};
qsort(colors, 3, sizeof(char *), strcmp_ptr);
// colors is now {"blue", "green", "red"}

qsort works on any array; you supply the comparator. qsort passes pointers to the elements — for an array of char *, that's char **. The comparator dereferences and calls strcmp.

Common mistakes

Modifying string literals. colors[0][0] = 'R'; — undefined when the strings are literals. Use modifiable storage.

Confusing char *[] and char [][]. Different memory layouts. Different mutability.

sizeof after passing to a function. sizeof(strs) inside the function is pointer size, not array bytes. Pass length separately.

NULL not-checked in NULL-terminated lists. for (int i = 0; ; i++) printf("%s", strs[i]); runs forever past the terminator.

strdup without freeing. strdup malloc's a copy. Each string needs its own free.

What's next

Episode 29: double pointers. int **pp — pointer to pointer. The mechanism behind char **argv and dynamically-resizable arrays of pointers.

Recap

char *names[] for an array of pointers to strings — read-only literals are typical. Modify? Use char names[N][M] or heap-allocated char **. Iterate by index or NULL-terminator. argv in main is an array of char *. For sorting, qsort with a comparator that dereferences. String literals are read-only — never modify them.

Next episode: double pointers.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.