I’ll be blunt: If you want to pass an array to a function, or have a function return an array, just give up now and use pointers. It’s a far easier operation, and it would save me the bother of having to write this post to further discuss the topic of array decay.
Picking up from last week’s Lesson, array decay can be most notable when working with a function. In the following code, an array is passed to a function, then output as an array within the function:
2025_09_13-Lesson-a.c
#include <stdio.h> void output( int a[] ) { for( int x=0; x<4; x++ ) printf("%d\n",a[x]); } int main() { int array[] = { 10, 20, 30, 40 }; output(array); return 0; }
From the main() function, array[]
is passed to function output(). This function declares the array as its sole argument, a[]
. Array notation is used within the function to output each element’s value:
10
20
30
40
Here is the same code, but using pointer notation in function output():
2025_09_13-Lesson-b.c
#include <stdio.h> void output( int a[] ) { for( int x=0; x<4; x++ ) printf("%d\n",*(a+x) ); } int main() { int array[] = { 10, 20, 30, 40 }; output(array); return 0; }
The printf() statement uses pointer notation to output array a[]
‘s elements. This switch works because the array is passed by name as an address. In fact, you could declare the output() function’s argument as int *a
and the code still works — though you can’t use array notation in the function when this switch is made.
One of the more technical aspects of array decay is that is removes the code’s capability to determine the array’s size. Consider this code:
2025_09_13-Lesson-c.c
#include <stdio.h> char *title(void) { static char t[] = "Fun Array Stuff"; printf("'%s' contains %zu bytes\n", t, sizeof(t) ); return(t); } int main() { printf("'%s' contains %zu bytes\n", title(), sizeof( title() ) ); return 0; }
Here is the output:
'Fun Array Stuff' contains 16 bytes
'Fun Array Stuff' contains 8 bytes
In the title() function, array t[]
must be declared static or its data is lost and cannot be returned. Within the function, the sizeof operator reports the number of bytes that the string occupies in storage.
Back in the main() function, title() returns the string — its address, as the function is a char pointer. But now, the sizeof operator reports the number of bytes that the address (the pointer) occupies in memory. The string’s size is lost due to array decay. (Yes, the strlen() function still works, but array decay has transformed the data.)
Some might say that such an issue isn’t a problem. Even so, it may come as a surprise when you expect to return an array from a function but it’s form is lost. I don’t find array decay an issue in my code because I allocate storage and use pointers when I can. But one area where it might be a problem is with an array of structures. I can’t think of a good example to mess with at this point, though it seems like an interesting problem.
Nice summary of array decay in C!
I noticed a single error that has crept in though: «In fact, you could declare the output() function’s argument as int *a and the code still works — though you can’t use array notation in the function when this switch is made.»
This assertion is wrong—from the compilers point of view, there is no difference between the following two variants of the
output()
function (in both cases, array as well as pointer-based access will work):⒈ void output (int a []) { …
a [i]
/*(a + i)
… }⒉ void output (int *a) { …
a [i]
/*(a + i)
… }As the function only receives a pointer to the beginning of the passed data in either case, itʼs as if the compiler rewrote
(int a [])
to(int *a)
… I like to think of all of this like follows:If one is faced with the choice of writing either ⒈ or ⒉, itʼs best to think of future reader of this code—if one wants to communicate that an array of things is being passed, then notation ⒈ is the preferable choice, if one wants to emphasize that a pointer to a (single) object is being passed, then variant ⒉ is preferable⁽¹⁾… i.e.,
void output (int a []) { /* ah, we got an array of int */ }
void output (int *a) { /* just a (pointer to a) single int */ }
«One of the more technical aspects of array decay is that is removes the code’s capability to determine the array’s size.»
True, thatʼs a nuisance. Maybe a post on the topic of “dope vectors” could be of interest with regards to this problem?
⁽¹⁾ If one has a C99-compatible compiler at hand, itʼs possible to use VLA syntax to convey even more information to the reader:
With regards to case ⒈:
(int a []) { /* no information on the number of elements */ }
(int a [static 10]) { /*
a
contains (at least) 10 elements */ }(size_t n, int a [static n]) { /*
a
contains (at least) n elements */ }With regards to case ⒉:
(int *a) { /*
a
may or may not be a NULL pointer */ }(int a [static 1]) { /*
a
is supposed to be non-NULL */ }With the caveat that none of these assertions can be verified for dynamically allocated arrays—i.e. the compiler only checks the size of the passed arrays at compile time (none of the above results in runtime checks).
Also, Microsoftʼs Visual C++ compiler doesnʼt currently support this syntax. If compatibility is key, this may further reduce the usefulness of such
[static …]
assertions.Wow. Outstanding feedback. Again, I thank you for sharing your knowledge. Your comments are appreciated!
I am glad you like my small additions—Iʼm happy if I can contribute something of value (and possibly stir some conversations).
One other thing, if I may: after rereading the above, something else occurred to me… in a follow-up it might also be interesting to examine all the cases in which arrays do not decay to pointers. (Although I guess this may already haven been the topic of one of your blog posts, even I didn’t find anything on it during a quick search.)