C Language Common pitfalls Misunderstanding array decay


Example

A common problem in code that uses multidimensional arrays, arrays of pointers, etc. is the fact that Type** and Type[M][N] are fundamentally different types:

#include <stdio.h>

void print_strings(char **strings, size_t n)
{
    size_t i;
    for (i = 0; i < n; i++)
        puts(strings[i]);
}

int main(void)
{
    char s[4][20] = {"Example 1", "Example 2", "Example 3", "Example 4"};
    print_strings(s, 4);
    return 0;
}

Sample compiler output:

file1.c: In function 'main':
file1.c:13:23: error: passing argument 1 of 'print_strings' from incompatible pointer type [-Wincompatible-pointer-types]
         print_strings(strings, 4);
                       ^
file1.c:3:10: note: expected 'char **' but argument is of type 'char (*)[20]'
     void print_strings(char **strings, size_t n)

The error states that the s array in the main function is passed to the function print_strings, which expects a different pointer type than it received. It also includes a note expressing the type that is expected by print_strings and the type that was passed to it from main.

The problem is due to something called array decay. What happens when s with its type char[4][20] (array of 4 arrays of 20 chars) is passed to the function is it turns into a pointer to its first element as if you had written &s[0], which has the type char (*)[20] (pointer to 1 array of 20 chars). This occurs for any array, including an array of pointers, an array of arrays of arrays (3-D arrays), and an array of pointers to an array. Below is a table illustrating what happens when an array decays. Changes in the type description are highlighted to illustrate what happens:

Before Decay After Decay 
char [20]array of (20 chars)char *pointer to (1 char)
char [4][20]array of (4 arrays of 20 chars)char (*)[20]pointer to (1 array of 20 chars)
char *[4]array of (4 pointers to 1 char)char **pointer to (1 pointer to 1 char)
char [3][4][20]array of (3 arrays of 4 arrays of 20 chars)char (*)[4][20]pointer to (1 array of 4 arrays of 20 chars)
char (*[4])[20]array of (4 pointers to 1 array of 20 chars)char (**)[20]pointer to (1 pointer to 1 array of 20 chars)

If an array can decay to a pointer, then it can be said that a pointer may be considered an array of at least 1 element. An exception to this is a null pointer, which points to nothing and is consequently not an array.

Array decay only happens once. If an array has decayed to a pointer, it is now a pointer, not an array. Even if you have a pointer to an array, remember that the pointer might be considered an array of at least one element, so array decay has already occurred.

In other words, a pointer to an array (char (*)[20]) will never become a pointer to a pointer (char **). To fix the print_strings function, simply make it receive the correct type:

void print_strings(char (*strings)[20], size_t n)
/* OR */
void print_strings(char strings[][20], size_t n)

A problem arises when you want the print_strings function to be generic for any array of chars: what if there are 30 chars instead of 20? Or 50? The answer is to add another parameter before the array parameter:

#include <stdio.h>

/*
 * Note the rearranged parameters and the change in the parameter name
 * from the previous definitions:
 *      n (number of strings)
 *   => scount (string count)
 *
 * Of course, you could also use one of the following highly recommended forms
 * for the `strings` parameter instead:
 *
 *    char strings[scount][ccount]
 *    char strings[][ccount]
 */
void print_strings(size_t scount, size_t ccount, char (*strings)[ccount])
{
    size_t i;
    for (i = 0; i < scount; i++)
        puts(strings[i]);
}

int main(void)
{
    char s[4][20] = {"Example 1", "Example 2", "Example 3", "Example 4"};
    print_strings(4, 20, s);
    return 0;
}

Compiling it produces no errors and results in the expected output:

Example 1
Example 2
Example 3
Example 4