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