Previous Next

Animation showing how a function that takes two pointer parameters can change two variables in the function that called it

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 * from the previous chapter 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 a single 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 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 old friend in this code: void, like we used it because main() takes no parameters. 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, too. 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