Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Array Parameters Are Pointers
Discover why sizeof on an array parameter in C returns the size of a pointer, how to detect the bug with compiler warnings, and how embedded C programming code stays safe by passing the length explicitly.
In This Lecture
What You Will Learn
In the previous lecture of this free C programming course we saw that a function receives only a pointer when you pass it an array. This lecture proves that claim with measurements and shows the consequence: the sizeof array parameter in C trap. If you want to write reliable embedded C programming code for sensors, UART drivers and bootloaders, you need to know exactly where array sizes survive and where they disappear.
- How
sizeofbehaves for a real array versus an array-looking parameter. - Why the answer inside the function is the pointer size, whatever size you write in the brackets.
- What older textbooks mean when they show a pointer size of 2 bytes, and what you will see on modern machines and microcontrollers.
- How to read the GCC warning that catches this bug before it ships.
- The standard
ARRAY_LENmacro, and where it may and may not be used.
Prerequisites
Finish the lecture on passing arrays to functions first. You should also know the fixed-width types from stdint.h such as uint8_t and uint16_t, and how sizeof works on variables and types.
The sizeof Array Parameter in C Trap
Look at this idea. In main() you declare an array of eight bytes. sizeof on that array gives 8, because the compiler knows the full array type. Now you pass the array to a function whose parameter is written as const uint8_t buf[8]. It certainly looks like the function receives eight bytes. It does not. The parameter is a pointer, so sizeof(buf) inside the function is the size of a pointer, not eight.
The same holds for any element type. An array of eight uint16_t values has sizeof equal to 16 in the caller, and an array of eight float values has 32. Inside a function that takes them as parameters, all three sizeof results collapse to the same number, because every data pointer on a given machine has the same size.
Same Name, Two Different Types
Pointer Size on Different Targets
| Platform | Typical pointer size | Where you meet it |
|---|---|---|
| Old 16-bit DOS compilers | 2 bytes | Classic textbooks, which print 2 for pointer sizes |
| 32-bit ARM Cortex-M, RISC-V RV32 | 4 bytes | STM32, nRF, ESP32-C3 style microcontrollers |
| 64-bit Linux on x86-64 or AArch64 | 8 bytes | Your laptop, Raspberry Pi with a 64-bit OS |
If you read an older C book and see the number 2 for pointer size, that reflects the compiler of its time. Never memorise the number. Remember the rule: it is the size of an address on the machine you build for, and it does not depend on what the pointer points to.
Build and Run the Experiment
The program below measures the same three arrays in both places. It also contains the correct approach, a checksum function that receives a pointer and a length. Save it as ep_sizeof_trap.c.
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
/* Looks like it receives an array. It does not. */
static void wrong_way(const uint8_t b[8], const uint16_t w[8], const float f[8])
{
printf("inside wrong_way : sizeof(b)=%zu sizeof(w)=%zu sizeof(f)=%zu\n",
sizeof(b), sizeof(w), sizeof(f));
}
/* The honest version: pointer + length. */
static uint8_t checksum8(const uint8_t *buf, size_t len)
{
unsigned sum = 0;
for (size_t i = 0; i < len; i++) {
sum += buf[i];
}
return (uint8_t)(sum & 0xFFu);
}
int main(void)
{
uint8_t rx[8] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80};
uint16_t adc[8] = {100, 200, 300, 400, 500, 600, 700, 800};
float temp[8] = {20.5f, 21.0f, 21.5f, 22.0f, 22.5f, 23.0f, 23.5f, 24.0f};
printf("inside main : sizeof(rx)=%zu sizeof(adc)=%zu sizeof(temp)=%zu\n",
sizeof(rx), sizeof(adc), sizeof(temp));
printf("inside main : element counts = %zu, %zu, %zu\n",
ARRAY_LEN(rx), ARRAY_LEN(adc), ARRAY_LEN(temp));
wrong_way(rx, adc, temp);
printf("checksum8 over %zu bytes = 0x%02X\n", ARRAY_LEN(rx),
checksum8(rx, ARRAY_LEN(rx)));
return 0;
}
gcc -std=c11 -Wall -Wextra -o ep_sizeof_trap ep_sizeof_trap.c
./ep_sizeof_trap
Expected output on a 64-bit Linux machine:
inside main : sizeof(rx)=8 sizeof(adc)=16 sizeof(temp)=32
inside main : element counts = 8, 8, 8
inside wrong_way : sizeof(b)=8 sizeof(w)=8 sizeof(f)=8
checksum8 over 8 bytes = 0x40
On a 32-bit microcontroller target the third line would show 4 for each value. The first two lines are identical on every platform, because those sizes come from the array types.
What the Compiler Tells You
During compilation GCC prints a warning for each of the three parameters. The message has this shape:
warning: 'sizeof' on array function parameter 'b' will return size of
'const uint8_t *' [-Wsizeof-array-argument]
This is the compiler telling you, in plain words, that your array parameter is really a pointer. Treat that warning as an error in your build flags for firmware projects. It is one of the few warnings that always indicates a genuine logic bug.
Checking the Checksum by Hand
The bytes are 0x10 through 0x80 in steps of 0x10. Their sum is 0x240. Keeping only the low eight bits gives 0x40, which matches the program output. Working such a result out by hand is a good habit when you test parsing code.
The Safe Pattern: Pointer Plus Length
Since the callee cannot recover the size, the caller must supply it. The convention in nearly every C API, from the C library to vendor HAL drivers, is a pointer followed by a length. The caller computes the length where the array type is still visible, using the standard element-count macro:
#define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
This macro divides the total bytes by the bytes of one element. It is correct only where a is a genuine array. If you apply it to a pointer parameter it silently returns the wrong number: on a 64-bit machine with a uint8_t * it gives 8, and with an int * it gives 2. Neither number has anything to do with the real buffer.
Where ARRAY_LEN Works
Documenting the Minimum Size With static
C99 gives you a way to state the expected minimum length in a parameter: const uint8_t buf[static 8] means the caller promises at least eight accessible elements. It does not change the fact that the parameter is a pointer, but compilers such as GCC and Clang can use it to warn when a caller passes a visibly smaller array. Use it for fixed-size records like a MAC address or a six-byte header.
Real-World Impact in Firmware
- Silent truncation: a helper that loops to
sizeof(buf)processes only 4 or 8 bytes of a large frame, and the rest of the data is ignored, which can look like a flaky communication link. - Buffer overrun: a copy loop that trusts a wrong length can write past the end of a stack buffer, which on a microcontroller usually corrupts a neighbouring variable instead of crashing immediately, making it very hard to debug.
- Code review smell: any
sizeofof a parameter name deserves an immediate second look.
Common Mistakes and Troubleshooting
| Mistake | What happens | Fix |
|---|---|---|
Using sizeof(param) as a length | Returns pointer size | Pass the length as an argument |
Using ARRAY_LEN on a parameter | Wrong count without any error | Compute the count in the caller |
Ignoring -Wsizeof-array-argument | Bug ships to hardware | Build with -Wall -Werror for firmware |
| Assuming pointer size is 2 or 4 everywhere | Code breaks when ported | Never hard-code it; use sizeof(void *) if you need it |
Best Practices, Performance and Security
- Use the
(pointer, length)pair as your default function signature for buffers. - Use
size_tfor the length and print it with%zu. - Compute lengths once, close to where the array is defined, and keep them in a named constant or macro.
- Check lengths before copying. In networked or serial firmware, a length that came from outside must be validated against the buffer capacity, because mishandled lengths are the classic source of memory-safety vulnerabilities.
- Performance note: passing a pointer and a length costs two machine words regardless of buffer size, so there is nothing to optimise away.
Key Takeaways
sizeofof a real array gives the whole array;sizeofof an array parameter gives one pointer.- The number in the brackets of a parameter is ignored by the compiler.
- Always send the element count along with the pointer.
- Treat
-Wsizeof-array-argumentas a bug report, not noise.
Conclusion
Understanding the sizeof array parameter in C behaviour is a rite of passage. Once you have seen the numbers come out identical for a byte array, a halfword array and a float array, you will never confuse a parameter with an array again. Write your interfaces as pointer plus length, count elements where the array is visible, and let the compiler warnings guard the rest. In the next lecture we move from one dimension to two and see what a function receives when you hand it a matrix.
Suggested Images for This Lecture
- Side-by-side graphic of sizeof in main and in the callee. Alt text: “sizeof array parameter in C compared between caller and callee in a free C programming course”.
- Terminal screenshot with the -Wsizeof-array-argument warning. Alt text: “gcc warning for sizeof array parameter in C embedded C programming”.
- Table of pointer sizes on 16-bit, 32-bit and 64-bit targets. Alt text: “pointer size on Cortex-M versus x86-64 for C programming for embedded systems”.
Frequently Asked Questions
Why does sizeof give the pointer size for my array parameter?
Because the parameter is a pointer. The compiler rewrites an array-style parameter into a pointer at the function boundary, so the array type is gone.
Does the number inside the brackets of a parameter do anything?
For a one-dimensional parameter it has no effect on the type. Only the C99 static form gives the compiler a minimum size hint to check callers against.
How do I get the number of elements inside a function?
You cannot compute it from the pointer. Pass it as an additional argument from the caller, which still sees the real array.
Is the ARRAY_LEN macro safe to use?
It is safe on real arrays, and wrong on pointers. Use it only in the scope where the array is declared, never on a function parameter.
Why did my textbook print 2 for pointer size?
It was written for a 16-bit compiler. On 32-bit microcontrollers you will see 4 and on 64-bit Linux you will see 8.
Can a struct hold the array and its length together?
Yes. A struct containing a pointer and a count is a common way to bundle a buffer description, and many embedded libraries use exactly that pattern.
Continue the Free C Tutorial
Next up: how a function receives a two-dimensional array and why the column count must be written down. Keep going with our free embedded C course.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments