Question

Creating an Array of Strings in C

carraysstring

Question

How can I correctly create an array of strings in C? I tried the following code:

char (*a[2])[14];
a[0] = "blah";
a[1] = "hmm";

GCC reports an assignment from incompatible pointer type warning. Why does this happen, what is the correct way to create the array, and why might printf(a[1]); still appear to print hmm?

Short Answer

You will learn that C strings are character arrays ending in \0, and that an “array of strings” can be represented either as an array of pointers to string literals or as a two-dimensional character array that owns writable character storage. You will also see why the original declaration has a different type than expected and why printf(a[1]); is unsafe.

Concept

A C string is an array of char values terminated by a null character ('\0'). For example, the literal "blah" occupies five characters in memory:

b l a h \0

C has no built-in string type, so an array of strings is usually created in one of two ways:

  1. An array of pointers: each element points to the first character of a string.
  2. A two-dimensional character array: each row contains storage for one string.

Your declaration is neither of these common forms:

char (*a[2])[14];

Read it from the identifier outward:

  • a[2] means a is an array with two elements.
  • *a[2] means each element is a pointer.
  • (*a[2])[14] means each pointer points to an array of 14 char values.

Therefore, a[0] has type char (*)[14]: a pointer to an entire 14-character array. But "blah", in most expressions, becomes a pointer to its first character, with type char * in traditional C terminology (and it should be treated as non-modifiable). A char * is not the same as a char (*)[14], so the compiler warns you.

Choosing the right representation matters because it determines ownership and mutability:

  • Use pointers when the strings already exist, such as string literals or command-line arguments.
  • Use a two-dimensional array when your program needs its own fixed-size writable storage for every string.

Mental Model

Think of a string as a row of letter tiles ending with a special stop tile ('\0').

An array of string pointers is like a list of addresses to existing rows of tiles. The rows may be in different places and may have different lengths.

words
+---------+      +---+---+---+---+---+
| address | ---> | b | l | a | h | \0|
+---------+      +---+---+---+---+---+
| address | ---> | h | m | m | \0|
+---------+      +---+---+---+---+

A two-dimensional character array is like reserving identical 14-tile slots in one cabinet. Every row has space for at most 13 visible characters plus its stop tile.

words[0]: [ b l a h \0 _ _ _ _ _ _ _ _ _ ]
words[1]: [ h m m \0 _ _ _ _ _ _ _ _ _ _ ]

Your original code instead creates a list of pointers to whole 14-tile rows. A normal string pointer points to one tile—the first character—not to a complete row object, which is why the types do not match.

Take Quiz

Syntax and Examples

Option 1: Array of pointers to string literals

Use this when the strings are fixed text that you do not need to modify.

#include <stdio.h>

int main(void) {
    const char *a[2] = {"blah", "hmm"};

    printf("%s\n", a[0]);
    printf("%s\n", a[1]);
    return 0;
}

a contains two const char * pointers. Each pointer refers to the first character of a string literal.

Use const char * because modifying a string literal is undefined behavior:

const char *text = "blah";
/* text[0] = 'B'; */  /* Invalid: do not modify a string literal. */

You can change which string a pointer refers to:

const char *a[2] = {"blah", "hmm"};
a[0] = "updated";

Option 2: A writable two-dimensional array

Use this when each string needs storage owned by the array and must be editable.

#include <stdio.h>
#include <string.h>

int main(void) {
    char a[2][14] = {"blah", "hmm"};

    strcpy(a[0], "hello");
    printf("%s\n", a[0]);
    printf("%s\n", a[1]);
    return 0;
}

Here, a[0] is an actual array of 14 characters. It can hold at most 13 non-null characters because one position is required for the terminating \0.

You cannot assign a new string directly to an array after initialization:

char a[2][14] = {"blah", "hmm"};
/* a[0] = "hello"; */  /* Invalid: arrays cannot be assigned. */

Copy characters into the row instead, ensuring the destination is large enough.

Step by Step Execution

Consider this writable array example:

char words[2][14] = {"blah", "hmm"};
printf("%s\n", words[1]);

Step by step:

  1. words[2][14] reserves space for two rows, each containing 14 char values.
  2. The initializer "blah" is copied into words[0] as {'b', 'l', 'a', 'h', '\0', ...}.
  3. The initializer "hmm" is copied into words[1] as {'h', 'm', 'm', '\0', ...}.
  4. In this context, words[1] is converted to a pointer to its first character: &words[1][0].
  5. %s tells printf to read characters starting at that pointer until it finds \0.
  6. The output is:
hmm

Now compare the pointer version:

const char *words[2] = {"blah", "hmm"};
printf("%s\n", words[1]);
  1. words reserves space for two pointers.
  2. words[1] stores the address of the first character of the literal "hmm".
  3. %s follows that address and prints until \0.

Both forms work with %s, but their memory layout, ownership, and ability to modify text are different.

Real World Use Cases

  • Menu labels and fixed messages: Use const char *labels[] = {"Open", "Save", "Quit"}; for text that does not change.
  • Days, months, or status names: Store a lookup table of fixed strings, such as HTTP method names or log-level labels.
  • Form fields and buffers: Use char fields[10][64]; when a program must store and edit up to 10 user-provided values.
  • Parsing input files: Read fixed-width names or identifiers into a two-dimensional buffer.
  • Command-line arguments: argv is effectively an array of pointers to character sequences, commonly declared as char *argv[] in main.
  • Embedded systems: Fixed-size two-dimensional arrays are useful when dynamic allocation is undesirable and maximum text lengths are known.
