C / ARRAYS
Passing arrays to functions with an explicit length
Write and call C functions that take an array as a pointer plus a size_t element count, and explain why sizeof inside the function cannot recover the length.
What you will learn
- Pass arrays as a pointer plus a size_t count; the array itself carries no length
- Compute sizeof arr / sizeof arr[0] only where arr is still a real array
- Recognise that int v[], int v[6] and int *v are one and the same parameter type
- Use const int *v when a function only reads, so accidental writes fail to compile
Understanding Passing arrays to functions with an explicit length
When you write sum(a, n), the argument a is not the array. An array expression used as a function argument is converted to a pointer to its first element, so what the function receives is a single address: the six ints are not copied into the call, and no length tag rides along with the address. The count has to be a second parameter because there is nowhere else in C for it to live.
The parameter can be spelled int v[], int v[6] or int *v, and the compiler adjusts all three to int *. That is why the number in the brackets is not a promise: it is discarded, a caller may hand over a three-element array instead, and sizeof v inside the function measures a pointer rather than the caller's storage. The familiar sizeof arr / sizeof arr[0] trick yields the true element count only in the scope where arr is declared as an array, which in practice means the caller computes it and passes it down.
So treat a C array argument as half a value: pointer plus count is the pair that makes it whole. Because the pointer refers to the caller's own storage, the function writes directly into the caller's array, and const int *v is how you state, and have the compiler enforce, that it will only read. The same pair gives you subranges for nothing extra: v + 3 with a count of 2 describes exactly two elements without copying anything.
<stdio.h>
<stddef.h>
/* The array argument arrives as a pointer, so the count must come with it. */
long sum(const int *values, size_t n)
{
long total = 0;
for (size_t i = 0; i < n; i++)
total += values[i];
return total;
}
/* Spelling the parameter int values[6] changes nothing: it is still int *. */
void report_size(int values[6])
{
printf("inside report_size: sizeof values == sizeof(int *) -> %d\n",
sizeof values == sizeof(int *));
}
int main(void)
{
int a[] = {3, 1, 4, 1, 5, 9};
int b[] = {10, 20};
size_t na = sizeof a / sizeof a[0];
size_t nb = sizeof b / sizeof b[0];
printf("main sees %zu elements in a\n", na);
printf("sum of a: %ld\n", sum(a, na));
printf("sum of b: %ld\n", sum(b, nb));
printf("sum of the first 3 of a: %ld\n", sum(a, 3));
report_size(a);
return 0;
}
An array argument becomes a bare pointer to its first element, so the number of elements must be passed as a separate parameter.
Worked examples
The callee writes into the caller's array
Shows that no copy is made and that a smaller count, or an offset pointer, names part of the array.
<stdio.h>
<stddef.h>
void scale(int *v, size_t n, int factor)
{
for (size_t i = 0; i < n; i++)
v[i] *= factor;
}
void print_ints(const int *v, size_t n)
{
for (size_t i = 0; i < n; i++)
printf("%d%s", v[i], i + 1 < n ? " " : "\n");
}
int main(void)
{
int data[] = {1, 2, 3, 4, 5};
size_t n = sizeof data / sizeof data[0];
scale(data, n, 10);
print_ints(data, n);
scale(data, 2, -1); /* only the first two elements */
print_ints(data, n);
scale(data + 3, 2, 0); /* the last two, as an independent slice */
print_ints(data, n);
return 0;
}
Example explained
Line 1scale receives v and n; nothing inside v records that main's array holds five ints.
Line 2v[i] *= factor writes through the pointer into main's storage, which is why print_ints sees the change.
Line 3scale(data, 2, -1) passes a smaller count, legally restricting the function to elements 0 and 1.
Line 4scale(data + 3, 2, 0) passes the address of element 3 with count 2, describing the last two elements only.
Count first, using the C99 array parameter form
Uses void f(size_t n, const double v[n]) to document the relationship between the count and the array.
<stdio.h>
<stddef.h>
/* n is declared before v so it can be named inside the brackets. */
double average(size_t n, const double v[n])
{
double total = 0.0;
for (size_t i = 0; i < n; i++)
total += v[i];
return n ? total / (double)n : 0.0;
}
int main(void)
{
double temps[] = {19.5, 21.0, 22.5, 20.0};
size_t n = sizeof temps / sizeof temps[0];
printf("average of %zu readings: %.2f\n", n, average(n, temps));
printf("average of the first two: %.2f\n", average(2, temps));
printf("average of an empty range: %.2f\n", average(0, temps));
return 0;
}
Example explained
Line 1const double v[n] is still adjusted to const double *, so the [n] informs readers and analysers, not the compiler's type check.
Line 2Putting n before v is required for that syntax, because n must already be in scope when the brackets are parsed.
Line 3average(2, temps) works on a prefix: the count, not the array, decides how far the loop walks.
Line 4average(0, temps) is safe because the loop body never executes, and the n ? test avoids dividing by zero.
Important notes
sizeof applied to an array parameter never sees the caller's array; gcc and clang warn about it under -Wall (-Wsizeof-array-argument), which is one more reason to compile with warnings enabled.
The count is a promise the compiler cannot verify, so a wrong n is not a diagnosed error but undefined behaviour at run time.
Common mistakes
Recomputing sizeof v / sizeof v[0] inside the receiving function: with 8-byte pointers and 4-byte int that is 2, so the loop quietly processes two elements and ignores the rest.
Passing the byte size instead of the element count, as in sum(a, sizeof a): the loop runs four times too far on a typical machine and reads memory past the array.
Believing that void f(int v[6]) restricts callers to six-element arrays: the bound is discarded, f(smaller_array) compiles without complaint, and indexing element 5 then reads out of bounds.
Try it yourself
Change, predict, then run
Write int count_above(const int *v, size_t n, int limit) that returns how many of the first n elements are greater than limit. Call it once with a 7-element array and sizeof arr / sizeof arr[0], then again with n = 3, and print both counts to confirm the caller's count is what controls the range.
Open the C workspaceCheck your understanding
On a machine with 8-byte pointers and 4-byte int, a function declared void f(int v[10]) is called with a genuine 10-element array. What does sizeof v / sizeof v[0] evaluate to inside f?
- 2, because v has type int * there, so the division is pointer size over element size
- 10, because the parameter declaration fixes the length at ten elements
- 40, because sizeof v is the total byte size of the caller's array
- Nothing: it is a compile error, since sizeof cannot be applied to a parameter
Show answer
The parameter int v[10] is adjusted to int *, so sizeof v is 8 and sizeof v[0] is 4, giving 2. Option 1 is tempting because the [10] looks like real type information, but the compiler discards that bound for parameters and does not check the argument's length either, which is precisely why the count must be passed explicitly.