There is one more way to pass variables to functions, and that is by address. Passing an argument by address involves passing the address of the argument variable rather than the argument variable itself. Because the argument is an address, the function parameter must be a pointer. The function can then dereference the pointer to access or change the value being pointed to.
Here is an example of a function that takes a parameter passed by address:
void foo(int *pValue)
{
*pValue = 6;
}
int main()
{
int nValue = 5;
cout << "nValue = " << nValue << endl;
foo(&nValue);
cout << "nValue = " << nValue << endl;
return 0;
}
The above snippet prints:
nValue = 5 nValue = 6
As you can see, the function foo() changed the value of nValue through the pointer parameter pValue.
Pass by address is typically used with dynamically allocated variables and arrays. For example, the following function will print all the values in an array:
void PrintArray(int *pnArray, int nLength)
{
for (int iii=0; iii < nLength; iii++)
cout << pnArray[iii] << endl;
}
Here is an example program that calls this function:
int main()
{
int anArray[6] = { 6, 5, 4, 3, 2, 1 };
PrintArray(anArray, 6);
}
This program prints the following:
6 5 4 3 2 1
Note that the length of the array must be passed in as a parameter, because arrays don’t keep track of how long they are. Otherwise the PrintArray() function would not know how many elements to print.
It is always a good idea to ensure parameters passed by address are not null pointers before dereferencing them. Dereferencing a null pointer will typically cause the program to crash. Here is our PrintArray() function with a null pointer check:
void PrintArray(int *pnArray, int nLength)
{
// if user passed in a null pointer for pArray, bail out early!
if (!pnArray)
return;
for (int iii=0; iii < nLength; iii++)
cout << pnArray[iii] << endl;
}
Advantages of passing by address:
- It allows us to have the function change the value of the argument, which is sometimes useful
- Because a copy of the argument is not made, it is fast, even when used with large structs or classes.
- We can return multiple values from a function.
Disadvantages of passing by address:
- Because literals and expressions do not have addresses, pointer arguments must be normal variables.
- All values must be checked to see whether they are null. Trying to dereference a null value will result in a crash. It is easy to forget to do this.
- Because dereferencing a pointer is slower than accessing a value directly, accessing arguments passed by address is slower than accessing arguments passed by value.
As you can see, pass by address and pass by reference have almost identical advantages and disadvantages. Because pass by reference is generally safer than pass by address, pass by reference should be preferred in most cases.
Passing by reference, address, and value is actually not so different
Now that you understand the basic differences between passing by reference, address, and value, let’s complicate things by simplifying them. :)
In the lesson on passing arguments by reference, we briefly mentioned that references are typically implemented by the compiler as pointers. Because of this, the only real difference between pointers and references is that references have a cleaner but more restrictive syntax. This makes references easier and safer to use, but also less flexible. This also means that pass by reference and pass by address are essentially identical in terms of efficiency.
Here’s the one that may surprise you. When you pass an address to a function, that address is actually passed by value! Because the address is passed by value, if you change the value of that address within the function, you are actually changing a temporary copy. Consequently, the original pointer address will not be changed!
Here’s a sample program that illustrates this.
#include <iostream>
int nFive = 5;
int nSix = 6;
// Function prototype so we can define
// SetToSix below main()
void SetToSix(int *pTempPtr);
int main()
{
using namespace std;
// First we set pPtr to the address of nFive
// Which means *pPtr = 5
int *pPtr = &nFive;
// This will print 5
cout << *pPtr;
// Now we call SetToSix (see function below)
// pTempPtr receives a copy of the address of pPtr
SetToSix(pPtr);
// pPtr is still set to the address of nFive!
// This will print 5
cout << *pPtr;
return 0;
}
// pTempPtr copies the value of pPtr!
void SetToSix(int *pTempPtr)
{
using namespace std;
// This only changes pTempPtr, not pPtr!
pTempPtr = &nSix;
// This will print 6
cout << *pTempPtr;
}
Because pTempPtr receives a copy of the address of pPtr, even though we change pTempPtr, this does not change the value that pPtr points to. Consequently, this program prints
565
Even though the address itself is passed by value, you can still dereference that address to permanently change the value at that address! This is what differentiates pass by address (and reference) from pass by value.
The next logical question is, “What if we want to be able to change the address of an argument from within the function?”. Turns out, this is surprisingly easy. You just use pass the pointer itself by reference (effectively passing the address by reference). You already learned that values passed by reference reflect any changes made in the function back to the original arguments. So in this case, we’re telling the compiler that any changes made to the address of pTempPtr should be reflected back to pPtr! The syntax for doing a reference to a pointer is a little strange (and easy to get backwards): int *&pPtr. However, if you do get it backwards, the compiler will give you an error.
The following program illustrates using a reference to a pointer.
// pTempPtr is now a reference to a pointer to pPtr!
// This means if we change pTempPtr, we change pPtr!
void SetToSix(int *&pTempPtr)
{
using namespace std;
pTempPtr = &nSix;
// This will print 6
cout << *pTempPtr;
}
Note that you’ll also have to update the function prototype above main to account for the new prototype of SetToSix():
// Function prototype so we can define // SetToSix below main() void SetToSix(int *&pTempPtr);
When we run the program again with this version of the function, we get:
566
Which shows that calling SetToSix() did indeed change the address of pPtr!
So strangely enough, the conclusion here is that references are pointers, and pointer addresses are passed by value. The value of pass by address (and reference) comes solely from the fact that we can dereference addresses to change the original arguments, which we can not do with a normal value parameter.
7.4a — Returning values by value, reference, and address
|
Index
|
7.3 — Passing arguments by reference
|
7.4a — Returning values by value, reference, and address
Index
7.3 — Passing arguments by reference
Disadvantages of passing by address:
Because dereferencing a pointer is slower than accessing a value directly, accessing arguments passed by address is slower than accessing arguments passed by value.
Could that sentence possibly supposed to read as follows:
Because dereferencing a pointer is slower than accessing a value directly, accessing arguments passed by address is slower than accessing arguments passed by reference.
I would think (am assuming) that arguments passed by value would take the longest because they have to be copied.
[ It turns out that references are usually implemented by the compiler (under the hood) using pointers. Consequently, references aren't any faster than addresses. They just have a nicer syntax, and are safer to use. -Alex ]
I think I get it. Passing by value is slow because it does a copy. Passing by reference or address is basically the same thing, just different syntax.
[ Correct. I've read that if you're passing simple built-in variables by value, it can be as fast or faster than passing by reference or address. Generally when we talk about passing by reference or address being faster, we're talking about for compound types (eg. structs and classes). -Alex ]
—————————————————-
Comparing these two programs:
void foo(int *pValue) { *pValue = 6; } int main() { int nValue = 5; cout < < "nValue = " << nValue << endl; foo(&nValue); cout << "nValue = " << nValue << endl; return 0; } Ouput: nValue = 5 nValue = 6void func(char *address) { address = "b"; } int main() { char *array = "a"; cout < < "array = " << array << endl; func(array); cout << "array = " << array << endl; } Output: array = a array = aWhy doesn’t the second example act as the first example. As the output would be:
What I’ve noticed is when passing an array by address it changes the address that the pointer points to when inside the function. This causes the original value to never be changed. I guess that’s just how it works.
[ Good question and insight. As it turns out, pointer parameters are actually passed by value. Consequently, if you try to change what a pointer points to inside a function, it's the same as changing a variable locally -- as soon as you leave the function, it will revert back to what it was. However, if you dereference the pointer and change the value of what it points to, that won't be reverted. If you actually want to be able to change the address that a pointer points to inside a function, the best way to do this is to pass the pointer itself by reference:
void func(char *&address)Incidentally, this line of code is dangerous:
address = "b"
This is setting the address of the "address" variable to the address of "b". What is the address of "b"? "b" isn't a variable, so this doesn't really make any sense. It probably works in this case due to the way the compiler is dealing with string literals, but I certainly wouldn't want to trust it. -Alex ]
Any insight on this issue? Btw, I know that char *array is not an array but a pointer, but once again it really is an array when used with strings right? Just different syntax right?
[ In C , arrays and pointers are pretty much identical. When you declare an array, all you're doing is setting a pointer to the beginning of your allocated memory. If I'm reading your above statement correctly, your understanding is correct. This means you can do something like this:
and it will print ‘o’. -Alex ]
Sorry for the double post, but the input box doesn’t work right almost everytime I use it. Most of the time it eats my pre tags too. I then have to edit my post to get it working. Just a heads up on that.
[ I haven't had any problems using the pre tags from the input box. I have had visual problems after editing my posts where the pre tags don't render correctly, forcing me to refresh the page. I'd really like to move to a threaded comments model so I don't have to respond to comments like this, but there doesn't seem to be any good plugins for wordpress to do so. I might have to spend the time to write my own. -Alex ]
Ok, the code below does act as your code does. So, what’s up with my:
char *array = “strings” code above?
void PrintArray(char *pnArray) { pnArray[0] = ‘c’; } int main() { char anArray[2] = “a”; cout < < anArray[0] << endl; PrintArray(anArray); cout << anArray[0] << endl; } Output: a c[ I think I answered this in my response above. Let me know if it isn't clear. -Alex ]
I beefed up this lesson significantly because some of the stuff mentioned in the comments here is pretty important.
Awesome, I get it.
I’d just like to say, that all made me realize that communicating C++’s logic is an artform.
1. In your first example function, PrintArray, don’t you have to dereference the pnArray variable, like this??:
void PrintArray(int *pnArray, int nLength) { for (int iii=0; iii < nLength; iii++) cout << *pnArray[iii] << endl; }2. Minor typo: “The next logical question is, “What if we want to be able to be able to change the address”
You don’t need to do an explicit dereference when using the array index operator ([]) because the array index operator does an implicit dereference.
Hi Alex, it’s me again
There is a small error in the last example in this page, you forgot to change the comments in the code, it’s writen like that:
// This only changes pTempPtr, not pPtr!
but, in fact, for that example, pPtr has its memory address changed together with pTempPtr since it’s a reference to it.
The perils of cut and paste on display. Thanks for the note, it’s fixed now.
I can’t get your last code (with the reference to a pointer) to work.
The linker gives the following error :
1>main.obj : error LNK2019: unresolved external symbol “void __cdecl SetToSix(int *)” (?SetToSix@@YAXPAH@Z) referenced in function _main
1>D:\Users\Pieter\Documents\Visual Studio 2008\Projects\Test\Debug\Test.exe : fatal error LNK1120: 1 unresolved externals
However, I was thinking…
If you change pTempPtr = &nSix; to *pTempPtr = nSix; (so dereference it and then assign the value of nSix) it works as it should, except that it doesn’t change the address of pTempPtr, but rather the contents.
I forgot to tell you to update the function prototype above main. If it doesn’t match the actual function, the compiler will be unhappy, as you’re seeing. Update the prototype to int *& instead of int * and you’ll be fine.