Take Quiz

Real Codebase Usage

Developers usually make the ownership rule clear in the type.

Read-only lookup tables

static const char *const error_names[] = {
    "OK",
    "NOT_FOUND",
    "TIMEOUT"
};

The first const means the characters should not be changed. The second const means the pointers in the table should not be reassigned.

Fixed-size writable records

#define MAX_USERS 100
#define NAME_SIZE 32

char user_names[MAX_USERS][NAME_SIZE];

Before copying external input, code validates length so there is room for \0.

#include <stdio.h>
#include <string.h>

int set_name(char destination[32], const char *source) {
    if (source == NULL) {
        return 0;
    }

    if (strlen(source) >= 32) {
        return 0;
    }

    strcpy(destination, source);
    return 1;
}

This uses a guard clause: invalid input returns early, and the copy happens only when it is safe.

Safe output

Always give printf a fixed format string when printing a string:

printf("%s\n", user_names[0]);

This avoids treating data as formatting instructions.

Common Mistakes

Declaring the wrong pointer type

This declaration creates pointers to arrays, not pointers to characters:

char (*a[2])[14];

Use one of these instead:

const char *a[2] = {"blah", "hmm"};
char a[2][14] = {"blah", "hmm"};

Modifying a string literal

This compiles on many systems but has undefined behavior:

char *message = "hello";
message[0] = 'H';

Use writable array storage when modification is needed:

char message[] = "hello";
message[0] = 'H';

Using printf(a[1])

Do not write:

printf(a[1]);

printf expects its first argument to be a format string. If the stored text contains %, printf tries to interpret it as a conversion specification. This can produce incorrect output or undefined behavior.

Write:

printf("%s\n", a[1]);

It may appear to work with "hmm" only because that particular text contains no % characters. It does not prove that the original incompatible assignment is correct.

Forgetting room for the null terminator

This array is too small for "hello" because the string needs six characters including \0:

char word[5] = "hello";  /* Invalid: no room for '\0'. */

Correct:

char word[6] = "hello";

Copying an oversized string into a row

char names[2][5];
strcpy(names[0], "hello");  /* Overflow: needs 6 characters. */

Check the destination capacity before copying, and ensure the result is null-terminated.

Comparisons

RepresentationExample declarationStoresCan change characters?Best for
Array of string pointersconst char *a[2]Two pointersNo, for literalsFixed labels and lookup tables
Two-dimensional char arraychar a[2][14]28 character slotsYesFixed-capacity writable strings
Pointer to an arraychar (*p)[14]One pointer to a 14-char rowDepends on pointed storagePassing or referring to rows of a 2D array

A pointer to an array is useful, but it has a different job:

char rows[2][14] = {"blah", "hmm"};
char (*p)[14] = rows;

printf("%s\n", p[1]);

Here rows converts to a pointer to its first 14-character row, so it correctly matches char (*)[14]. In the original code, a string literal does not have that pointer-to-row type.

Take Quiz

Cheat Sheet

/* Read-only strings: array of pointers */
const char *words[] = {"blah", "hmm"};
printf("%s\n", words[1]);

/* Writable strings: fixed-size rows */
char words[2][14] = {"blah", "hmm"};
words[0][0] = 'B';
printf("%s\n", words[0]);

/* Pointer to one 14-character row */
char (*row_pointer)[14];
  • A C string ends with \0.
  • char text[] = "hi"; creates writable array storage.
  • const char *text = "hi"; points at a string literal; do not modify it.
  • char table[rows][columns] needs one extra column for \0.
  • Use printf("%s", text), not printf(text).
  • Arrays cannot be assigned after declaration; copy into them instead.
  • char * and char (*)[N] are different pointer types.

FAQ

Why is char (*a[2])[14] different from char a[2][14]?

char a[2][14] allocates two arrays of 14 characters. char (*a[2])[14] allocates two pointers, each intended to point to an array of 14 characters.

Should I use char * or const char * for string literals?

Use const char *. A string literal must not be modified, even though older C code often uses char *.

Why does printf(a[1]) seem to work?

It happens to work when the text contains no % characters, but it is unsafe because printf treats the text as a format string. Use printf("%s", a[1]).

Can strings in char a[2][14] have different lengths?

Yes. Each row has capacity for up to 13 visible characters plus \0; unused positions are simply not part of the string.

Can I assign a[0] = "new text" to a two-dimensional array?

No. a[0] is an array, and arrays are not assignable in C. Copy the characters with a size-checked operation instead.

How do I pass a two-dimensional array to a function?

The column size is part of the element type, so include it in the parameter:

void print_words(size_t count, char words[][14]);

What does char (*p)[14] mean?

It means p points to an array of 14 char values. It is commonly used to point at a row in a two-dimensional array.

Take Quiz

Mini Project

Description

Create a small status-message table for a command-line program. The program uses an array of pointers for fixed status labels, prints each label safely, and shows how to select a message by index.

Goal

Build a program that stores and prints several read-only status messages using an array of string pointers.

Requirements

Create an array containing at least four status messages. Print every message with a loop. Use const char * for the message pointers. Print strings with printf and the %s format specifier. Check that a requested message index is within the array bounds.

Take Quiz

Keep learning

Related questions

View more