Pointers and arrays are intrinsically related in C++.
Similarities between pointers and fixed arrays
In lesson 6.1 -- Arrays (part i), you learned how to define a fixed array:
1 |
int array[5] = { 9, 7, 5, 3, 1 }; // declare a fixed array of 5 integers |
To us, the above is an array of 5 integers, but to the compiler, array is a variable of type int[5]. We know what the values of array[0], array[1], array[2], array[3], and array[4] are (9, 7, 5, 3, and 1 respectively). But what value does array itself have?
The variable array contains the address of the first element of the array, as if it were a pointer! You can see this in the following program:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <iostream> int main() { int array[5] = { 9, 7, 5, 3, 1 }; // print the value of the array variable std::cout << "The array has address: " << array << '\n'; // print address of the array elements std::cout << "Element 0 has address: " << &array[0] << '\n'; return 0; } |
On the author’s machine, this printed:
The array has address: 0042FD5C Element 0 has address: 0042FD5C
Note that the address held by the array variable is the address of the first element of the array.
It’s a common fallacy in C++ to believe an array and a pointer to the array are identical. They’re not. Although both point to the first element of the array, they have different type information. In the above case, array is of type int[5], whereas a pointer to the array would be of type int *. We’ll see where this makes a difference shortly.
The confusion is primary caused by the fact that in many cases, when evaluated, a fixed array will “decay” (be implicitly converted) into a pointer to the first element of the array (essentially, losing its type information).
However, this also effectively allows us to treat fixed arrays and pointers identically in most cases.
For example, we can dereference the array to get the value of the first element:
1 2 3 4 5 6 7 |
int array[5] = { 9, 7, 5, 3, 1 }; // dereferencing an array returns the first element (element 0) cout << *array; // will print 9! char name[] = "Jason"; // C-style string (also an array) cout << *name; // will print 'J' |
Note that we’re not actually dereferencing the array itself. The array (of type int[5]) gets implicitly converted into a pointer (of type int *), and we dereference the pointer to get the value at that the memory address the pointer is holding (the value of the first element of the array).
We can also assign a pointer to point at the array:
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <iostream> int main() { int array[5] = { 9, 7, 5, 3, 1 }; std::cout << *array; // will print 9 int *ptr = array; std::cout << *ptr; // will print 9 return 0; } |
This works because the array decays into a pointer of type int *, and our pointer (also of type int *) has the same type.
Differences between pointers and fixed arrays
There are a few cases where the difference in typing between fixed arrays and pointers makes a difference. These help illustrate that a fixed array and a pointer are not the same.
The primary difference occurs when using the sizeof() operator. When used on a fixed array, sizeof returns the size of the entire array (array length * element size). When used on a pointer, sizeof returns the size of a memory address (in bytes). The following program illustrates this:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <iostream> int main() { int array[5] = { 9, 7, 5, 3, 1 }; std::cout << sizeof(array) << '\n'; // will print sizeof(int) * array length int *ptr = array; std::cout << sizeof(ptr) << '\n'; // will print the size of a pointer return 0; } |
This program prints:
20 4
A fixed array knows how long the array it is pointing to is. A pointer to the array does not.
The second difference occurs when using the address-of operator (&). Taking the address of a pointer yields the memory address of the pointer variable. Taking the address of the array returns a pointer to the entire array. This pointer also points to the first element of the array, but the type information is different (in the above example, int[5] *). It’s unlikely you’ll ever need to use this.
Revisiting passing fixed arrays to functions
Back in lesson 6.2 -- Arrays (part ii), we mentioned that because copying large arrays can be very expensive, C++ does not copy an array when an array is passed into a function. When passing an array as an argument to a function, a fixed array decays into a pointer, and the pointer is passed to the function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> void printSize(int *array) { // array is treated as a pointer here std::cout << sizeof(array) << '\n'; // prints the size of a pointer, not the size of the array! } int main() { int array[] = { 1, 1, 2, 3, 5, 8, 13, 21 }; std::cout << sizeof(array) << '\n'; // array decays into a pointer here printSize(array); return 0; } |
This prints:
32 4
Note that this happens even if the parameter is declared as a fixed array:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <iostream> // C++ will implicitly convert parameter array[] to *array void printSize(int array[]) { // array is treated as a pointer here, not a fixed array std::cout << sizeof(array) << '\n'; // prints the size of a pointer, not the size of the array! } int main() { int array[] = { 1, 1, 2, 3, 5, 8, 13, 21 }; std::cout << sizeof(array) << '\n'; // array decays into a pointer here printSize(array); return 0; } |
This prints:
32 4
In the above example, C++ implicitly converts parameters using the array syntax ([]) to the pointer syntax (*). That means the following two function declarations are identical:
1 2 |
void printSize(int array[]); void printSize(int *array); |
Some programmers prefer using the [] syntax because it makes it clear that the function is expecting an array, not just a pointer to a value. However, in most cases, because the pointer doesn’t know how large the array is, you’ll need to pass in the array size as a separate parameter anyway (strings being an exception because they’re null terminated).
We recommend using the pointer syntax, because it makes it clear that the parameter is being treated as a pointer, not a fixed array, and that certain operations, such as sizeof(), will operate as if the parameter is a pointer.
An intro to pass by address
The fact that arrays decay into pointers when passed to a function explains the underlying reason why changing an array in a function changes the actual array argument passed in. Consider the following example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <iostream> // parameter ptr contains a copy of the array's address void changeArray(int *ptr) { *ptr = 5; // so changing an array element changes the _actual_ array } int main() { int array[] = { 1, 1, 2, 3, 5, 8, 13, 21 }; std::cout << "Element 0 has value: " << array[0] << '\n'; changeArray(array); std::cout << "Element 0 has value: " << array[0] << '\n'; return 0; } |
Element 0 has value: 1 Element 0 has value: 5
When changeArray() is called, array decays into a pointer, and the value of that pointer (the memory address of the first element of the array) is copied into the ptr parameter of function changeArray(). Although the value in ptr is a copy of the address of the array, ptr still points at the actual array (not a copy!). Consequently, when ptr is dereferenced, the actual array is dereferenced!
Astute readers will note this phenomena works with pointers to non-array values as well. We’ll cover this topic (called passing by address) in more detail in the next chapter.
Arrays in structs and classes don’t decay
Finally, it is worth noting that arrays that are part of structs or classes do not decay when the whole struct or class is passed to a function. This yields an useful way to prevent decay if desired, and will be valuable later when we write classes that utilize arrays.
In the next lesson, we’ll take a look at pointer arithmetic, and talk about how array indexing actually works.
![]() |
![]() |
![]() |
I did something similar to Chris, and obtained similar results.
Here is my code:
#include
using namespace std;
void inspectIntPointer();
void inspectCharPointer();
int main()
{
cout << "CALLING inspectIntPointer()" << endl << endl;
inspectIntPointer();
cout << "CALLING inspectCharPointer()" << endl << endl;
inspectCharPointer();
return 0;
}
void inspectIntPointer()
{
const int nArraySize = 20;
int nArray[nArraySize] = {11,12,13,14,15, 16,17,18,19,20, 21,22,23,24,25, 26,27,28,29,30};
cout << "Pass *pnPtr pnPtr" << endl;
cout << "==== ====== =====" << endl;
int pass=1;
for (int *pnPtr = nArray; pnPtr < nArray + nArraySize; pnPtr++)
{
cout << pass << "\t\t" << *pnPtr << "\t\t\t" << pnPtr << endl << endl;
pass++;
}
}
void inspectCharPointer()
{
const int nArraySize = 20;
char szArray[nArraySize] = "abcdefghijklmnopqrs";
cout << "Pass *pnPtr pnPtr" << endl;
cout << "==== ====== =====" << endl;
int pass=1;
for (char *pnPtr = szArray; pnPtr < szArray + nArraySize; pnPtr++)
{
cout << pass << "\t\t" << *pnPtr << "\t\t\t" << pnPtr << endl << endl;
pass++;
}
}
Here is the output I get:
CALLING inspectIntPointer()
Pass *pnPtr pnPtr
==== ====== =====
1 11 0x7fff5fbff6b0
2 12 0x7fff5fbff6b4
3 13 0x7fff5fbff6b8
4 14 0x7fff5fbff6bc
5 15 0x7fff5fbff6c0
6 16 0x7fff5fbff6c4
7 17 0x7fff5fbff6c8
8 18 0x7fff5fbff6cc
9 19 0x7fff5fbff6d0
10 20 0x7fff5fbff6d4
11 21 0x7fff5fbff6d8
12 22 0x7fff5fbff6dc
13 23 0x7fff5fbff6e0
14 24 0x7fff5fbff6e4
15 25 0x7fff5fbff6e8
16 26 0x7fff5fbff6ec
17 27 0x7fff5fbff6f0
18 28 0x7fff5fbff6f4
19 29 0x7fff5fbff6f8
20 30 0x7fff5fbff6fc
CALLING inspectCharPointer()
Pass *pnPtr pnPtr
==== ====== =====
1 a abcdefghijklmnopqrs
2 b bcdefghijklmnopqrs
3 c cdefghijklmnopqrs
4 d defghijklmnopqrs
5 e efghijklmnopqrs
6 f fghijklmnopqrs
7 g ghijklmnopqrs
8 h hijklmnopqrs
9 i ijklmnopqrs
10 j jklmnopqrs
11 k klmnopqrs
12 l lmnopqrs
13 m mnopqrs
14 n nopqrs
15 o opqrs
16 p pqrs
17 q qrs
18 r rs
19 s s
20 *** //*** I got an upside down question mark here! Though it didn't paste in...
Program ended with exit code: 0
(the formatting looked great on my debugger :p)
I also notice that the pointer does not return a memory address to the console using COUT. I am wondering if that is a due to how COUT is interpreting the contents of the pointer, or whether the pointer is actually holding the appended string that is being sent to the console...
I also notice two other things:
1. When I initialize a character array to a length of ten cells, I am only able to initialize the array with 9 characters.
char szArray[10] = "abcdefghij"; //won't work
char szArray[10] = "abcdefghi"; //works fine
2. I am assuming that this is due to the NULL character/pointer that is being stored in the last cell of the character array? And if so, this is why I get the upside down question mark as the last *pnPtr output on the 20th pass of the for loop when I ran my program? Should I put a condition for a break to avoid passing this to the console, or is it possible or expected to have this type of pointer located in else besides the final cell of an array?
Why does:
"char szName[] = "Stefan Molyneux";
cout << szName;"
output: Stefan Molyneux
whereas:
"int x[] = { 1, 2, 3, 4, 5 };
cout << x;"
outputs the address of x?
I tried to do the same thing with a string array:
"string x[] = {"a", "b", "c"};
cout << x;"
and I got some sort of runtime error.
I CAME UP WITH DYNAMIC EXAMPLE FROM ALEX EXAMPLE...
PEOPLE CAN ENTER A WORDS OF THEIR CHOICE.. BY ICT DE IFYCENT2...
// ICT DE IFYCENT2 EXAMPLE
#include "stdafx.h"
#include
using namespace std;
int main()
{
const int nArraySize = 50;
char szName[nArraySize];
cout <> szName;
int nVowels = 0;
for (char *pnPtr = szName; pnPtr < szName + nArraySize; pnPtr++)
{
switch (*pnPtr)
{
case 'A':
case 'a':
case 'E':
case 'e':
case 'I':
case 'i':
case 'O':
case 'o':
case 'U':
case 'u':
nVowels++;
break;
}
}
cout << szName << " has " << nVowels << " vowels" << endl;
system ("PAUSE");
return 0;
}
THANKS MR.ALEX
Hi,
I had a basic doubt regarding pointers. As per the pointer arithmetic, pnPtr+1 would refer to next object of the type that pnPtr points to. Thus, if pnPtr is an integer pointer than (pnPtr+1) would be (current_address_of_pnPtr + 4).
Consider the memory block as follows :
Memory location 0: pnPtr (Some_Int_Pointer. Assuming int takes 4 bytes)
Memory location 4: pchPtr (Some_Char_Pointer. Assuming char takes 1 byte)
Memory location 5: pnPtr1 (Some_Int_Pointer)
Now when we try to access (pnPtr+2), it would try to access memory location 8 (the last byte for pnPtr1).
Isnt that an undesirable situation. Wouldnt it return some gibberish value ?
Thanks!
Oh, In my haste writing my comment I forgot to mention that the main question was if I correctly understand how the test condition of the loop was working. That last bit at the end was something I just thought to ask at the last second.
You could've used a simple if statement, and no this code doesn't work, and it will add to nVowels if there is a lowercase u.
I have a question about the use of the test-condition in the loop of this example and was hoping you could clear it up for me Alex.
const int nArraySize = 7;
char szName[nArraySize] = "Mollie";
int nVowels = 0;
for (char *pnPtr = szName; pnPtr < szName + nArraySize; pnPtr++)
{
switch (*pnPtr)
{
case 'A':
case 'a':
case 'E':
case 'e':
case 'I':
case 'i':
case 'O':
case 'o':
case 'U':
case 'u':
nVowels++;
break;
}
}
cout << szName << " has " << nVowels << " vowels" << endl;
If I understand this correctly, in pnPtr < szName + nArraySize;, szName is the name of the array which points to the address of element 0 in the array. nArraySize evaluates to 7, the actual size of the array which is equivalent to the pointer addition of pPtr (for the sake of making it similar to your earlier pointer arithmetic example) + 7.
szName points to element[0] or the "M", pPtr + 1 to "o", 2 to "l", 3 to the second "l", 4 to "i", 5 to "e", 6 to the null terminator, and because the less-than sign was used instead of <=, the final element must be 1 integer higher than the size of the array so the loop ends. In this case though since you're using addresses, would it be that pPtr + 7 be the address of the next char in memory as opposed to some integer index number?
I keep seeing stuff like:
How is that possible?
Nvm, I see it has been fixed now. Maybe it was just my crappy pc.
BTW: Thx for your tutorials!