Search

6.8 — Pointers and arrays

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:

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:

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:

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:

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:

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:

This prints:

32
4

Note that this happens even if the parameter is declared as a fixed array:

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:

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:

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.

6.8a -- Pointer arithmetic and array indexing
Index
8.7 -- Null pointers

44 comments to 6.8 — Pointers and arrays

  • Chuck

    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?

  • ahrramin

    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

  • sagar24

    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!

  • Matt

    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.

  • Matt

    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?

  • Dr. HaXX

    I keep seeing stuff like:

    How is that possible?

Leave a Reply

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">