Masters of the Void [The Void]
7. Multiple Return Values

Previous | Next

We know how to hand several parameters into a function and how to get one value out of them. But it would be kind of boring if we couldn't get more than one parameter out of a function. But how do we do that, if C forbids accessing variables in the function that called the current function and we only have one return value? We already know the solution. Remember how scanf requires that funny ampersand before all the variables it's supposed to fill? This ampersand is the "address-of operator" we just heard about.

You'll remember that, memory is kind of like a piece of graph paper. And each variable can take up one or more boxes on this sheet of paper. And each box has a number. One of these boxes is a byte, and an int on the Macintosh, for example, is always four bytes long. When you declare a variable of type int, C sets aside four bytes in sequence and remembers their number. This number is called the address. If it helps you, think of it as a building number, and your computer's memory being a very long street.

The address-of operator "&", when used on a variable name, gives you the memory address of that variable. Now, a memory address is simply a number, like an int. You can hand it to another function as a parameter. And now, once you have that address in that other function, you can use the "follow-address" prefix operator "*", to access whatever is at that address in memory. So, if you have the address of the variable named a of the main() function in a variable named aAddress, you can change a by writing

*aAddress = 15;

Note that this "*" operator is different from the multiplication operator "*". The follow-address-operator is a unary prefix operator. That is, it goes right before the one variable containing the address you want to follow. The multiplication operator, on the other hand, is a binary infix operator, that is it goes in between the two variables or values that you want to multiply.

Now, there's just one problem: Since an address is only a house number that points at one house, what about a bigger house that occupies several house numbers, what about a data type that uses several bytes? Like an int? Since C needs to know what type to treat an address as when you use the follow-address operator on it, they didn't use an int for storing addresses. Instead, they invented the "pointer" data type.

Trouble is, some masochist decided not to call the pointer data type pointer, but instead it uses the "*" character (which is already doing double-duty as follow-address operator and multiplication operator). Triple shifts, oy! I guess I'll best give you an example: aAddress above is supposed to be a pointer to an int, so when you declare that variable, it's written as:

int  *   aAddress;
You can then put an address into this variable by writing e.g.:
aAddress = &a;
aAddress now contains a number, the house number of a. It's really pretty easy, only that there are a few pitfalls. One of them is declaring multiple pointer variables in one statement. What do you think are the types of bAddress, cAddress and dAddress below:
int  *   bAddress,
         cAddress,
         dAddress;
bAddress is a pointer to an int, just as you expected. But cAddress and dAddress are actually just ints (!) WTF?! Well, whoever had the bright idea to use "*" to mean "pointer" also had the bright idea that, for declaring new variables, this "*" belongs to the name, not to the type. i.e. this "*" goes with bAddress, not with "int". Consequently, the correct way to declare three int pointers is to repeat the asterisk each time:
int  *   bAddress,
     *   cAddress,
     *   dAddress;
Stupid, isn't it? But it gets worse. There's a nice handy shorthand notation where you can declare a variable and assign it a value right away. Fancy-schmancy people call this "initialising" the variable. You do that by writing:
int  num = 15;
This is equivalent to:
int num;
num = 15;
However, when you initialise a pointer, this would be kind of ambiguous:
int  *   bAddress = &a;
You could read this as equivalent to
int  *   bAddress;   // pointer to int.
bAddress = &a;    // assign house number to pointer variable.
or equivalent to
int      bAddress;    // int
*bAddress = &a;    // treat int as pointer and assign &a to the int on the other side.
or even equivalent to
int  *   bAddress;    // pointer to int.
*bAddress = &a;    // follow the pointer and assign &a to the int on the other side.
What is it? Well, luckily they went the sensible route and took the first option. The second option would be kind of pointless since an int is any integral number, not an address, and C wouldn't let you treat this int as an address for safety's sake. The third option is also kind of pointless, because an int* points to an int, and for the same reason C doesn't want you to assign an address to an int. C is very picky about having types match.

