Created
October 19, 2014 15:47
-
-
Save JoshCheek/ab65b85f5a1e9723ba23 to your computer and use it in GitHub Desktop.
Getting multiple return values in C. Explains structs, typedefs, and pointers.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
puts(""); | |
// ----- 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 :) | |
update_nums_by_value(my_n); | |
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 :/ | |
update_nums_by_reference(&my_n); | |
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