Book 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 value back from a function. 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 cover that in a later chapter.

So why do we return void here? Well, in this example we’re really only providing one value back, the one we used to give back as the return value. So since we have nothing else to give back, there’s no point in our particular case. That said, it would be perfectly valid to both modify a parameter passed in as a pointer, and return a second thing the old-fashioned way. In fact, many programmers return a bool whether a function could successfully do its work as the return value, and give back the actual information using pointer parameters, for functions that can fail (e.g. a file copying function can fail if the disk runs full).

One more 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, &vFirstArg );

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 vFirstArg 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

This entry was posted in C Tutorial. Bookmark the permalink.

9 Responses to Book 7: Multiple Return Values

  1. Bill Polhemus says:

    I was confused for awhile – it’s C after all – but I’m beginning to get a glimmer here.

    Please tell me true or false:

    1. The GetArgument() function now does not require assignment to a variable when called in main(), because it returns with the address for the variable you want to assign:

    getArgument( true, &vFirstArg );

    instead of:

    vFirstArg = getArgument( true, &vFirstArg );

    So the “assignment” effectively takes place by the function passing back the POINTER of the variable via *outNum to &vFirstAr.

    2. In the getArgment() function, we now assign the keyboard input directly to the local variable outNum rather than to the ADDRESS of the variable:

    OLD: scanf( “%d”, &outNum );
    NEW: scanf( “%d”, outNum );

    That makes sense to me since giving the type void for getArgument() means getArgument no longer “returns” a value in the usual way. Rather, it “implicitly” (???) passes the POINTER address to main(), which then assigns the data found at that address to the variables vFirstArg or vSecondArg as the case may be.

    [What DOESN’T make sense to me, by the way, is why, when using scanf() for keyboard input, we invoke the ADDRESS (scanf( “%c”, &vOperation ); e.g.) rather than the variable directly for the assignment. This is confusing; it feels like you should be inputting a HEX value for an address there instead of the value you want to assign to the variable!]

    3. We have to remember to delete the line in the getArgument() function as follows:

    return vNum;

    This is because we are no longer returning a value in the “normal way” since the type of the function is void, and void does not allow return of a value (I even tried return 0 but it triggered an error in XCode).

    Thanks again for the time it took you to do this.

    • Uli Kusterer says:

      I think you’re on the right track here, but your terminology is a bit iffy…

      GetArgument() in its new form *receives* the address (aka a pointer) of vFirstArg as its new, second parameter. Since having the address of something means you can de-reference that pointer and change the variable directly, the function now changes vFirstArg directly, by de-referencing the pointer to it in outNum and then assigning the value it would have returned to it.

      Since the new GetArgument() has already modified vFirstArg, there is no need for whoever calls GetArgument() to perform the assignment anymore. So we can get rid of GetArgument()’s return type and change it to void, and can get rid of the assignment to vFirstArg at the call site as well.

      The same applies to scanf(): It is just a regular function, one you could theoretically write yourself. To be able to change a variable in the calling function, you need to give it the address of that variable (because scanf() already has a return value, we just don’t care about it, so the int or string it gives us has to be returned in some other way).

      So far, whenever we used scanf(), I’ve been handwaving and telling you to just use the & operator (which will give the address of your local variable so scanf() can change it), because I hadn’t given this explanation yet.

      Does that make more sense now? Local variables are “invisible” to anyone but the function they are local to, unless you explicitly hand out their address to someone.

      Once that is a given, it makes sense that in the case of our new GetArgument(), since it already receives an address as its parameter (i.e. there is already an & operator turning vFirstArg into an address when you call the new GetArgument()), you do not need to do that again. You just hand on the address to scanf(), the one who really needs it.

      I’ve added a paragraph on why we don’t return a return value to the chapter. Hope that clarifies things a bit.

  2. Slade says:

    I was confused for a while with GetArgument (true, &leftArgument); I think this is because in the calculator we used the integer FirstArg… not leftArgument. So if you haven’t changed your integers in main() to leftArgument and rightArgument there will be an error. If you don’t want to change your int variables in main() replace leftArgument with vFirstArg and rightArgument with vSecondArg. Or I may be totally wrong… Some help Uli?

    • Uli Kusterer says:

      You understood it correctly. I can see how that would be a bit confusing, so I changed leftArgument to vFirstArg in that example of calling our pointer-based GetArgument(). Thanks for pointing that out!

      For those wondering: You pick whatever variable name you have and pass it to GetArgument(), so leftArgument is not wrong per se. However, since this is an alternate version of the function in Book 4, I should probably pick the same variable name as in Book 4 for the example as well.

  3. Thijs says:

    Isnt it supposed to be scanf( “%d”, *outNum ); when you pass a pointer to outNum in as an argument to the GetArgument() function?

    • Uli Kusterer says:

      That would be correct for printf(), where you are giving it the actual number to display. In scanf(), it’s all about reading, so for every %whatever placeholder you give it, it expects the address of a place where you want it to put what it reads. So in this case, since outNum already is a pointer/address (an int*), you just pass outNum directly. Otherwise, if it was just an int, you’d have to provide outNum’s address by writing &outNum.

      • Uli Kusterer says:

        To rephrase: scanf() is implemented using the very technique this chapter tries to teach. So if you want to just pass through a variable, it already has the right type, you already have the destination variable’s address.

  4. Uli Kusterer says:

    @cameron, here are a few things that jump out at me:
    1. You declared GetArgument to have a return type of ‘void’ not ‘int’. It doesn’t return anything. So you can’t do vFirstArgument = GetArgument( … anymore. That’s probably one case where it complains about incompatible types.

    2. The names of the parameters are only visible “from the inside” of a function. So you using GetArgument(true,&outNum) from “main” doesn’t work, because there is no “outNum” in main(). What you instead want to do is give it a variable that actually exists inside main().

    3. Same with your ‘+’ case: There is no declaration for a variable named leftArgument anywhere here, is there? If you get error messages about incompatible *pointer* types, the compiler is probably making an assumption about what leftArgument could be, and it is wrong. The error is a bit misleading, because you are confusing the compiler 😉

    4. Ask yourself what you are trying to achieve with each line of code. In these cases, you are trying to get a number from the user and put it into vFirstArg or vSecondArg so you can do math with them. So at the very least, one of these variables needs to be involved, and it needs to be given as a parameter that is declared to be some sort of ‘int’. Since giving an int to a function means you just get a copy of the number, but you actually want to change it, you instead want the int’s address, so a pointer. You are giving it all sorts of addresses, just not the right one. Take a step back, look at those lines again.

  5. cameron says:

    Trying to re-write the calculator to use pointers, but I can’t get it to work. I get issues with ‘leftArgument, and incompatible pointer types.

    // main.c
    // MyFirstProgram
    //
    // Created by Cameron Palm on 1/17/14.
    // Copyright (c) 2014 Cameron Palm. All rights reserved.
    //
    #include
    #include
    //void GetArgument( bool isLeft, int * outNum ); // Ignore this line for now.
    void GetArgument( bool isLeft, int * outNum )
    {
    if( isLeft )
    printf( “Enter the left argument: ” );
    else
    printf( “Enter the right argument: ” );
    scanf( “%d”, outNum );
    fpurge( stdin );
    }

    int main()
    {
    int vFirstArg,
    vSecondArg;
    char vOperation;
    bool vFinished;

    // Make sure our flag is initialized!
    vFinished = false;

    // Now loop until user doesn’t want anymore:
    while ( vFinished != true )
    {
    printf( “What operation do you want to do?\n” );
    scanf( “%c”, &vOperation );
    fpurge( stdin );

    if( vOperation == ‘+’ )
    {
    vFirstArg = GetArgument(true,&leftArgument);

    vSecondArg = GetArgument(false,&rightArgument);

    printf( “\n%d + %d = %d\n”,
    vFirstArg,
    vSecondArg,
    vFirstArg + vSecondArg );
    }
    else
    if (vOperation == ‘-‘)
    {
    vFirstArg = GetArgument(true,&outNum);

    vSecondArg = GetArgument(false,&outNum);

    printf(“\n%d – %d = %d\n”,vFirstArg,vSecondArg, vFirstArg-vSecondArg);
    }
    else
    if (vOperation == ‘*’)
    {
    vFirstArg = GetArgument(true,&outNum);

    vSecondArg = GetArgument(false,&outNum);

    printf(“\n%d x %d = %d\n”, vFirstArg,vSecondArg,vFirstArg*vSecondArg);
    }
    else
    if (vOperation == ‘/’)
    {
    vFirstArg = GetArgument(true,&outNum);

    vSecondArg = GetArgument(false,&outNum);

    printf(“\n%d / %d = %d\n”, vFirstArg,vSecondArg, vFirstArg/vSecondArg);
    }
    else
    vFinished = true;
    }

    printf( “Finished.\n” );

    return 0;
    }

Leave a Reply

Your email address will not be published. Required fields are marked *


*