Now that we have that confusing bit out of the way, back to something easier: Of course you can have pointers to other types. Take a char, for example:

char   a = 'X';
char * aAddress = &a;
*aAddress = 'Y';
printf( "%c %c\n", a, *aAddress ); // Outputs "Y Y".
And if you wanted to rewrite GetArgument to use pointers, you could:
void    GetArgument( bool isLeft, int * outNum )
{
    if( isLeft )
        printf( "Enter the left argument: " );
    else
        printf( "Enter the right argument: " );
    scanf( "%d", outNum );
    fpurge( stdin );
}
There's one new thing in this code: void. void is the no-type. When you have a function that does not return a value, instead of writing int or bool you use void. Apart from that, void is also used for pointers that point to data whose type you don't know, but we'll have to cover that another time.

One thing you should note is that there is no address-of operator ("&") in front of outNum in the call to scanf() this time. This is because outNum is declared as an int-pointer ("int *"). Since it already is an address, there's no need to use the address-of operator. But where does this address come from? Well, when you call this variant of GetArgument, you have to do it this way:

GetArgument( true, &leftArgument );
If you left away the "&", your C compiler would complain that you're trying to give it an int where a pointer belonged. After all, you specified int* as the type when you wrote GetArgument and its commands. Also, this returns void, i.e. nothing, so for this function there is no return value to assign to leftArgument using the = operator. That would be confusing anyway, after all why change a variable through a pointer and then try to overwrite its value with your own return value?

Previous | Next

