Arrays Part 1: Declaration, Initialization, Decay
This chapter introduces arrays, their initialization, and the single most important — and most misunderstood — behavior in C: array-to-pointer decay.
Why This Matters
Arrays are the fundamental contiguous storage in C. Decay is the rule that makes arrays and pointers seem interchangeable when they are not. Understanding decay — and its exceptions — prevents a whole class of sizeof, parameter, and return-value bugs.
Prerequisites
c.core.19— pointer basics.c.core.20— pointer arithmetic.
Core Concept
Arrays are contiguous objects
An array is a contiguous sequence of objects of the same type. Its size is fixed at declaration (for non-VLA arrays) and is part of its type.
int arr[5]; /* five consecutive int objects */
arr[0] through arr[4] are the elements. sizeof(arr) is 5 * sizeof(int).
Array-to-pointer decay
In most contexts, an expression of array type is converted ("decays") to a pointer to its first element. The result is int * pointing to arr[0], not a pointer to the array.
int arr[5];
int *p = arr; /* arr decays to &arr[0] */
This is why arrays and pointers often appear interchangeable in function arguments and loops.
Syntax
Declaration and initialization
int a[5]; /* uninitialized (automatic) or zeroed (static) */
int b[5] = {1, 2, 3}; /* remaining elements are 0 */
int c[] = {1, 2, 3, 4, 5}; /* size inferred: 5 */
int d[5] = {0}; /* all elements 0 */
Designated initializers (C99)
int e[5] = { [2] = 7, [4] = 9 }; /* e[2]=7, e[4]=9, others 0 */
Indexing
arr[i] /* element i (0-based) */
*(arr + i) /* equivalent, by definition */
Array subscripting is defined in terms of pointer arithmetic: arr[i] is *((arr) + (i)).
Examples
sizeof vs. decay
#include <stdio.h>
int main(void)
{
int arr[10];
printf("%zu\n", sizeof(arr)); /* 40 (10 * 4) */
printf("%zu\n", sizeof(&arr[0])); /* 8 (pointer size) */
return 0;
}
Expected output (typical LP64): 40 then 8.
Array as a function parameter decays
#include <stdio.h>
void print_size(int a[10]) /* parameter is really int *a */
{
printf("%zu\n", sizeof(a)); /* size of a POINTER, not the array */
}
int main(void)
{
int arr[10];
print_size(arr);
return 0;
}
Expected output: 8 (pointer size), not 40.
The exceptions to decay
Decay does not happen when the array is:
1. the operand of sizeof; 2. the operand of & (address-of) — &arr is int (*)[10], a pointer to the whole array; 3. a string literal used to initialize a character array.
int arr[10];
sizeof(arr); /* whole array */
&arr; /* int (*)[10] */
How It Works
At the type level, arr has type int[10]. In an expression where a value is needed, the compiler converts it to &arr[0] (type int *). The original array object still exists and has its full size; only the *expression* changes type. This is why sizeof (which does not evaluate its operand and which looks at the *type*, not the value) sees the full array.
Variations
Arrays of any type
Arrays can hold any object type, including structs, pointers, and other arrays (multidimensional arrays, c.core.22).
String literals
A string literal "abc" is an array of char with static storage. It decays to char * in expressions, but you must not modify it (it is typically in read-only memory, and modifying it is UB).
Common Mistakes
- Using
sizeofon an array parameter and expecting the array size. - Returning a local array from a function (returning the decayed pointer to a
dead array).
- Assuming
&arrandarrare the same type (they are not:int (*)[N]vs.
int *).
- Modifying a string literal.
Undefined Behavior
- Indexing out of bounds (
arr[i]whereiis outside[0, N-1]).
VERIFIED
- Modifying a string literal.
VERIFIED - Returning a pointer to a local array and dereferencing it (dangling).
VERIFIED
Portability
- Array sizes must be positive constant expressions for fixed arrays.
- Designated initializers are C99 and later.
- The exact size of pointer and int affect the
sizeofexamples but not the
decay rules themselves.
Under the Hood
An array is a contiguous block of memory. Decay is a compile-time type change that produces an address; it generates no code. Indexing compiles to address arithmetic with a scaled offset.
Practical Usage
- Use arrays for fixed-size contiguous storage.
- Pass arrays to functions with an explicit length parameter, because the
pointer alone carries no length.
- Use
sizeof(arr)/sizeof(arr[0])only whenarris a true array, not a
pointer.
Exercises
1. Write a program that prints sizeof of a local array, a pointer to it, and an array parameter, and explain the differences. 2. Initialize an array with designated initializers and print it. 3. Demonstrate that arr[i] == *(arr + i) by printing both. 4. Explain why &arr + 1 differs from arr + 1 (type and value).
Deep Challenge
Write a function that accepts a pointer to a whole array (int (*a)[10]) and prints sizeof(*a), then contrast it with a function that accepts int *a. Explain the exact type difference and when you would use the pointer-to-array form.
Related Concepts
c.core.22— multidimensional arrays, VLA, pointer-to-array.c.core.20— pointer arithmetic.c.core.23— strings.c.struct.fam— flexible array members.
References
- ISO/IEC 9899:2018 §6.2.5 (array types), §6.3.2.1 (lvalues and array
conversion), §6.5.3.4 (sizeof), §6.7.9 (initialization).
Verification
- Decay and its three exceptions are standard.
VERIFIED - Array parameters decay to pointers.
VERIFIED arr[i]is defined as*(arr + i).VERIFIED- Out-of-bounds indexing is UB.
VERIFIED - No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] Array declaration and initialization
- [ ] Array-to-pointer decay
- [ ] Decay exceptions (sizeof, &, string literal init)
- [ ] Array parameters as pointers
- [ ] Out-of-bounds indexing UB
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.arr.decl | 0 | 5 |
| c.arr.decay | 0 | 6 |