What is C Array Size and Decay – C Programming Course For Engineering Students

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

Free C Programming Course | Arrays

C Array Size and Decay

Why array sizes must be constants, what VLAs change, and why callee edits reach the caller.

The answer key at the end of the arrays chapter hides three ideas that confuse almost every beginner: why the array size in C must be a constant, why changes made to an array inside a function show up in the caller, and how the same data can be printed as rows or as columns just by swapping two loops. In this lecture of our free C programming course you will test each idea with a small program and real compiler output. These are core embedded C programming skills, because almost every driver function receives a buffer and a length. This free C tutorial keeps it simple and practical.

What You Will Learn

  • What counts as a constant expression for the array size in C, and what does not
  • How a C99 variable length array behaves, and why embedded standards avoid it
  • Why an array parameter is really a pointer, and what sizeof then returns
  • Why a function can change the caller’s array but not the caller’s integer
  • How swapping the loop order prints a matrix transposed

Prerequisites

You need arrays, functions and basic pointer syntax. If the word “pointer” still feels shaky, do not worry: this lecture explains the one fact you need, which is that an array name turns into the address of its first element when you pass it to a function.

Array Size in C Must Be a Constant Expression

In the original C89 language, the number between the square brackets had to be known when the program is compiled. The compiler reserves a fixed block of memory, so it must know the exact size. A constant expression is something the compiler can calculate without running the program.

Size written asValid as an array size?Reason
6 (a literal)YesPlain constant
BUF_LEN defined with #defineYesThe preprocessor replaces it with a literal
enum constant such as MAX_NODESYesEnumeration constants are integer constant expressions
2 * BUF_LEN + 1YesArithmetic on constants
sizeof(int) * 4Yessizeof is evaluated at compile time
const int size = 4;No (not a true constant in C)A const variable is still a variable in C (C++ treats it differently). At file scope it is an error; inside a function it quietly becomes a VLA
int n read from the userOnly as a local VLA (C99 and later)Value known only at run time

See the Compiler Reject It

This tiny file tries to size a global array with a const int. It is meant to fail.

Program: ex56_err.c

/* ex56_err.c - this file is MEANT to fail to compile */
const int size = 4;
int table[size];

int main(void)
{
    return 0;
}
gcc -std=c11 -o ex56_err ex56_err.c

Compiler output:

ex56_err.c:3:5: error: variably modified 'table' at file scope
    3 | int table[size];
      |     ^~~~~

The phrase “variably modified” is the compiler telling you that the size depends on a variable. Global arrays cannot have a run-time size, so this is an error.

Three Legal Ways to Size an Array

The next program uses a macro, an enum and a variable length array side by side.

Program: ex56b.c

/* ex56b.c - three ways to size an array: macro, enum, and a C99 VLA */
#include <stdio.h>

#define BUF_LEN 6                 /* 1: preprocessor constant              */
enum { MAX_NODES = 4 };           /* 2: enumeration constant               */

static int lookup[BUF_LEN];       /* legal at file scope: constant size    */

static void squares(int n)
{
    int v[n];                     /* 3: C99 variable length array          */

    for (int i = 0; i < n; i++)
        v[i] = i * i;
    printf("VLA with n = %d uses %zu bytes: last element = %d\n",
           n, sizeof v, v[n - 1]);
}

int main(void)
{
    int nodes[MAX_NODES] = { 0 };

    printf("lookup  has %zu elements\n", sizeof lookup / sizeof lookup[0]);
    printf("nodes   has %zu elements\n", sizeof nodes / sizeof nodes[0]);
    squares(5);
    return 0;
}
gcc -std=c11 -Wall -Wextra -o ex56b ex56b.c
./ex56b

Expected output:

lookup  has 6 elements
nodes   has 4 elements
VLA with n = 5 uses 20 bytes: last element = 16

Notice sizeof v prints 20 for n = 5: the size of a VLA is computed while the program runs.

Array Size in C With Variable Length Arrays