Reader Comments: (RSS Feed)
Colton writes:
These last two lessons got really confusing, really fast.
Cameron Cooke writes:
This tutorial is fantastic. I had so many light bulb moments! It makes total sense.
Jason writes:
I agree the last two lessons were tough, a little confusing, but after reading them over twice, I think I now get them. Real brain exercise, that's for sure. Thanks for doing this.
Marc Davidson writes:
I believe "GetArgument( true, &leftArgument );" should read "GetArgument( true, &vFirstArg ); " to be consistent with the variable names used previously, right?
Josh writes:
Marc is right, it should be &vFirstArg, not &leftArgument. Regardless, thank you very much for writing these tutorials!
Jaxerell writes:
This page is way confusing, didn't understand half of it. Is the lowercase a in aAddress supposed to be a letter or replaced with a number?
John David Dunson writes:
well, it took me three days to figure all of this out. to jaxerell: "a" and "aAddress" are two different variables. "a" is a normal variable. "aAddress" is a variable that stores the memory address of another variable (in this case, "a"). this type of variable is called a pointer. so there are two ways to change "a". you could go the normal route and say: a = 15; or you could go the technical route and say: *aAddress = 15 the first route is saying: go to the place in memory where the variable named "a" is stored and change the value stored there to 15. the second route is saying: go to "this specific address in memory" and change the value stored there to 15. here's my explanation of his example: char a = 'X'; // declare a character-type variable named "a" and initialize it to the value X. char * aAddress = &a; /* declare a pointer-type variable named "aAddress" and initialize it to the address of the variable "a". (a character-type can also hold an integer.)*/ *aAddress = 'Y'; /* go to the place in memory that "aAddress" points to and change the value stored there to Y. in other words, change the variable "a" to the value Y (it used to be X and now it's Y).*/ printf("%c %c\n", a, *aAddress); /* print the value of the variable "a" to the console, then print the value stored at "aAddress" to the console. basically, you're accessing the same sopt in memory using two different methods. Y Y would be the result. if you left off the third line, then X X would be the result. if you left off the asterisk in the last line, then Y 32 (or whatever the address of "a" is) would result.*/ what i don't get is why you taught us the ability to do more than one thing in a function, then you give us an example that will require the function to be called twice. so here's how it should look… although, this method is still not as efficient for this simple program as my original code was that i posted on page 4. A Crude Calculator… but here it is anyway, using pointers, address-of, and a function: void GetArgument(int* outFirstArg, int* outSecondArg) { printf("Enter left argument: "); scanf("%d", outFirstArg); fpurge(stdin); printf("Enter right argument: "); scanf("%d", outSecondArg); fpurge(stdin); } then the call would look like this: GetArgument(&vFirstArg, &vSecondArg);
Uli Kusterer replies:
John, your code is certainly also correct. It's just a different level of re-use. You *can* use this notation to return several values, but I wanted to show that this is *completely equivalent* to returning a value. I'll think about ways to make this clearer.
TJ Leeland writes:
I'm confused on a different point. void GetArgument( bool isLeft, int * outNum ) //Creates a function(?) named GetArgument using the void data type that uses an arguement that is creating a boolean variable named isLeft, then a creates a integer of the pointer data type named outNum { if( isLeft ) //here is where I am confused. How does the program know if "if ( isLeft )" is indeed isLeft? printf( "Enter the left argument: " ); else printf( "Enter the right argument: " ); scanf( "%d", outNum ); fpurge( stdin ); }
Uli Kusterer replies:
TJ, I have to admit I don't really understand your question. But I'll try: "if" simply does the upper thing if the thing between the brackets ends up being "true". Otherwise it does the second thing. The things between the brackets are parameters, that is, they're local variables, but they get filled in when the function is called, right before any of the code between the { and } is run. If you call it as GetArgument( true, &vMyNumber );, isLeft will be set to true, if you call GetArgument( false, &vMyNumber );, isLeft will be set to false. Whatever we specify as the first parameter value in the call will end up in the first parameter here, because we wrote "bool isLeft" as the first item between the brackets. That's the magic of parameters. Regular local variables are declared lower down between { and }, but the parameters are special "mailboxes" through which data from the outside gets into our function, just like the return value (which we're not using here, hence the "void") can pass data back out again. Now, when the code actually gets run, the "if( isLeft )" line does the same thing it would do in other cases: Compare what's in the brackets to true and do one thing or the other. Did that help? Or did I misunderstand your problem?
Bruno Gätjens González writes:
Hi!!! First of all thanks for this amazing tutorial for us the C beginner, thank you for take the time to prepare and write all that. I am from Costa Rica and i was thinking if there is the possibility in the future to make it in Spanish too? :) Thank you so much again. Bruno
Uli Kusterer replies:
Bruno, thanks for the kind words. I'd love to do a Spanish version, but sadly I know about three words of Spanish, so this isn't very likely to happen.
Mk12 writes:
I still don't get the point of the pointers (no pun intended). Why not just pass the actual variables and set those instead of using the pointers and addresses (except for the fact that scanf needs addresses)?
Gordon writes:
Uli, First thanks for the tutorials. This one definitely takes a few reads. I think this particular chapter needs some more visuals. The part that is really confusing for me is the part about int * bAddress = &a; and the ambiguity. Some visuals illustrating pointer relationships and why this is ambiguous might be helpful to cement the problem in our minds. But otherwise a fairly clear explanation.
Richard Howell writes:
OK. I wrote this: #include <stdio.h> int main () { int number; int newNumber; int * address = &number; printf( "Give me a number to store in memory: " ); scanf( "%d", &number ); fpurge( stdin ); printf( "\nThe number %d can be found at memory location %d", number,* address); newNumber = *address = +1; printf( "\nThe new number stored in location %d is %d", * address, newNumber); return 0; } And I had this output: Give me a number to store in memory: 123 The number 123 can be found at memory location 123 The new number stored in location 1 is 1 I think I'm missing the point here. Shouldn't I be printing a memory location, and not just a repeat of what I typed in?
Uli Kusterer replies:
You're using the * operator in all your print statements. That operator means "follow the address to its value". That's why it's printing the value again, not the address.
steven writes:
I have read these last two chapters twice, and now I am four times as confused. ^_^ Anyway, thanks for a really great tutorial. So, A=15 and aAddress= the place in memory where I can find the location of A, which contains 15?
Richard Howell writes:
OK. But when I replaced the misplaced '*'s in my printf statements...we get a new problem in that the compiler tells me: Format 'd%' expects type 'int', but argument 'n' has type 'int *'. When you used a char to store the address in your example, you were able to use '%c'. Why can't I use %d?
Uli Kusterer replies:
Well, an int* isn't really an int. it's a pointer to an int. It is a number, but marked specially. There's a special format for those, %p, but that prints the number in hexa-decimal. Alternately, you could typecast it, (see later chapters), and write (int) address This will work as long as you're on a 32-bit Mac. If your Mac runs in 64 bits, addresses are 64-bit long and won't fit in a shorter int. They do fit in a "long int" with format "%d" in that case, though.
Richard Howell writes:
Ha! It works. And right now, Hex is a blessing :) Thanks
College student writes:
I also will admit the past two chapters of this tutorial were a tad confusing. I myself have taken two semesters of JAVA, and this C business isn't bad at all. To those of you out there that are learning this stuff without any prior experience to programming, I must give you kudos; this stuff isn't easy at first.
Roope writes:
#include <stdio.h> #include <stdbool.h> int GetArgument( bool isLeft, int * outPut) { if( isLeft ) printf( "Enter the left argument: " ); else printf( "Enter the right argument: " ); scanf( "%d", outPut ); fpurge( stdin ); } int main() { int a; int * aAddress = &a; int b; int * bAddress = &b; int sum = *aAddress + *bAddress; *aAddress = GetArgument( true, &a); *bAddress = GetArgument( false, &b); printf("%d plus %d equals %d", *aAddress, *bAddress, sum); } That's my code, what's wrong coz I get 0 plus 0 equals 0. ? Great tutorial! Thanks!
Roope writes:
This is what I have now, and I get 1+2=0? #include <stdio.h> #include <stdbool.h> int GetArgument( bool isLeft, int * outPut) { if( isLeft ) printf( "Enter the left argument: " ); else printf( "Enter the right argument: " ); scanf( "%d", outPut ); fpurge( stdin ); } int main() { int a; int * aAddress = &a; int b; int * bAddress = &b; int sum; int * sumAddress = &sum; sum = *aAddress+*bAddress; GetArgument( true, &a); GetArgument( false, &b); printf("%d plus %d equals %d", *aAddress, *bAddress, *sumAddress); }
Roope writes:
Got it! It was before calls afterwards works a bit better :D
Aman writes:
Roope, your code should be like this, else it doesnt add up ..... GetArgument( true, &a); GetArgument( false, &b); sum = *aAddress+*bAddress; printf("%d plus %d equals %d", *aAddress, *bAddress, *sumAddress); }
Tristan Moriarty writes:
Hi Uli, and thankyou again for a great set of tutorials! I also found this lesson trickier than what has gone before. Am I right in thinking that essentially what we learn here is a new way of changing a variable using a pointer, and that what makes this useful is that by putting a pointer into a parameter function, we can call that function with any variable we like (provided it is a variable of the same kind as the pointer type) and change it's value to one in line with our parameter function? Apologies if that seems like just a long winded way of saying something that should have made sense anyway, but that's how I'm thinking about it! Thanks again for these articles- they really are very very helpful!
Uli Kusterer replies:
Not sure what you mean by a "parameter function"? But yes, the idea here is to change a variable that is *outside* the current function. Every function can only change the variables declared within its { and } brackets, (or return a value, which the function that called it can then assign to one of its variables). To change any other variable, you need to use a pointer. Oh, of course global variables can also be changed by any old function, that is their purpose, after all, and their curse.
Tristan Moriarty writes:
By "parameter function", I just meant really the kind of function that we have here with GetArgument. I think about it like this because it was the first function we introduced with a parameter a couple of tutorials ago. Apologies, I'm new to programming and it seems quite easy to make up one's own jargon!
Comment on this article:
Name:
E-Mail: (not shown, hashed for Gravatar)
Web Site URL: (optional)
Comment: (plain text only)
Please Enter the following word:
Or E-Mail Uli privately.