Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Return Multiple Values in C
A C function has a single return statement, yet real programs constantly need two or three results. Learn how to return multiple values from a function in C using output parameters, status codes and structs, with examples from embedded C programming.
What You Will Learn
- Why a plain
returncan carry only one value - How to return multiple values from a function in C with output parameters
- The professional pattern of returning a status code while results travel through pointers
- How to read a sensor and report both the raw reading and the converted value
- When a struct return is a better choice than several pointers
- Typical bugs and safe habits for out-parameters
Prerequisites
You need the previous two lectures: call by value and call by reference in C. Basic knowledge of arrays and sizeof is also helpful for the second example.
The One-Value Limit and How to Beat It
Look at any C function header and you will see exactly one return type. That is enough for int square(int x), but not for a function that divides two numbers and wants to report both the quotient and the remainder, or one that scans a list and wants to report the smallest and largest element. You could write two separate functions and loop over the data twice, but that wastes time and splits logic that belongs together.
The standard solution follows directly from the last lecture. The caller creates one variable per extra result and passes their addresses. The function writes through those pointers. These pointer parameters are called output parameters or out-parameters, because data flows out of the function through them.
Three Ways to Hand Back More Than One Result
| Technique | How it looks | Best when |
|---|---|---|
| Output parameters | int f(int a, int *r1, int *r2) | Two to four independent results, plus an error code |
| Return a struct | struct pair f(int a) | Small group of related values, no error path needed |
| Fill a caller buffer | int f(uint8_t *buf, size_t len) | Variable amount of data such as a received frame |
Example 1: Quotient and Remainder Together
Integer division produces two useful numbers, and computing them in one call is natural. The function below also rejects a zero divisor with a status code, so the caller can tell success from failure.
#include <stdio.h>
static int div_mod(int dividend, int divisor, int *quot, int *rem)
{
if (divisor == 0) {
return -1;
}
*quot = dividend / divisor;
*rem = dividend % divisor;
return 0;
}
int main(void)
{
int q, r;
if (div_mod(47, 5, &q, &r) == 0) {
printf("47 / 5 = %d remainder %d\n", q, r);
}
if (div_mod(47, 0, &q, &r) != 0) {
printf("division by zero rejected\n");
}
return 0;
}
Save it as div_mod.c and run:
gcc -std=c11 -Wall -Wextra -o div_mod div_mod.c
./div_mod
47 / 5 = 9 remainder 2
division by zero rejected
Notice how the function never touches q or r when the divisor is zero. That is deliberate: on failure, the caller’s variables keep whatever they held before, and the return value tells the story.
Example 2: Smallest and Largest in One Pass
Scanning an array twice, once for the minimum and once for the maximum, is wasteful. One loop can find both. This version also validates every pointer and the element count before doing any work.
#include <stdio.h>
#include <stddef.h>
static int min_max(const int *data, size_t count, int *min, int *max)
{
if (data == NULL || min == NULL || max == NULL || count == 0) {
return -1;
}
*min = data[0];
*max = data[0];
for (size_t i = 1; i < count; i++) {
if (data[i] < *min) {
*min = data[i];
}
if (data[i] > *max) {
*max = data[i];
}
}
return 0;
}
int main(void)
{
int temps[] = {31, 28, 35, 22, 30};
size_t n = sizeof temps / sizeof temps[0];
int lo, hi;
if (min_max(temps, n, &lo, &hi) == 0) {
printf("coldest = %d, hottest = %d\n", lo, hi);
}
if (min_max(temps, 0, &lo, &hi) != 0) {
printf("empty data set rejected\n");
}
return 0;
}
coldest = 22, hottest = 35
empty data set rejected
Two choices in this code deserve attention. The input array is declared const int *data, which promises the function will only read it, while the outputs are plain int * because the function is expected to write them. A reader can tell the direction of every parameter from its type alone.
Return Multiple Values in Embedded C: A Sensor Example
Firmware very often needs “the raw ADC count and the converted physical value, or an error”. The following program simulates a 12-bit ADC reading a temperature sensor with a 3.3 V reference. The conversion uses integer arithmetic only, so it behaves the same on a small Cortex-M0 as on your PC.
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#define ADC_MAX_COUNT 4095u
#define VREF_MV 3300u
#define OFFSET_MV 500u
/* Pretend ADC: the test code in main() decides what the "hardware" returns. */
static uint16_t fake_adc_value;
static int read_temperature(uint16_t *raw_out, int32_t *milli_c_out)
{
uint32_t mv;
if (raw_out == NULL || milli_c_out == NULL) {
return -1;
}
if (fake_adc_value > ADC_MAX_COUNT) {
return -2;
}
*raw_out = fake_adc_value;
mv = ((uint32_t)fake_adc_value * VREF_MV) / ADC_MAX_COUNT;
*milli_c_out = ((int32_t)mv - (int32_t)OFFSET_MV) * 100;
return 0;
}
int main(void)
{
uint16_t raw;
int32_t milli_c;
int status;
fake_adc_value = 1000;
status = read_temperature(&raw, &milli_c);
if (status == 0) {
printf("raw = %u, temperature = %ld.%03ld C\n",
(unsigned int)raw, (long)(milli_c / 1000), (long)(milli_c % 1000));
}
fake_adc_value = 5000;
status = read_temperature(&raw, &milli_c);
printf("bad sample -> status = %d\n", status);
return 0;
}
raw = 1000, temperature = 30.500 C
bad sample -> status = -2
The function returns three pieces of information: the raw count, the temperature in milli-degrees and an error code. Distinct negative codes (-1 for a bad pointer, -2 for an impossible sample) help while debugging on target hardware, where you may only have a UART log. Temperature is stored in milli-degrees Celsius so no floating point is needed, which keeps the code small and fast on parts without an FPU.
Option: Return a Struct Instead
When the results always belong together and there is no failure path, a small struct is cleaner than several pointers:
struct div_result {
int quot;
int rem;
};
static struct div_result div_mod_pair(int dividend, int divisor)
{
struct div_result r = { dividend / divisor, dividend % divisor };
return r;
}
The call site becomes struct div_result r = div_mod_pair(47, 5); and you read r.quot and r.rem. The compiler copies the struct, which is fine for a handful of bytes but wasteful for large objects. This version has no way to signal a zero divisor, which is why the out-parameter style remains the favourite in driver and library code.
Real-World Uses of Multiple Results
- Parsing: return the parsed number and how many characters were consumed.
- Drivers: return a status code plus the data that was read from a peripheral.
- Math helpers: quotient with remainder, sine with cosine, mean with variance.
- Protocol stacks: return the payload length and the message type from a single decode call.
Rule of thumb: if the caller must be able to tell “it worked” from “it failed”, use a status return plus out-parameters. If the values are always valid and travel together, a small struct is simpler.
Common Mistakes and Troubleshooting
| Mistake | Consequence | Fix |
|---|---|---|
Forgetting to pass & for an output | Warning or crash | Build with -Wall -Wextra |
| Ignoring the status code | Using uninitialised result variables | Always test the return value first |
| Writing outputs before validating inputs | Half-updated results on error | Validate, then write |
Passing NULL for an output the function needs | Hard fault | Check pointers at function entry |
| Mixing up the order of two same-typed pointers | Min and max silently swapped | Use descriptive names and keep a consistent order |
Warning: Never leave result variables uninitialised and then use them after a failed call. Either initialise them to a safe value before the call or check the status code every time.
Best Practices
- Status in the return value, data in out-parameters. This convention is used across the Linux kernel, POSIX and most RTOS APIs, so it feels familiar to every embedded developer.
- Put inputs first, outputs last in the parameter list.
- Mark inputs
const. It documents direction and helps the compiler catch mistakes. - Performance: out-parameters avoid struct copies, but too many of them make a function hard to call. If you need more than about four results, group them in a struct and pass a single pointer to it.
- Security: validate counts and pointers before writing, and never write more elements than the caller’s buffer can hold.
- Use fixed-width types such as
uint16_tandint32_tin firmware so behaviour is identical across compilers and targets.
Key Takeaways
- One
returngives one value; pointers give you as many extra results as you need. - Return a status code and deliver data through output parameters.
- Mark read-only inputs
constand validate every pointer. - Small groups of related values can also be returned as a struct.
- Embedded code prefers integer arithmetic and fixed-width types for predictable results.
Conclusion
The ability to return multiple values from a function in C is a small step that changes how you design code. Instead of scattering related computations across several functions, you can write one clear routine, report errors cleanly and keep the caller in control of where results are stored. Practise by rewriting a few of your earlier programs so they return a status code, and you will already be writing in the same style as professional firmware. In the next lecture of this free C tutorial we look at the opposite direction: functions that return a pointer, and the dangerous mistake hiding there.
Frequently Asked Questions
Can a C function return two values?
Not directly with one return statement. Use output parameters, return a struct, or fill a buffer supplied by the caller.
What are output parameters in C?
They are pointer parameters the function writes through, so results appear in the caller’s variables after the call.
Should I return a struct or use pointers?
Use a struct for a few related values with no error path. Use pointers with a status code when failure is possible or when the data is large.
Why do embedded drivers return negative numbers on errors?
It lets zero or positive values mean success and keeps one integer type for both status and, sometimes, a count. The Linux kernel follows the same convention with negative errno values.
Is it safe to leave an output pointer as NULL?
Only if the function documents that the output is optional and checks for it. Otherwise dereferencing NULL causes a crash or a hard fault.
Can a function return an array?
No, but it can fill an array that the caller supplies, or return a pointer to storage that outlives the call. That is the subject of the next lecture.
Build Strong Embedded C Foundations
Keep going with the free embedded C course from EmbeddedPathashala. Next you will learn how a function returning a pointer works, and why returning a local address is a classic bug.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments