C / POINTERS
Out-parameters: returning results through pointers
Design and call C functions that hand back extra results by writing through caller-supplied pointers, with a clear rule about when they write.
What you will learn
- Pass &var so a function can store its result in storage the caller already owns
- Use the return value for success or failure and out-parameters for the data
- Write through *out only after the work succeeded, so a failed call changes nothing
- Treat a NULL out-pointer as the caller saying: I do not need this result
Understanding Out-parameters: returning results through pointers
A return statement carries exactly one value out of a C function, and functions often have more than one thing to say: a quotient and a remainder, or a parsed number plus whether the text was a number at all. An out-parameter moves the extra results onto a second channel. The caller declares the variable, passes its address, and the function assigns through that address with *quot = a / b. Because the storage belongs to the caller's frame and the function holds only a copy of the address, the assignment is still there after the function returns.
Read int *quot in a prototype as a delivery address, not a box. The function does not own that int, cannot resize it, and must not assume anything about the value sitting in it on arrival; the only thing it may do is store there. That also fixes the caller's duty: the address must point at real, still-living storage. Writing int q; divide(47, 5, &q, &r) is correct, while declaring int *q; and passing q without ever pointing it at anything hands the function a garbage address and the store lands somewhere unrelated.
Since the return channel is now free, spend it on status. The most useful convention is all-or-nothing: compute into locals and write through the out-pointers only once you know you will succeed, so a rejected call leaves the caller's variables exactly as they were and the return value becomes the single thing worth checking. Nothing in the type system marks a pointer as an output, so the signature has to say it for you: const on the parameters you only read, plain pointers on the ones you write, and parameter names that state the direction.
Consistency across an API matters more than the individual choice: once callers learn that your functions never touch their variables on failure, they stop writing defensive resets.
<stdio.h>
/* Returns 1 on success, 0 if b is zero.
On success it writes the quotient and the remainder into the caller's ints. */
int divide(int a, int b, int *quot, int *rem)
{
if (b == 0)
return 0; /* nothing written: the caller's ints stay as they were */
*quot = a / b;
*rem = a % b;
return 1;
}
int main(void)
{
int q = 0, r = 0;
if (divide(47, 5, &q, &r))
printf("47 / 5 = %d remainder %d\n", q, r);
if (!divide(47, 0, &q, &r))
printf("divide by zero refused; q and r unchanged: %d and %d\n", q, r);
return 0;
}
An out-parameter is storage the caller owns and lends out by address, letting one function hand back more results than a single return value can carry.
Worked examples
Optional results with NULL
Two results from one call, either of which the caller can decline by passing NULL.
<stdio.h>
/* lo and hi are optional: pass NULL for a result you do not want. */
void span(const int *v, int n, int *lo, int *hi)
{
int min = v[0], max = v[0];
for (int i = 1; i < n; i++) {
if (v[i] < min) min = v[i];
if (v[i] > max) max = v[i];
}
if (lo != NULL) *lo = min;
if (hi != NULL) *hi = max;
}
int main(void)
{
int data[5] = { 12, -4, 7, 30, 5 };
int lo = 0, hi = 0;
span(data, 5, &lo, &hi);
printf("lo=%d hi=%d\n", lo, hi);
hi = 999;
span(data, 5, NULL, &hi);
printf("hi only: %d\n", hi);
return 0;
}
Example explained
Line 1span has two out-pointers, so a single call can report two separate answers.
Line 2if (lo != NULL) *lo = min; makes the minimum optional, with NULL meaning the caller is not interested.
Line 3The second call passes NULL for lo, so only hi is written and 999 becomes 30.
Line 4v is const int * while lo and hi are plain int *, so the prototype alone shows which side is input and which is output.
Status in the return, value in the out-parameter
A parser that reports success through its return value and delivers the number through a pointer, never writing on failure.
<stdio.h>
<ctype.h>
/* The return value says whether the text was a number;
the number itself leaves through out. */
int parse_uint(const char *s, unsigned *out)
{
unsigned n = 0;
if (s[0] == '\0')
return 0;
for (int i = 0; s[i] != '\0'; i++) {
if (!isdigit((unsigned char)s[i]))
return 0;
n = n * 10 + (unsigned)(s[i] - '0');
}
*out = n; /* written only once the whole string checked out */
return 1;
}
int main(void)
{
const char *inputs[3] = { "4096", "12x", "" };
unsigned value = 0;
for (int i = 0; i < 3; i++) {
if (parse_uint(inputs[i], &value))
printf("[%s] -> %u\n", inputs[i], value);
else
printf("[%s] -> rejected, value still %u\n", inputs[i], value);
}
return 0;
}
Example explained
Line 1The return value reports only yes or no, which leaves the unsigned answer to travel through out.
Line 2n is built up in a local and *out is assigned once, after the last character has been validated.
Line 3[12x] is rejected after two digits were already accepted, yet value still reads 4096, so nothing partial escaped to the caller.
Line 4The empty string is rejected before out is touched at all, which is why value is initialised in main before the loop.
Important notes
The compiler cannot tell an out-pointer from an in-pointer, so divide(47, 5, &r, &q) compiles cleanly and silently swaps your two results; argument order and naming are the only protection.
Out-parameters are for extra results, not a replacement for returning values: a function whose only job is to produce one int should just return that int.
Common mistakes
Passing the variable instead of its address, as in divide(47, 5, q, r): the compiler objects that int is not int *, and silencing it with a cast makes the function treat a small integer as an address, so the store crashes or corrupts unrelated memory.
Assigning to the pointer rather than through it, writing out = &local; inside the function instead of *out = local;. That only changes the function's private copy of the pointer, so the caller's variable never changes and no warning is produced.
Reading the out variable after a call that returned failure: under the write-on-success rule it was never touched, so an uninitialised variable holds an indeterminate value and printing it is undefined behaviour, not a convenient zero.
Try it yourself
Change, predict, then run
Write void split_time(int seconds, int *h, int *m, int *s) that fills all three out-parameters and call it with 3725 to print 1:02:05. Then let m be NULL and confirm a call asking only for hours and seconds still works.
Open the C workspaceCheck your understanding
parse_uint is documented as writing through its out-pointer only when the text is a valid number. A caller writes: unsigned n; parse_uint("12x", &n); printf("%u\n", n); What is the problem?
- Nothing is wrong; a failed parse stores 0 in n.
- The call should pass n instead of &n, because parse_uint wants the value.
- n is read while still uninitialised, because the failed call never wrote through out.
- parse_uint cannot modify n at all, since C passes all arguments by value.
Show answer
parse_uint rejects "12x" and returns without ever assigning to *out, so n still holds whatever was in that storage and printing it is undefined behaviour; the fix is to check the return value first. The last option is tempting because arguments really are passed by value, but what gets copied is the address, and dereferencing that copy reaches the caller's own object, which is exactly why out-parameters work.