Docs
String Arrays and Pointers
String Arrays and Pointers in C
Table of Contents
- •Introduction
- •Arrays of Strings
- •Pointer Arrays vs 2D Character Arrays
- •Allocating String Arrays
- •Accessing String Array Elements
- •Sorting String Arrays
- •Searching String Arrays
- •Command Line Arguments
- •Common Patterns
- •Memory Management
- •Best Practices
- •Summary
Introduction
Working with multiple strings requires understanding how to organize them in arrays. C provides two main approaches:
- •2D Character Arrays: Fixed-size rows and columns
- •Array of Pointers: Flexible, dynamic string storage
Understanding these concepts is essential for:
- •Processing word lists
- •Handling command line arguments
- •Building dictionaries
- •Text processing applications
Arrays of Strings
2D Character Array Approach
A 2D character array stores strings in fixed-size rows.
// Declare array of 5 strings, each up to 19 characters + null
char names[5][20] = {
"Alice",
"Bob",
"Charlie",
"David",
"Eve"
};
Memory Layout
Memory (2D Array - Fixed Size):
[0] [1] [2] ... [19]
┌──────┬──────┬──────┬─────┬──────┐
Row 0 │ A │ l │ i │ │ \0 │ "Alice"
├──────┼──────┼──────┼─────┼──────┤
Row 1 │ B │ o │ b │ \0 │ │ "Bob"
├──────┼──────┼──────┼─────┼──────┤
Row 2 │ C │ h │ a │ │ \0 │ "Charlie"
├──────┼──────┼──────┼─────┼──────┤
Row 3 │ D │ a │ v │ │ \0 │ "David"
├──────┼──────┼──────┼─────┼──────┤
Row 4 │ E │ v │ e │ \0 │ │ "Eve"
└──────┴──────┴──────┴─────┴──────┘
Total: 5 rows × 20 columns = 100 bytes (contiguous)
Array of Pointers Approach
An array of pointers stores addresses of strings.
// Array of 5 string pointers
char *names[] = {
"Alice",
"Bob",
"Charlie",
"David",
"Eve"
};
Memory Layout (Pointer Array)
Pointer Array: String Literals (in read-only memory):
names[0] ──────────────────► "Alice\0"
names[1] ──────────────────► "Bob\0"
names[2] ──────────────────► "Charlie\0"
names[3] ──────────────────► "David\0"
names[4] ──────────────────► "Eve\0"
Pointer array: 5 × sizeof(char*) = 40 bytes (on 64-bit system)
Strings: Variable sizes in separate memory locations
Pointer Arrays vs 2D Character Arrays
Comparison Table
| Feature | 2D Array char arr[N][M] | Pointer Array char *arr[N] |
|---|---|---|
| Memory | Contiguous block | Scattered (pointers + strings) |
| String Size | Fixed (M-1 max) | Variable |
| Modification | Strings modifiable | Literals are read-only |
| Memory Usage | May waste space | Efficient |
| Reordering | Expensive (copy strings) | Cheap (swap pointers) |
| Declaration | Size needed | Can omit size |
When to Use 2D Arrays
// Good for: Modifiable strings of similar length
char menu[4][20] = {
"New Game",
"Load Game",
"Settings",
"Exit"
};
// Can modify:
strcpy(menu[0], "Start New"); // OK
When to Use Pointer Arrays
// Good for: Read-only strings, varying lengths, sorting
const char *months[] = {
"January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December"
};
// For sorting: Just swap pointers (fast)
const char *temp = months[0];
months[0] = months[1];
months[1] = temp;
Modifiability Issue
// Pointer to string literal - READ ONLY
char *str = "Hello";
// str[0] = 'J'; // UNDEFINED BEHAVIOR! (may crash)
// Character array - MODIFIABLE
char str2[] = "Hello";
str2[0] = 'J'; // OK: str2 is now "Jello"
Allocating String Arrays
Static Allocation (2D Array)
// Fixed at compile time
#define MAX_STRINGS 10
#define MAX_LENGTH 50
char strings[MAX_STRINGS][MAX_LENGTH];
// Initialize
strcpy(strings[0], "First");
strcpy(strings[1], "Second");
Dynamic Allocation (Pointer Array)
// Allocate array of pointers
int n = 5;
char **strings = malloc(n * sizeof(char *));
if (strings == NULL) {
// Handle error
}
// Allocate each string
for (int i = 0; i < n; i++) {
strings[i] = malloc(50 * sizeof(char));
if (strings[i] == NULL) {
// Handle error, free previous allocations
}
}
// Use strings
strcpy(strings[0], "Hello");
strcpy(strings[1], "World");
// Free in reverse order
for (int i = 0; i < n; i++) {
free(strings[i]);
}
free(strings);
Dynamic Allocation with Variable Lengths
char **create_string_array(const char *data[], int count) {
char **arr = malloc(count * sizeof(char *));
if (arr == NULL) return NULL;
for (int i = 0; i < count; i++) {
// Allocate exact size needed
arr[i] = malloc(strlen(data[i]) + 1);
if (arr[i] == NULL) {
// Cleanup on failure
for (int j = 0; j < i; j++) {
free(arr[j]);
}
free(arr);
return NULL;
}
strcpy(arr[i], data[i]);
}
return arr;
}
Using strdup() for Convenience
#define _POSIX_C_SOURCE 200809L // For strdup
#include <string.h>
char **names = malloc(3 * sizeof(char *));
// strdup allocates and copies
names[0] = strdup("Alice"); // Allocates strlen("Alice")+1
names[1] = strdup("Bob");
names[2] = strdup("Charlie");
// Don't forget to free!
for (int i = 0; i < 3; i++) {
free(names[i]);
}
free(names);
Accessing String Array Elements
Indexing Syntax
char *words[] = {"Hello", "World", "C"};
// Access string
printf("%s\n", words[0]); // "Hello"
// Access character
printf("%c\n", words[0][1]); // 'e'
// Using pointers
printf("%c\n", *(words[0]+1)); // 'e' (same as above)
printf("%c\n", *words[0]); // 'H' (first char)
Iterating Through String Arrays
// Method 1: Using counter
int count = 3;
char *words[] = {"one", "two", "three"};
for (int i = 0; i < count; i++) {
printf("%s\n", words[i]);
}
// Method 2: NULL-terminated array
char *fruits[] = {"apple", "banana", "cherry", NULL};
for (int i = 0; fruits[i] != NULL; i++) {
printf("%s\n", fruits[i]);
}
// Method 3: Using pointer to pointer
char **ptr = fruits;
while (*ptr != NULL) {
printf("%s\n", *ptr);
ptr++;
}
Calculating Array Size
// For static arrays
char *words[] = {"Hello", "World", "C"};
int count = sizeof(words) / sizeof(words[0]); // 3
// For 2D arrays
char names[5][20] = {"Alice", "Bob", "Charlie"};
int rows = sizeof(names) / sizeof(names[0]); // 5
int cols = sizeof(names[0]); // 20
Sorting String Arrays
Bubble Sort for Strings
void bubble_sort_strings(char **arr, int n) {
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (strcmp(arr[j], arr[j + 1]) > 0) {
// Swap pointers (efficient!)
char *temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
// Usage
char *names[] = {"Charlie", "Alice", "Bob"};
bubble_sort_strings(names, 3);
// Now: {"Alice", "Bob", "Charlie"}
Using qsort()
#include <stdlib.h>
#include <string.h>
// Comparison function for qsort
int compare_strings(const void *a, const void *b) {
// a and b are pointers to char*
const char *str_a = *(const char **)a;
const char *str_b = *(const char **)b;
return strcmp(str_a, str_b);
}
// Case-insensitive comparison
int compare_strings_nocase(const void *a, const void *b) {
const char *str_a = *(const char **)a;
const char *str_b = *(const char **)b;
return strcasecmp(str_a, str_b); // POSIX
}
// Usage
char *words[] = {"banana", "Apple", "cherry"};
int count = sizeof(words) / sizeof(words[0]);
qsort(words, count, sizeof(char *), compare_strings);
Sorting 2D Character Arrays
// Sorting 2D arrays requires copying strings
void bubble_sort_2d(char arr[][20], int n) {
char temp[20];
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (strcmp(arr[j], arr[j + 1]) > 0) {
// Must copy entire strings
strcpy(temp, arr[j]);
strcpy(arr[j], arr[j + 1]);
strcpy(arr[j + 1], temp);
}
}
}
}
Searching String Arrays
Linear Search
int linear_search(char **arr, int n, const char *target) {
for (int i = 0; i < n; i++) {
if (strcmp(arr[i], target) == 0) {
return i; // Found at index i
}
}
return -1; // Not found
}
// Case-insensitive version
int linear_search_nocase(char **arr, int n, const char *target) {
for (int i = 0; i < n; i++) {
if (strcasecmp(arr[i], target) == 0) {
return i;
}
}
return -1;
}
Binary Search (Sorted Array)
int binary_search_string(char **arr, int n, const char *target) {
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
int cmp = strcmp(arr[mid], target);
if (cmp == 0) {
return mid; // Found
} else if (cmp < 0) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1; // Not found
}
Using bsearch()
#include <stdlib.h>
int compare_for_bsearch(const void *key, const void *element) {
const char *search_key = *(const char **)key;
const char *arr_element = *(const char **)element;
return strcmp(search_key, arr_element);
}
// Usage (array must be sorted!)
char *words[] = {"apple", "banana", "cherry", "date"};
int count = 4;
char *target = "cherry";
char **result = bsearch(&target, words, count, sizeof(char *),
compare_for_bsearch);
if (result != NULL) {
printf("Found: %s\n", *result);
}
Command Line Arguments
Understanding argc and argv
int main(int argc, char *argv[]) {
// argc: Argument count (including program name)
// argv: Array of argument strings
printf("Number of arguments: %d\n", argc);
for (int i = 0; i < argc; i++) {
printf("argv[%d] = \"%s\"\n", i, argv[i]);
}
return 0;
}
Example Execution
$ ./program hello world 123
Output:
Number of arguments: 4
argv[0] = "./program"
argv[1] = "hello"
argv[2] = "world"
argv[3] = "123"
Memory Layout of argv
argv (array of pointers):
argv[0] ──► "./program\0"
argv[1] ──► "hello\0"
argv[2] ──► "world\0"
argv[3] ──► "123\0"
argv[4] ──► NULL (always null-terminated)
Processing Command Line Options
int main(int argc, char *argv[]) {
int verbose = 0;
const char *filename = NULL;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-v") == 0 ||
strcmp(argv[i], "--verbose") == 0) {
verbose = 1;
} else if (strcmp(argv[i], "-f") == 0 && i + 1 < argc) {
filename = argv[++i];
} else {
printf("Unknown option: %s\n", argv[i]);
}
}
if (verbose) printf("Verbose mode enabled\n");
if (filename) printf("File: %s\n", filename);
return 0;
}
Alternative Main Signature
// With environment variables
int main(int argc, char *argv[], char *envp[]) {
// envp contains environment variables
for (int i = 0; envp[i] != NULL; i++) {
printf("%s\n", envp[i]);
}
return 0;
}
Common Patterns
Tokenizing Input into String Array
int tokenize(char *str, char *tokens[], int max, const char *delim) {
int count = 0;
char *token = strtok(str, delim);
while (token != NULL && count < max) {
tokens[count++] = token;
token = strtok(NULL, delim);
}
return count;
}
// Usage
char input[] = "one,two,three,four";
char *tokens[10];
int count = tokenize(input, tokens, 10, ",");
Building String Array from Input
char **read_lines(FILE *file, int *count) {
char **lines = NULL;
char buffer[256];
int capacity = 0;
*count = 0;
while (fgets(buffer, sizeof(buffer), file)) {
// Remove newline
buffer[strcspn(buffer, "\n")] = '\0';
// Grow array if needed
if (*count >= capacity) {
capacity = capacity == 0 ? 10 : capacity * 2;
char **new_lines = realloc(lines, capacity * sizeof(char *));
if (new_lines == NULL) {
// Handle error
break;
}
lines = new_lines;
}
// Allocate and copy line
lines[*count] = strdup(buffer);
if (lines[*count] == NULL) break;
(*count)++;
}
return lines;
}
Finding Unique Strings
int find_unique(char **arr, int n, char **unique) {
int unique_count = 0;
for (int i = 0; i < n; i++) {
int is_duplicate = 0;
for (int j = 0; j < unique_count; j++) {
if (strcmp(arr[i], unique[j]) == 0) {
is_duplicate = 1;
break;
}
}
if (!is_duplicate) {
unique[unique_count++] = arr[i];
}
}
return unique_count;
}
Filtering Strings
int filter_by_prefix(char **arr, int n, char **result,
const char *prefix) {
int count = 0;
size_t prefix_len = strlen(prefix);
for (int i = 0; i < n; i++) {
if (strncmp(arr[i], prefix, prefix_len) == 0) {
result[count++] = arr[i];
}
}
return count;
}
Memory Management
Proper Cleanup Pattern
void free_string_array(char **arr, int count) {
if (arr == NULL) return;
for (int i = 0; i < count; i++) {
free(arr[i]); // Free each string
}
free(arr); // Free the array of pointers
}
// Usage
char **words = create_string_array(...);
// ... use words ...
free_string_array(words, count);
words = NULL; // Prevent dangling pointer
Resizing String Arrays
char **resize_string_array(char **arr, int old_count, int new_count) {
char **new_arr = realloc(arr, new_count * sizeof(char *));
if (new_arr == NULL) {
return NULL; // Original array still valid
}
// Initialize new slots to NULL
for (int i = old_count; i < new_count; i++) {
new_arr[i] = NULL;
}
return new_arr;
}
Copying String Arrays
char **copy_string_array(char **src, int count) {
char **dest = malloc(count * sizeof(char *));
if (dest == NULL) return NULL;
for (int i = 0; i < count; i++) {
dest[i] = strdup(src[i]);
if (dest[i] == NULL) {
// Cleanup on failure
for (int j = 0; j < i; j++) {
free(dest[j]);
}
free(dest);
return NULL;
}
}
return dest;
}
Best Practices
1. Use const for Read-Only Arrays
// Good: Clear intent
void print_words(const char * const words[], int count) {
for (int i = 0; i < count; i++) {
printf("%s\n", words[i]);
}
}
2. Always Track Array Size
typedef struct {
char **data;
int count;
int capacity;
} StringArray;
void init_string_array(StringArray *arr) {
arr->data = NULL;
arr->count = 0;
arr->capacity = 0;
}
3. Validate Before Access
const char* get_string(char **arr, int count, int index) {
if (arr == NULL || index < 0 || index >= count) {
return NULL;
}
return arr[index];
}
4. Use NULL Termination for Flexibility
// Self-describing array (no need to pass count)
char *colors[] = {"red", "green", "blue", NULL};
// Can iterate without knowing size
for (char **p = colors; *p != NULL; p++) {
printf("%s\n", *p);
}
5. Choose the Right Approach
// Use 2D array when:
// - Strings have similar lengths
// - Need to modify strings
// - Know maximum count and length at compile time
char names[MAX_NAMES][MAX_NAME_LEN];
// Use pointer array when:
// - String lengths vary greatly
// - Need efficient sorting/reordering
// - Dynamic number of strings
char **names;
Summary
Key Concepts
| Concept | Description |
|---|---|
char arr[N][M] | 2D array, fixed size, contiguous memory |
char *arr[N] | Array of pointers, flexible lengths |
char **arr | Pointer to pointer, fully dynamic |
argv | Command line arguments array |
Memory Allocation Patterns
// Static 2D array
char strings[10][50];
// Static pointer array
char *strings[10];
// Dynamic pointer array
char **strings = malloc(n * sizeof(char *));
for (int i = 0; i < n; i++) {
strings[i] = malloc(len);
}
Access Patterns
// Access string
arr[i]
// Access character
arr[i][j]
// Using pointers
*(arr + i) // Same as arr[i]
*(*(arr + i) + j) // Same as arr[i][j]
Common Operations
| Operation | Pointer Array | 2D Array |
|---|---|---|
| Sort | Swap pointers | Copy strings |
| Compare | strcmp(arr[i], arr[j]) | Same |
| Copy | arr[i] = strdup(src) | strcpy(arr[i], src) |
| Search | Linear/Binary | Same |
| Resize | realloc + allocate | Limited |
Cleanup Pattern
// Always free in reverse order
for (int i = 0; i < count; i++) {
free(arr[i]); // Free strings first
}
free(arr); // Then free array
arr = NULL;