Array parameters and caller-visible changes
A copied argument can still identify shared storage
The foundation lesson showed that changing an integer parameter does not change the caller's integer variable. The pointer lesson then showed that copying a pointer value can leave two routes to one object. Put those ideas together: a helper can receive a copied pointer value and use it to change an element of the caller's array.
There is no change to C's copied-argument model. What changes is the kind of value being supplied and the object selected by a later write.
By the end of this lesson you will trace an array call with an explicit length, distinguish changed elements from changed helper parameters, and state the assumptions that make a helper's accesses valid. You will also distinguish a helper's return value from its caller-visible writes.
Prerequisites: the accepted scalar function/copy lesson, arrays and valid indices, prefix scans, and pointer target records. We do not yet use pointer arithmetic, sizeof, allocation or pointers to pointers.
C11 is our teaching convention. All real arrays shown have positive constant sizes and initialized int elements. Defined calls use live suitable storage and valid selected lengths. Every arithmetic result fits within −32767 through 32767. Each complete program is independent. The explicitly unsafe practice fragment is for classification only and must not be compiled or executed.
Read the call and the parameter separately
In the caller, int marks[4] = {3, 1, 4, 2}; creates a real four-element array. In the call add_to_prefix(marks, used, 2), the expression marks supplies a pointer to its first element. The array object stays in the caller; the call does not make an element-by-element copy of it.
In the helper's parameter list, int values[] declares a parameter whose type is adjusted to a pointer to int. For the plain parameter forms used here, it can also be written int *values. This parameter form is different from the caller's actual array declaration. The helper receives a pointer value, not a new array whose size can be read from the empty brackets.
values[i] accesses the corresponding element through that pointer. For the calls here, the pointer identifies the first element of the caller's array, and the caller supplies the separate valid length. A pointer value does not carry an automatic count of available elements. We will study array-size expressions next; do not invent a length from the helper's parameter spelling.
The integer arguments still work as before. length receives the current value of the caller's used, and delta receives 2. They are separate helper parameters. The argument expressions here have no side effects, so their evaluation order cannot change the result.
A helper that returns no value
In void add_to_prefix(...), void says that the call supplies no result value. We use it as a statement: add_to_prefix(marks, used, 2);. Its purpose is the writes made by its body, not a returned number to store in a variable.
Reaching the closing brace of this void helper returns control to the caller. The caller continues at the next statement. This is different from the integer helpers that explicitly return a number, and neither kind of return is itself a print operation.
Worked example 1 Change a prefix but not the caller's length
Which of the caller's five printed integers change?
#include <stdio.h>
void add_to_prefix(int values[], int length, int delta)
{
for (int i = 0; i < length; i++) {
values[i] += delta;
}
length = 0;
}
int main(void)
{
int marks[4] = {3, 1, 4, 2};
int used = 3;
add_to_prefix(marks, used, 2);
printf("%d %d %d %d | %d\n",
marks[0], marks[1], marks[2], marks[3], used);
return 0;
}Before the call, draw the caller's real array once: marks = {3, 1, 4, 2} and caller used = 3. On entry to the helper, add separate parameter records: values → marks[0], length = 3, delta = 2. Do not draw a second four-element array for values.
| Helper checkpoint | Caller array marks | Helper length | Caller used |
|---|---|---|---|
| Before first loop test | {3, 1, 4, 2} | 3 | 3 |
| After index 0 body | {5, 1, 4, 2} | 3 | 3 |
| After index 1 body | {5, 3, 4, 2} | 3 | 3 |
| After index 2 body | {5, 3, 6, 2} | 3 | 3 |
| After failed test at index 3 | {5, 3, 6, 2} | 3 | 3 |
After length = 0 | {5, 3, 6, 2} | 0 | 3 |
The loop accesses indices 0, 1 and 2. Each store reaches the corresponding caller element. The fourth element remains 2 because it is outside the selected prefix. There are three bodies and four loop tests.
The final length = 0 changes only the helper's integer parameter. It neither erases the array nor assigns to caller used. After the helper ends, the caller prints 5 3 6 2 | 3, followed by a newline.
The element changes are already writes to the original storage while the helper executes. They do not wait for a special “copy back” operation at return. The helper's own parameter objects finish their lifetime when the call ends; the caller's array remains alive in main and retains its new values.
An unchanged array would incorrectly assume that the helper worked on a private array copy. A last element of 4 would process one element beyond the selected prefix. A caller used of 0 would copy a change back from a separate integer parameter. Each mistake confuses a different object or boundary.
Put the access contract beside the helper
For add_to_prefix, the calls in this lesson require:
- A pointer to the first element of a live initialized integer array
- A
lengthfrom 0 through that array's actual element count - Permission, as part of this helper's intended behavior, to change the selected elements
- Every evaluated addition to remain representable in
int
With length = 0, the first loop test fails and no element is read or written. We still supply the real array; we are not introducing null-array conventions or zero-sized arrays. With length 4 in the worked program, all four elements would be updated. Length 5 would exceed its four-element capacity and is not an allowed call.
The helper cannot infer or check real capacity merely from the pointer parameter and the supplied length. Writing an incorrect length does not enlarge the caller's array. These are explicit preconditions, not checks C silently performs on every call.
Worked example 2 Retarget the local parameter
Now pass two scalar targets. Does assigning a new value to the helper's pointer parameter also retarget the caller's pointer variable?
#include <stdio.h>
void retarget_then_write(int *target, int *other)
{
target = other;
*target = 9;
}
int main(void)
{
int left = 2;
int right = 7;
int *selected = &left;
retarget_then_write(selected, &right);
printf("%d %d %d\n", left, right, *selected);
return 0;
}- Caller before the call:
left = 2,right = 7, andselected → left - Helper entry: its separate
target → leftandother → right. The pointer argument values have been copied into those parameters target = otherchanges the helper'stargetto identifyright. Callerselectedstill identifiesleft; both integers are unchanged*target = 9now writes intoright, making it 9- After return, caller
selected → leftstill reads 2. Output:2 9 2, followed by a newline
There are three pointer variables in the trace: caller selected and helper parameters target and other. Sharing a target does not merge those variables. target = other changes one local pointer value. *target = 9 changes the integer selected by that local pointer value.
The wrong result 2 9 9 lets the helper's pointer reassignment move caller selected. The wrong result 9 7 9 ignores the retargeting before the store. The wrong result 2 7 2 misses the actual store to caller right. Drawing each pointer variable separately resolves all three errors.
We are not teaching how to reassign the caller's pointer variable from a helper here. That needs a different interface. Do not treat an ordinary pointer argument as an automatic reference to the caller's pointer variable.
State what the function returns and what it changes
A function that only sums elements may return an integer without writing the array. Another function may change elements and return nothing. A function can also both modify storage and return a value, but neither behavior follows solely from the name or the presence of brackets in a parameter list. Read the body and the stated contract.
For each call, keep four pieces of evidence: the argument values, the separate helper parameters, the caller objects reached by writes, and any returned value. Use distinct caller and helper state records, exactly as in the scalar function lesson, adding target arrows where needed.
Practice before the solutions
These four original exercises are unscored. Keep call order, target identity and selected length visible. Question 4 deliberately violates a bound and is for classification and repair only.
Practice 1 Read-only calculation followed by a mutation
Give the output and final array. Which helper changes the array, and does the saved return value before change afterward?
#include <stdio.h>
int sum_prefix(int values[], int length)
{
int total = 0;
for (int i = 0; i < length; i++) {
total += values[i];
}
return total;
}
void clear_first(int values[], int length)
{
if (length > 0) {
values[0] = 0;
}
}
int main(void)
{
int a[3] = {3, 4, 6};
int before = sum_prefix(a, 3);
clear_first(a, 2);
int after = sum_prefix(a, 2);
printf("%d %d %d\n", before, after, a[2]);
return 0;
}Use valid selected lengths. “Read-only” here describes what sum_prefix actually does; it is not a new type qualifier.
Practice 2 A local length change affects the helper's own loop
This program is defined for the shown four-element array, but the intended helper contract is to fill exactly the requested prefix. Find the actual output, identify the contract error and repair it by deleting one assignment. The caller's requested must stay unchanged.
#include <stdio.h>
void fill_prefix(int values[], int length, int value)
{
length = 2;
for (int i = 0; i < length; i++) {
values[i] = value;
}
}
int main(void)
{
int a[4] = {2, 4, 6, 8};
int requested = 1;
fill_prefix(a, requested, 9);
printf("%d %d %d %d | %d\n",
a[0], a[1], a[2], a[3], requested);
return 0;
}Practice 3 Swap target values and consider a shared target
Trace both calls. The second call supplies the same target twice. Explain whether it creates a problem in this particular helper, and give the output.
#include <stdio.h>
void swap_values(int *left, int *right)
{
int saved = *left;
*left = *right;
*right = saved;
}
int main(void)
{
int x = 4;
int y = 9;
swap_values(&x, &y);
swap_values(&x, &x);
printf("%d %d\n", x, y);
return 0;
}Both supplied targets are live initialized integers. Do not assume that all pointer-based helpers either require distinct targets or always support aliasing; inspect this one's statements.
Practice 4 The caller supplies too large a length
The following fragment is deliberately unsafe. Assume the shown call occurs inside main and the helper is defined beforehand. Do not compile or execute it.
void add_one(int values[], int length)
{
for (int i = 0; i < length; i++) {
values[i] += 1;
}
}
int a[2] = {3, 7};
add_one(a, 3);Identify the first invalid index that the loop would attempt. Does the helper's values[] parameter create enough elements for the supplied length? Repair only the call so the helper increments the whole real array, then give the repaired output for its two elements.
Full solutions and wrong-turn feedback
Practice 1 solution
The first sum_prefix call receives access to a and length 3. Its local totals are 3, 7 and 13 as indices 0, 1 and 2 are visited. It returns 13 without changing any element; caller before = 13.
clear_first(a, 2) receives a copied pointer value and local length 2. Its condition is true, so it writes 0 to a[0]. It does not clear both selected elements: its body contains only a store at index 0. The caller's array is now {0, 4, 6}.
The second sum uses length 2, so its fresh local total becomes 0 then 4. It returns 4 into after. The saved caller integer before stays 13. Output: 13 4 6, then a newline.
4 4 6 makes a saved return value follow later array changes. 13 10 6 sums the whole array despite the second length being 2. 13 0 6 imagines that clear_first fills the whole prefix, although the body writes only its first element. Function contracts and actual bodies must agree; names alone cannot supply missing operations.
Practice 2 solution
On entry, the helper's length is 1 and caller requested is 1. The local assignment changes helper length to 2. The loop therefore writes 9 at indices 0 and 1. Both indices exist in this four-element array, so this displayed run is defined. The final array is {9, 9, 6, 8} and caller requested remains 1. Output: 9 9 6 8 | 1, then a newline.
The helper has violated the intended selected-prefix contract even though this run stays inside allocated storage. Defined behavior and intended behavior are separate questions. The copied parameter rule does not prevent a helper from using its changed local length in later statements.
Delete length = 2;. The complete repaired program is:
#include <stdio.h>
void fill_prefix(int values[], int length, int value)
{
for (int i = 0; i < length; i++) {
values[i] = value;
}
}
int main(void)
{
int a[4] = {2, 4, 6, 8};
int requested = 1;
fill_prefix(a, requested, 9);
printf("%d %d %d %d | %d\n",
a[0], a[1], a[2], a[3], requested);
return 0;
}The repaired helper keeps length 1, writes only a[0], then stops at the failed test with index 1. It prints 9 4 6 8 | 1, followed by a newline. Setting caller requested to 2 would change what the caller asked for rather than repair the helper. Treating the original assignment as a change to caller requested would wrongly predict a final | 2.
Practice 3 solution
First call: helper left → x, right → y; saved copies x = 4. The store *left = *right makes x = 9, then *right = saved makes y = 4. No pointer parameter is retargeted. After return, caller values are x = 9, y = 4.
Second call: both helper parameters identify x. Its new local saved is 9. The first store reads and writes the same x, leaving 9. The second stores the saved 9 there, again leaving it unchanged. These are separate, sequenced statements, not conflicting unsequenced updates. This specific helper permits the two supplied targets to be the same object.
Output: 9 4, followed by a newline. Predicting 4 9 for the final pair invents another exchange with y, which the second call never references. Predicting an automatic error merely because the pointers share a target overgeneralizes aliasing: safety depends on the operations and the helper's contract.
Practice 4 solution
The caller's array has exactly two elements, at indices 0 and 1. The helper receives access to that array and a separate integer length 3. At index 2, values[2] += 1 would attempt an out-of-bounds element access. The parameter declaration creates no third element. The fragment has undefined behavior; do not assign it a C11-prescribed output or a guaranteed “partly updated” final array.
Use add_one(a, 2);. A complete safe program is:
#include <stdio.h>
void add_one(int values[], int length)
{
for (int i = 0; i < length; i++) {
values[i] += 1;
}
}
int main(void)
{
int a[2] = {3, 7};
add_one(a, 2);
printf("%d %d\n", a[0], a[1]);
return 0;
}The valid call changes 3 to 4 and 7 to 8, then the loop test fails at index 2 without accessing it. Output: 4 8, followed by a newline. Using length 1 would be safe but would not increment the whole real array. Keeping length 3 because it was passed “by value” confuses copying a count with validating it.
Before moving on
Explain how a copied pointer value can allow an element change while a copied length remains separate. Then compare a pointer reassignment with a store through that pointer. Next we will move a pointer within an array and distinguish an actual array's element count from the size of a pointer parameter, without assuming a machine's byte sizes.
Source note
The explanations, code, traces and exercises are original. The first two program designs develop this module's earlier original planning prototypes. C facts were checked against the WG14 N1570 C11 committee draft: array conversion §6.3.2.1p3; parameter adjustment §6.7.6.3p7; copied arguments §6.5.2.2p4; dereference §6.5.3.2p4; indexed access and bounds §§6.5.2.1p2, 6.5.6p8; assignment §6.5.16.1p2; void expressions §6.3.2.2p1 and function completion §6.9.1; automatic lifetime §6.2.4. Reference: https://open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
C11 is a teaching convention, not a claimed GATE language-version requirement. This is a bounded lesson on calls and shared array storage, not full C or GATE CS coverage.
Quick reference
- In the calls here, an array argument supplies a pointer to its first element; the real array is not copied into the helper
- A plain
int values[]parameter is adjusted to a pointer parameter; it is not a new local array - Pass a valid selected length explicitly. A pointer value does not carry an automatic element count
- Parameters receive copied values, including when those values are pointers
- A write to
values[i]or*targetreaches the identified caller object when that object is shared and alive - Reassigning a helper's pointer parameter does not reassign the caller's pointer variable
- Changing a helper's integer length changes its later local decisions, not the caller's length variable
- A void helper supplies no returned result value; its writes can still be visible to the caller
- Distinguish return values, parameter updates and writes to shared objects in every call trace
- State capacity, lifetime, allowed mutation and arithmetic assumptions; neither a parameter name nor a supplied length validates them
Notes for this lesson
Sign in to keep your progress. Sign in