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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> void foo(int *ptr) { *ptr = 6; } int main() { int value{ 5 }; std::cout << "value = " << value << '\n'; foo(&value); std::cout << "value = " << value << '\n'; return 0; } |
The above snippet prints:
value = 5 value = 6
As you can see, the function foo() changed the value of the argument (variable value) through pointer parameter ptr.
Pass by address is typically used with pointers, which most often are used to point to built-in arrays. For example, the following function will print all the values in an array:
1 2 3 4 5 6 7 |
void printArray(int *array, int length) { for (int index{ 0 }; index < length; ++index) { std::cout << array[index] << ' '; } } |
Here is an example program that calls this function:
1 2 3 4 5 |
int main() { int array[6]{ 6, 5, 4, 3, 2, 1 }; // remember, arrays decay into pointers printArray(array, 6); // so array evaluates to a pointer to the first element of the array here, no & needed } |
This program prints the following:
6 5 4 3 2 1
Remember that fixed arrays decay into pointers when passed to a function, so we have to pass the length as a separate parameter.
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void printArray(int *array, int length) { // if user passed in a null pointer for array, bail out early! if (!array) return; for (int index{ 0 }; index < length; ++index) std::cout << array[index] << ' '; } int main() { int array[6]{ 6, 5, 4, 3, 2, 1 }; printArray(array, 6); } |
Passing by const address
Because printArray() doesn’t modify any of its arguments, it’s good form to make the array parameter const:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void printArray(const int *array, int length) { // if user passed in a null pointer for array, bail out early! if (!array) return; for (int index{ 0 }; index < length; ++index) std::cout << array[index] << ' '; } int main() { int array[6]{ 6, 5, 4, 3, 2, 1 }; printArray(array, 6); } |
This allows us to tell at a glance that printArray() won’t modify the array argument passed in, and will ensure we don’t do so by accident.
Addresses are actually passed by value
When you pass a pointer to a function, the pointer’s value (the address it points to) is copied from the argument to the function’s parameter. In other words, it’s passed by value! If you change the function parameter’s value, you are only changing a copy. Consequently, the original pointer argument will not be changed.
Here’s a sample program that illustrates this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#include <iostream> void setToNull(int *tempPtr) { // we're making tempPtr point at something else, not changing the value that tempPtr points to. tempPtr = nullptr; // use 0 instead if not C++11 } int main() { // First we set ptr to the address of five, which means *ptr = 5 int five{ 5 }; int *ptr{ &five }; // This will print 5 std::cout << *ptr; // tempPtr will receive a copy of ptr setToNull(ptr); // ptr is still set to the address of five! // This will print 5 if (ptr) std::cout << *ptr; else std::cout << " ptr is null"; return 0; } |
tempPtr receives a copy of the address that ptr is holding. Even though we change tempPtr to point at something else (nullptr), this does not change the value that ptr points to. Consequently, this program prints:
55
Note that even though the address itself is passed by value, you can still dereference that address to change the argument’s value. This is a common point of confusion, so let’s clarify:
- When passing an argument by address, the function parameter variable receives a copy of the address from the argument. At this point, the function parameter and the argument both point to the same value.
- If the function parameter is then dereferenced to change the value being pointed to, that will impact the value the argument is pointing to, since both the function parameter and argument are pointing to the same value!
- If the function parameter is assigned a different address, that will not impact the argument, since the function parameter is a copy, and changing the copy won’t impact the original. After changing the function parameter’s address, the function parameter and argument will point to different values, so dereferencing the parameter and changing the value will no longer affect the value pointed to by the argument.
The following program illustrates the point:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#include <iostream> void setToSix(int *tempPtr) { *tempPtr = 6; // we're changing the value that tempPtr (and ptr) points to } int main() { // First we set ptr to the address of five, which means *ptr = 5 int five{ 5 }; int *ptr{ &five }; // This will print 5 std::cout << *ptr; // tempPtr will receive a copy of ptr setToSix(ptr); // tempPtr changed the value being pointed to to 6, so ptr is now pointing to the value 6 // This will print 6 if (ptr) std::cout << *ptr; else std::cout << " ptr is null"; return 0; } |
This prints:
56
Passing addresses by reference
The next logical question is, “What if we want to change the address an argument points to from within the function?”. Turns out, this is surprisingly easy. You can simply pass the address by reference. The syntax for doing a reference to a pointer is a little strange (and easy to get backwards). However, if you do get it backwards, the compiler will give you an error.
The following program illustrates using a reference to a pointer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#include <iostream> // tempPtr is now a reference to a pointer, so any changes made to tempPtr will change the argument as well! void setToNull(int *&tempPtr) { tempPtr = nullptr; // use 0 instead if not C++11 } int main() { // First we set ptr to the address of five, which means *ptr = 5 int five{ 5 }; int *ptr{ &five }; // This will print 5 std::cout << *ptr; // tempPtr is set as a reference to ptr setToNull(ptr); // ptr has now been changed to nullptr! if (ptr) std::cout << *ptr; else std::cout << " ptr is null"; return 0; } |
When we run the program again with this version of the function, we get:
5 ptr is null
Which shows that calling setToNull() did indeed change the value of ptr from &five to nullptr!
There is only pass by value
Now that you understand the basic differences between passing by reference, address, and value, let’s get reductionist for a moment. :)
In the lesson on references, we briefly mentioned that references are typically implemented by the compiler as pointers. This means that behind the scenes, pass by reference is essentially just a pass by address (with access to the reference doing an implicit dereference).
And just above, we showed that pass by address is actually just passing an address by value!
Therefore, we can conclude that C++ really passes everything by value! The properties of pass by address (and reference) come solely from the fact that we can dereference the passed address to change the argument, which we can not do with a normal value parameter!
Pass by address makes modifiable parameters explicit
Consider the following example:
1 2 3 4 5 6 7 8 9 |
int foo1(int x); // pass by value int foo2(int &x); // pass by reference int foo3(int *x); // pass by address int i {}; foo1(i); // can't modify i foo2(i); // can modify i foo3(&i); // can modify i |
It’s not obvious from the call to foo2() that the function can modify variable i, is it?
For this reason, some guides recommend passing all modifiable arguments by address, so that it’s more obvious from an existing function call that an argument could be modified.
However, this comes with its own set of downsides: the caller might think they can pass in nullptr when they aren’t supposed to, and you now have to rigorously check for null pointers.
We lean towards the recommendation of passing non-optional modifiable parameters by reference. Even better, avoid modifiable parameters altogether.
Pros and cons of pass by address
Advantages of passing by address:
- Pass by address allows a function to change the value of the argument, which is sometimes useful. Otherwise, const can be used to guarantee the function won’t change the argument. (However, if you want to do this with a non-pointer, you should use pass by reference instead).
- 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 via out parameters.
Disadvantages of passing by address:
- Because literals (excepting C-style string 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.
When to use pass by address:
- When passing built-in arrays (if you’re okay with the fact that they’ll decay into a pointer).
- When passing a pointer and nullptr is a valid argument logically.
When not to use pass by address:
- When passing a pointer and nullptr is not a valid argument logically (use pass by reference).
- When passing structs or classes (use pass by reference).
- When passing fundamental types (use pass 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.
Rule
Prefer pass by reference to pass by address whenever applicable.
![]() |
![]() |
![]() |
We lean towards the recommendation of passing non-optional modifiable parameters by reference. Even better, avoid modifiable parameters altogether.
Hi.
I have no idea about the word non-optional parameter.
(And what is the difference between optional parameter and non-optional parameter?)
Thank you in advance!
I think their point is that whenever you can, avoid using modifiable parameters. I think a non-optional modifiable parameter would be a situation where you have no other solution but to use a modifiable parameter (for example using out parameters).
1. This chapter is definitely better before Chapter 6.x
https://www.learncpp.com/cpp-tutorial/6-x-chapter-6-comprehensive-quiz/
2. Feedback
One of the confusing things about C++ is the lack of matchcase.
void setToNull(int *tempPtr) has a * in it but when you call setToNull(ptr).
Notice ptr has no * in it.
I'm just pointing out that the confusion is very strong here and might be a core source of confusion globally.
3. This is really good tip earlier in the lesson.
"Prefer pass by reference & to pass by address * whenever applicable."
"Remember that fixed arrays decay into pointers when passed to a function, so we have to pass the length as a separate parameter."
Do dynamic array not decay into pointers?
Dynamic arrays do not decay into pointers, because they are already pointers. Recall from chapter 9.14 how to initialize a dynamic array:
(9.14 for reference: https://www.learncpp.com/cpp-tutorial/dynamically-allocating-arrays/ )
Also, if you want to see this for yourself, compile and run the following code:
Oh, thanks Will.
This helped to clarify something basic.
In the snippet below, why is it that array already decayed to a pointer to the first element but we can still print out the entire array using the pointer?
I mean shouldnt the line be
Sorry if this is a silly one, thank you
The reason, at least to my understanding, is that the [] operator (in this case) converts to pointer arithmetic and then dereferencing.
So, when we write "array[index]" this is really being converted (by the compiler??) to "*(array + index)", which is why the whole thing works.
As a side note, that's why "index[array]" will work as well! That converts to "*(index + array)" which is the same as "*(array + index)"
I visited the lesson again and its exactly what you said. thank you
In the snippet below, why is it that array already decayed to a pointer to the first element but we can still print out the entire array using the pointer? I mean shouldnt the line be
Sorry if this is a silly one, thank you
Decay doesn't affect how you access elements. It only prevents you from obtaining the array's size.
`array` is an `int*`, so `array[index]` is an `int`. You can't perform indirection through an `int`, only through pointers.
Thank you
Two minor suggestions:
* In line 8 of the 4th code snippet: std::cout instead of cout
* Please place the last line of this lesson
"Rule: Prefer pass by reference to pass by address whenever applicable."
in a beautiful green Rule box.
You say: When you pass a pointer to a function by address, the pointer’s value (the address it points to) is copied from the argument to the function’s parameter.
That seems illogical. It seems to imply that passing pointer by address == passing pointer by value.
Do you mean: when you pass a variable by address via a pointer to the variable address, the pointer is passed by value to the function called?
?
Updated to
"When you pass a pointer to a function [...]"
Thanks for letting us know that this was confusing!
>>The properties of pass by address (and reference) comes..
Should be either 'come' or 'property' :)
shouldn't it be 'passing'?
>> Even better, avoid modifiable parameters altogether.
Then, how come we modify an argument in this case?
>>When passing a pointer and nullptr is not a valid argument logically (use pass by reference and dereference the pointer argument).
would you please elaborate on this?when we chose to sen pass by reference not address, the how come dereference the pointer?There would be no pointer.
Lesson updated for point 1 and 3.
We modify the arguments in this lesson to show that it can be done. Sometimes arguments need to be modified. The rule isn't "Don't ever do this", but "avoid".
In the example for the section "Addresses are actually passed by value", I think the comment in the code should be changed.
Instead of: "we're making tempPtr point at something else, not changing the value that tempPtr points to"
I think it should be "we're making tempPtr point at something else, not changing the value that ptr points to"
Original:
Modified:
hello,I'm back with my questions as always xd, I hope you are not bored with me.
1:You say passing by reference essentially just a pass by address:
but when we pass by address the parameter "type *name e.g int *x" this will create a ptr and use 4 byte of the memory for it and then assign it the address we give it in the caller.but in case of references from what i researched so far and what you say here is like :they don't occupy any memory,they are just like a name alias. so if you say that they act just like pass by address behind the scenes,it means that, the 4 byte allocated memory for pass by address is also allocated behind the scenes for pass by reference as well.
I think these two are in contradiction,because regarding the second context we cannot assume they don't occupy any memory, and I also saw on stackOverFlow people have different mindsets about how references are created some say there will be no more memory allocated for them some say this is not guaranteed.I dunno which side should I believe in.what do you think ?
one more thing I encountered is that some ppl references not always work like ptrs behind the scenes they can be like sth else but don't remember exactly what he said what about this one ?
I'm totally struggling with these right know I hope You can help me with that.
oh here is the StackOverFlow link:
https://stackoverflow.com/questions/52177329/differences-between-passing-by-reference-and-passing-by-address
thanks dear nasCarDriver for your responses which helped me a lot so far.
References can been seen as aliases, but they're pointers behind the scenes. A reference variable takes up the same space and has the same cost as a pointer.
Then come compiler optimizations, which can remove references and pointers if possible. For example if you have a local reference to a local variable, there's no reason to create another variable. But the same goes for pointers.
thanks for answer.
1_"References can been seen as aliases, but they're pointers behind the scenes. A reference variable takes up the same space and has the same cost as a pointer.":
does it apply on all compilers ? cuz I've heard that's the thing that some compilers do and some don't.
2_Then come compiler optimizations, which can remove references and pointers if possible. For example if you have a local reference to a local variable, there's no reason to create another variable. But the same goes for pointers:
but that doesn't seems rational... I mean we create a ptr which has an address and a value.we can take the address any time we want. so it should mean it's still there.so how does the compiler do this process "optimization" and what does it do to some variables ?
1_ Different compilers can do different things
2_ Compilers can almost do whatever they want as long as they don't change the program's behavior. For example, every optimizer should turn this
into this
The program still does the same, so the compiler is allowed to remove all of our unnecessary code.
If you use the pointer for something, eg. you print it, the compiler has to store 123 somewhere so that it can take the address of it. This code
might turn into
oh thanks for answer. I am in a discord channel and I asked how can I know more about those things under the hood and they said I gotta learn assembly language for that is that a good idea ?
I also got a question about this "When passing structs or classes (use pass by reference)"
if 'pointer and a reference' have the same cost,why do you say we should prefer references for structs and classes?
is it because they cannot be null anymore & they are safer & thus less error prone?
2:"Because a copy of the argument is not made, it is fast, even when used with large structs or
classes":it is because instead of copying the struct itself we copy the address of it and assign it to the parameter.how much memory is taken up for doing this then ?
> is [learning assembly language] a good idea
Yes. Assembly is straightforward, but can be boring to learn. You just need to be able to read it, then you can use tools like compiler explorer to compare different pieces of code.
> why do you say we should prefer references for structs and classes?
Pointers can be nullptr and have a more verbose syntax. Pointers should only be used if a parameter is optional.
> it is because instead of copying the struct itself we copy the address of it
Correct. An address is 8 bytes wide on a 64 bit system. In addition to passing the address, there's the cost of the indirection (*variable) (This also happens for references). That's why passing fundamental types by reference is slower than passing them by value.
oh ok thanks for everything!
Looking at your lessons and at the comments I learnt that following code is wrong as the ptr2 will not "know" that it points to a block of memory (unlike ptr1) and not to just a single int.
If I am not wrong I could use a reference to make it work as expected.
But if I wanted to use only pointers is the following code correct?
Your first snippet is correct. Pointers to arrays don't have size information that is accessible in code. But for the very reason of freeing the memory, they secretly know their size. `delete[]` accesses this secret size so it knows how much memory to free. This size cannot be accessed (without causing undefined behavior) from within your code.
But using reference or a pointer to an original pointer inside doSomething function can I access this "secret" information about how much memory to free?
you can never access it, only `delete` can.
This is all you need. No references and not pointers to pointers.
Thanks a lot. Appreciate your help.
Hi!
Could you tell me what's the difference between twp codes below?
The first one failed to compile with error:
'void changePtr(int *&)': cannot convert argument 1 from 'int *' to 'int *&'
Thank!
--1st--
--2nd--
`&x` is an rvalue, you can't bind a non-const reference to it. Otherwise you'd have line 7 make your program explode.
How to disable that "not used variable" warning? It is good for developing and debugging but not for learning. All the time I have to do some little (unrelated to example code) thing, just to satisfy the compiler. It's enough. That setToNull () function and many another examples won't compile just because of that.
LE: nvm, I deleted -Werror from my makefile. Now it is just a warning but compiles.
Hi,
In the "Pros and cons of pass by address", and in the advantages part, the first one
[Pass by address allows a function to change the value of the argument, which is sometimes useful. Otherwise, const can be used to guarantee the function won’t change the argument. (However, if you want to do this with a non-pointer, you should use pass by reference instead).]
can be considered as the advantage for Pass by Ref in section 7.3 but it is not mentioned there. I wonder why.
thanks
So a copy of the argument is not made but a copy of the address is?
Correct
You say that a disadvantage of pass by address is the fact that literals don't have addresses, but this is false. If I try to pass the literal "Hi" as a const char[] type in a parameter, it will have an address, right?
String literals are an exception case, as they are l-values, whereas other literals are r-values.
I was doing some testing, and I found that you can't pass a string literal as a reference. Why is that, if it has an address?
Sorry! It should read.
Also, the above narrative states that passing by address and dereferencing a pointer is generally slower than the process of passing by value. How does this compare to the process of passing by reference.
So for clarity, if a function needs to return (/change) 2 values (I cannot think of a practical example yet, but just in case) which is the best method?
Pass by address with dereferencing nullptr risk. Or by reference , using "out" suffix, but at a risk of looking like passing by value.
Also, the above narrative states that passing by value and dereferencing a pointer is generally slower than the process of passing by value. How does this compare to the process of passing by reference.
Thanks.
> which is the best method?
I can't answer that question. If you're passing by pointer, you should make sure that a `nullptr` doesn't cause issues.
Pointers and references are the same under the hood.
Since as mentioned in the lesson, references are just implemented with pointers under the hood, are references just syntactic sugar so that I don't have to call the function with the ampersand (as in line 15)?
If you change line 12 to
then both are the same. There are small differences, like references not being able to be null. Mostly it's a difference in syntax.
Ahh yes line 12, my bad!
So references vs address is more or less a preference and ease of use right (apart from the slight differences as you reminded me, ref can't be null)?
Yes.
References are primarily used when something is passed by const reference to prevent a copy.
I don't usually pass by non-const reference, as it can be misleading. Take line 7 for example. The caller can't know that `someptr` can be modified without looking at `setToNull`. Line 15 on the other hand is pretty obvious.
Okay perfect!
A small clarification ...
The functions are effectively documented in their respective header files. So a diligent programmer should look at the function declaration and if it does not have a const parameter, they should expect that the function might change the arguments, right? Say I was to make a small library, the reference syntax is much cleaner. Would preferring the reference syntax make it less 'C++' style or unintuitive?
> header
Yes, it's clear from looking at the header. But you'd have to look at the header. Passing by pointer is clearer even to someone who doesn't know the function.
> Would preferring the reference syntax make it less 'C++' style
There's no such thing. Some other languages have a uniform style. In C++ the style varies between programmers and projects.
> Would preferring the reference syntax make it [...] unintuitive?
I'd say so, yes. When I see a function call like line 7, my intuition tells me that the argument doesn't get modified.
In practice, modifying arguments is rare. You'll learn about classes later, they allow an object to be modified by using a member function, eg.
Thank you!
I understand that I can use a for loop instead, however, is this a viable alternative, or is it prone to error. Lets assume all of the values in our array are not zero.
`input` should be a `const int* input` or `const int input[]`. Don't pass a pointer by reference unless you modify it.
Don't use `using namespace std;`.
Line 8 should be `++i`.
Don't use `std::endl` unless you need to flush the stream. Most of the time '\n' suffices.
Use a for-loop if you know how often your loop is going to run. You don't know that, so a while-loop is correct. You don't need an index though.
Am I understanding this correctly?
yes
Why doesn't this code print "NULL FOUND" ?
The array in main decays to a pointer when passed to printArray, pointing to the first element of the array, which has a non-zero address. So the array parameter of printArray is non-zero.