Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Getting multiple return values in C. Explains structs, typedefs, and pointers.
#include <stdio.h>
// ===== Our Types =====
// A struct is a single type (like an int)
// that contains other types, and gives them names
struct numbers {
int first;
int second;
// This is all one expression, it's on multiple lines for readability
typedef // means to make a new type
struct numbers // it is defined in terms of types we already know, in this case, the struct we just defined
Numbers; // its name is `Numbers`, so it's basically an alias for for struct (we do this b/c it's more convenient to type and read)
// ===== Function Declarations =====
// so you can go view them as they are used, rather than as they are declared
Numbers new_nums();
void print_numbers(char*, Numbers);
void update_nums_by_value(Numbers);
void update_nums_by_reference(Numbers*);
// ===== Using the struct =====
int main (int argc, char const *argv[]) {
// ----- Understanding the struct -----
// The struct is literally just two ints, adjacent to eachother in memory.
puts("Understanding the struct");
printf(" sizeof(int) -> %d\n", (int)sizeof(int));
printf(" sizeof(Numbers) -> %d\n", (int)sizeof(Numbers)); // 2x as big b/c it's literally just 2 ints side by side
// when we use an attribute name like `n.first`, it will map to the offset of that attribute.
Numbers n = {100, 123}; // <-- fancy way to initialize
printf(" first -> %d\n", n.first);
printf(" second -> %d\n", n.second);
// ----- Example 1: Returning a struct directly -----
// `my_n` is in our stack, it copies its value from the return location of `new_nums`
// so after the `new_nums` returns, there will have been 3 different Numbers structs:
// 1. the `n` inside new_nums,
// 2. the return value of `new_nums`
// 3. and the `my_n` here
Numbers my_n = new_nums();
print_numbers("Returned from `new_nums()`", my_n);
// ----- Example 2: The problem with passing values -----
// Lets update the value. There's a problem here
// see if you can figure out what it is before reading the next example :)
print_numbers("After `update_nums_by_value(my_n)`", my_n);
// ----- Example 3: The solution is to pass a reference -----
// Because `update_nums_by_value` takes a `Number` on the stack,
// we are copying the memory at `my_n` into the memory at it's `n` argument.
// So the struct it is updating is at a different memory locaiton,
// its data came from ours, but it's not ours
// What we need to do is pass it the memory location of our struct
// then have it go to that memory location and update the values there.
// we pass the location of `my_n` rather than its value by using the `&`.
// The "location" is often called the "address" or "reference"
// An analogy: If you haven't seen these before, you can think of it as the difference
// between giving your friend a clone of yourself, and giving them your phone number.
// it's more convenient to have the clone around, but it's expensive to clone yourself,
// and whatever conversations they have with the clone will be the clone's conversations, not yours.
// But if you give them your phone number, then you're not as immediate as the clone,
// but they can still have conversations with you, they just have to call the number first,
// and these are conversations that *you* are having, not some clone that's going to die as soon as the method returns
// ...okay, that analogy might have broken down at the end there :/
print_numbers("After `update_nums_by_reference(&my_n)`", my_n);
// ----- A few gotchas to watch out for -----
// Don't return a reference to a variable declared in your function
// If your function finds the values and wants to return them as a pointer,
// you need to actually allocate that memory on the heap (the `malloc` function),
// don't return a pointer to a local variable, because it's on the stack,
// and as soon as you return from the function, its memory is at risk of being overridden.
// This will result in either its values getting really wonky and changing in unexplainable ways over time,
// or in a segmentation fault.
// It's often more convenient to have the caller pass a reference, even if it has no meaning until after the function
// Memory management is annoying.
// If you let the caller give you the memory, then your function doesn't have to deal with all the allocation.
// And everyone knows who is responsible for making sure that memory gets handled correctly: the caller.
// This reduces the probability of a memory leak.
// And it's likely that the caller can just make a local variable on the stack, and do everything it needs with that.
// No need to off into the heap at all!
return 0;
// ===== The Functions =====
void print_numbers(char* title, Numbers n) {
printf("%s\n first -> %d\n second -> %d\n\n", title, n.first, n.second);
Numbers new_nums() {
Numbers n; // A new varable on the stack named `n`
n.first = 1; // The dot in `n.first` means that within the n's memory,
n.second = 2; // we're dealing with the subset of memory that we named `first`
return n; // Copy memory at `n` to the memory treated as the return value
void update_nums_by_value(Numbers n) {
n.first = 11;
n.second = 22;
// The asterisk in a declaration means that we're receiving the memory address of the type
// So the asterisk in `Numbers* n` means that n is the address of a `Numbers`
void update_nums_by_reference(Numbers* n) {
// Because it's an address, we have to go look it up before doing anything with it
// In the above analogy, this is like calling you on the phone
(*n).first = 11;
// That's what we want to do, but it's annoying to type that, and in reality,
// we do this sort of thing almost all of the time, and directly talk to structs very infrequently.
// So we have the arrow syntax to mean "go to this location and access this attribute"
n->second = 22; // this is the same as `(*n).second = 22`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.