C99 added variable length arrays (VLAs), which let a local array take its size from a variable. C11 then made them an optional feature, so a compiler may legally refuse them. In embedded work the usual advice is to avoid them, for good reasons:

  • Unchecked stack growth. A VLA lives on the stack. If the size comes from outside data, a large value can overflow the stack with no warning.
  • Standards say no. The MISRA C guidelines used in automotive and safety-critical firmware forbid variable length array types, and the Linux kernel project removed them from its own code base.
  • Harder analysis. Static analysers and stack-usage tools cannot predict worst-case memory when sizes change at run time.

You can ask GCC to warn about them with -Wvla:

gcc -std=c11 -Wall -Wextra -Wvla -o ex56b ex56b.c
ex56b.c: In function 'squares':
ex56b.c:11:5: warning: ISO C90 forbids variable length array 'v' [-Wvla]
   11 |     int v[n];                     /* 3: C99 variable length array          */
      |     ^~~

If the array size in C has to be decided at run time, use a fixed maximum size with a bounds check, or a static pool, instead.

Array Decay: What the Function Really Receives

When you pass an array to a function, C does not copy the elements. It passes the address of the first element. The parameter declarations int a[] and int *a mean exactly the same thing. This conversion is called array decay. It has three consequences you can see in the next program.

  1. The function can change the caller’s array, because both refer to the same memory.
  2. sizeof on the parameter returns the size of a pointer, not the whole array.
  3. A plain int argument is copied, so changes to it stay local.

Who owns what when double_all(readings, 5) runs

main: int readings[5] holds the real 20 bytes
double_all: int *a holds only the address of readings[0]
a[i] *= 2 writes straight into readings[i]
try_to_change(single) receives a private copy of the integer 7

Program: ex56a.c

/* ex56a.c - arrays decay to pointers: callee edits are visible to the caller */
#include <stdio.h>
#include <stddef.h>

#define ROWS 3
#define COLS 4

/* 'int a[]' and 'int *a' mean exactly the same thing in a parameter list. */
static void double_all(int *a, size_t n)
{
    printf("  inside double_all : sizeof a = %zu (just a pointer), n = %zu\n",
           sizeof a, n);
    for (size_t i = 0; i < n; i++)
        a[i] *= 2;
}

/* a plain int is copied, so this change stays local */
static void try_to_change(int value)
{
    value = 999;
    printf("  inside try_to_change : value = %d\n", value);
}

static void print_transposed(const int m[ROWS][COLS])
{
    for (int c = 0; c < COLS; c++) {          /* outer loop walks columns */
        for (int r = 0; r < ROWS; r++)        /* inner loop walks rows    */
            printf("%3d", m[r][c]);
        printf("\n");
    }
}

int main(void)
{
    int readings[5] = { 1, 2, 3, 4, 5 };
    int single = 7;
    int grid[ROWS][COLS] = {
        { 1, 2, 3, 4 },
        { 5, 6, 7, 8 },
        { 9, 10, 11, 12 }
    };

    printf("in main : sizeof readings = %zu bytes\n", sizeof readings);
    double_all(readings, 5);
    printf("back in main: %d %d %d %d %d\n",
           readings[0], readings[1], readings[2], readings[3], readings[4]);

    try_to_change(single);
    printf("single after try_to_change = %d\n", single);

    printf("grid read column by column:\n");
    print_transposed(grid);
    return 0;
}
gcc -std=c11 -Wall -Wextra -o ex56a ex56a.c
./ex56a

Expected output (the sizeof value assumes a 64-bit machine with 8-byte pointers; on a 32-bit microcontroller you will see 4):

in main : sizeof readings = 20 bytes
  inside double_all : sizeof a = 8 (just a pointer), n = 5
back in main: 2 4 6 8 10
  inside try_to_change : value = 999
single after try_to_change = 7
grid read column by column:
  1  5  9
  2  6 10
  3  7 11
  4  8 12

Why Callee Edits Reach the Caller

The output shows 2 4 6 8 10 in main after double_all ran, while single is still 7 even though try_to_change set its copy to 999. Arrays travel by address, simple values travel by copy. This is the most important rule for working with buffers in firmware: a driver function that fills your receive buffer does not return the data, it writes into your memory.

Protect Read-Only Data With const

If a function should only read the array, say so: void print_all(const int *a, size_t n). The compiler will then reject any write through a. Combine that with a separate length parameter, because the callee has no way to discover the size on its own.

Printing a Matrix Transposed

The same program ends with a short function that prints a 3 x 4 matrix as four lines of three numbers. The only change from normal printing is the loop order: the outer loop walks the columns and the inner loop walks the rows.

Normal printTransposed print
Outer loop: rows. Inner loop: columns.Outer loop: columns. Inner loop: rows.
Each output line is one rowEach output line is one column
Element printed: m[r][c]Element printed: m[r][c] (same expression, different order)
3 lines of 4 numbers4 lines of 3 numbers

The matrix in memory is not changed at all. You only change the order in which you visit it. If you actually need a transposed copy, store m[r][c] into t[c][r] in a second array.

Real-World Uses

  • Driver APIs: a function such as read(buf, len) fills the caller’s buffer. The two parameters, address and length, are exactly what decay forces on you.
  • Static buffers: a compile-time array size lets the linker place the buffer in RAM and report the total memory use before the program ever runs.
  • Image and matrix code: reading column by column is how you process vertical lines of a display buffer.

Common Mistakes and Troubleshooting

  • Using sizeof on a parameter to get the element count. It returns the pointer size. Pass the length separately.
  • Sizing an array with a const int. It works in C++ and fails in C for file-scope arrays. Use a macro or enum.
  • Trusting an external size. Never create a VLA or index an array with a length that arrived over UART, SPI or a network without checking it first.
  • Expecting a copy. If a function must not damage your data, mark the parameter const or copy the array yourself.
  • Thinking pointer size is always 8. It is 4 bytes on most 32-bit microcontrollers.

Array Size in C: Best Practices and Security

  • Treat the array size in C as a design decision. Define every buffer length once, with a macro or enum, and use it for the array, loops and bounds checks.
  • Pair every array parameter with a length parameter, and check the length before writing. Missing length checks are the root of most buffer overflow vulnerabilities.
  • Prefer fixed maximum sizes over VLAs in firmware.
  • Use -Wall -Wextra and, where policy requires it, -Wvla, so the compiler catches these issues early.

Summary and Key Takeaways

  • The array size in C must be a constant expression: a literal, a macro, an enum constant or a sizeof calculation.
  • A const variable is not a constant expression in C, so global arrays cannot use it.
  • VLAs size themselves at run time, but they are optional in modern C and discouraged in embedded code.
  • Arrays decay to pointers when passed to functions, so callee changes are visible and sizeof gives the pointer size.
  • Swapping the two loops prints a matrix as its transpose without changing the data.

With these three ideas clear, you can read function signatures in any driver or library with confidence. The final lecture of this chapter covers the prime sieve, moving negative numbers to the end of an array, and a full quiz with interview questions.

Frequently Asked Questions

Why must the array size in C be constant?

For normal arrays the compiler reserves memory at compile time, so it needs the size then. Only local variable length arrays can use a size known at run time.

Can I use const int to size an array in C?

Not for global or static arrays. A const variable is still a variable in C. Use #define or an enum constant instead.

What is array decay in C?

It is the automatic conversion of an array name into a pointer to its first element, which happens when you pass an array to a function or use it in most expressions.

Why does sizeof give 8 inside the function?

Inside the function the parameter is a pointer, and a pointer is 8 bytes on a typical 64-bit machine. It says nothing about the array size.

Are variable length arrays allowed in embedded C?

Some compilers accept them, but safety standards such as MISRA C forbid them and the risk of stack overflow is real. Use fixed maximum sizes.

How do I pass a 2-D array to a function?

Give every dimension except the first, as in int m[][COLS], or pass a flat pointer with the row and column counts as separate parameters.

Keep Building Your Embedded C Skills

This lecture is part of the EmbeddedPathashala free C programming course, a free C tutorial written for students who want to write solid embedded C programming code. Compile every program yourself, change the data, and break things on purpose.

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *