Search

6.8 — Pointers and arrays

Pointers and arrays are intrinsically related in C++.

Array decay

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).

In all but two cases (which we’ll cover below), when a fixed array is used in an expression, the fixed array will decay (be implicitly converted) into a pointer that points to the first element of the array. You can see this in the following program:

On the author’s machine, this printed:

Element 0 has address: 0042FD5C
The array decays to a pointer holding address: 0042FD5C

It’s a common fallacy in C++ to believe an array and a pointer to the array are identical. They’re not. In the above case, array is of type “int[5]”, and it’s “value” is the array elements themselves. A pointer to the array would be of type “int *”, and its value would be the address of the first element of the array.

We’ll see where this makes a difference shortly.

All elements of the array can still be accessed through the pointer (we’ll see how this works in the next lesson), but information derived from the array’s type (such as how long the array is) can not be accessed from the pointer.

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 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 lightly 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.

Recommendation: Favor the pointer syntax (*) over the array syntax ([]) for array function parameters.

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 a 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
6.7a -- Null pointers

190 comments to 6.8 — Pointers and arrays

  • hellmet

    I have a small doubt. While messing around with C (due to a course on Coursera), I learnt that 'sizeof' is actually an 'operator' in the C and C++ language, and hence the value it returns is computed at compile time and stored in the binary itself! So there is no 'runtime' cost to find the size of the array in this case. Just to make sure (I'm also following a book on OS organization), I checked with 'Compiler Explorer' and sure enough, it explicitly moves 20 into the value. I think std::size/std::ssize work this way too. Also, I don't see any other usage where 'array' doesn't degrade to pointer. All this makes me think, is it really meaningful to say that 'array' knows its size?

    • > hence the value it returns is computed at compile time
      What you said is correct, but it's not the reason why `sizeof` is computed at compile-time. Operators are functions, they can be evaluated at run-time.

      > I checked with 'Compiler Explorer'
      Compiler explorer is a great tool, but don't use it to prove anything. You found out that the compiler you selected computes `sizeof` at compile-time, that doesn't mean that the language requires this to happen.

      > I think std::size/std::ssize work this way too
      `std::size` and `std::ssize` don't use `sizeof`, but they can be computed at compile-time when passed an array.

      > is it really meaningful to say that 'array' knows its size?
      The type of the array has a size. When an array decays it changes its type to a simple pointer, which doesn't have any information about the size. The size is a part of the type, not of the value. Once you learn about templates you'll understand how types can store information. Arrays don't use templates, it should help nonetheless.

      • hellmet

        > The size is a part of the type, not of the value.
        Ahhh! That makes sense now!

        > Once you learn about templates you'll understand how types can store information.
        I looked around and found that C doesn't have templates. Does this mean in C/C++, the mechanism by which types store the size are similar?

        I somehow can't seem to wrap my head around the fact that a type's array can also store it's size, since everything is a number in the end? In an array, there doesn't seem to be any extra block allocated around it to store its size? How does this happen!

        Thank you very much for your time and patience!

        • > Does this mean in C/C++, the mechanism by which types store the size are similar?
          You don't need templates for built-in arrays. Built-in types have their own properties and rules which can't be reproduced by using the language. The compiler knows how these types work, but they're not defined in any .cpp file or similar.
          I mentioned templates because they can be used to create custom types with attached information and to extract the size out of an array. Built-in types should function the same in C and C++.

          > there doesn't seem to be any extra block allocated around it to store its size?
          Types in C++ are only a help for the programmer and compiler. There are no types at run-time (There's an exception which doesn't matter now). Since the array's size is a part of the array's type, the size doesn't exist at run-time (Unless you use a very weird compiler that keeps the size for whatever reason).

  • Samira Ferdi

    Hi, Alex and Nascardriver!

    Is it true that how we indexing array is actually indexing through pointer arithmetic?

  • Vitra

    Thanks much but I'm still not clear about this case:

    int main()
    {
        int a[5] = {1, 2, 3, 4, 5};

        std::cout << (a + 1) << '\n';

        // Does array a decay to pointer in the line below?
        // Why a = a + 1 got an error?

        std::cout << a++ << '\n';

        return 0;
    }

  • Samira Ferdi

    Hi, Alex and Nascardriver!

    What do you think about my code? Are there anythings should I change or simplify?

    But, I have a question. The "array" argument in the my three functions is hard to tell that is it an array or just a normal variable named "array" or pointer variable named "array". The name of those argument can tell us that those are arrays. But, it is just based on the name of argument, but I'm still not sure enough. So, I think that for the function argument we should write array syntax (array[]) instead of just "array". But, what do you think about this?

    • - Wrap line 18+, 32+ in curly brackets, because they exceed 1 line.
      - Line 43, 44: Should be `constexpr`. Line 44 could use `std::ssize` (Or `std::size` if your compiler doesn't support `std::ssize` yet).

      > But, what do you think about this?
      That doesn't help. The caller knows the type of the variables they're passing.
      You can change your functions to use array syntax

      It has the same meaning, but indicates that the function wants an array.

  • Samira Ferdi

    Hi, Alex and Nascardriver!

    Because fixed-array decay into a pointer when passing it to the function, so, I can do this. But, is this way considerably a good way?

    • Please be more specific about what you mean.
      Line 3 is fine.
      Line 6 should use `array[i]`.
      Line 13 should use `std::ssize` or `std::size`.

    • Alex

      Passing an array by pointer with a separate length parameter is how things used to be done.

      In modern C++, you're better off using std::array and templates to avoid any mismatch between the array and length parameters.

    • Samira Ferdi

      Thanks Alex and Nascardriver! I really really appreciate your answers!
      I forget to make my length parameter to be const and I think it should be const. But, once again, thank you!

  • Anastasia

    Hi!
    In the conclusion to this chapter Alex wrote: "Pointers to const values are primarily used in function parameters (for example, when passing an array to a function) to help ensure the function doesn’t inadvertently change the passed in argument."
    But it seems that passing an array as const (a pointer pointing to a const value) doesn't fully ensure that the values won't be overwritten, since the values are treated as const only while accessed by the pointer the array is converted into. But nothing prevents another pointer from accessing and modifying them.

    There probably will be a more detailed explanation of how to handle this in the future chapters, looking forward to that.

    • Line 9 produces undefined behavior, you're not allowed to modify an entity after casting away its constness.
      A `const_cast` should only be used if, for whatever reason, a `const` member function isn't marked as such.

      • Anastasia

        > Line 9 produces undefined behavior

        Hm, I don't see any warnings while compiling that snippet with all the flags turned on by '-Wall' (gcc 7.4.0).

        And that wasn't the point anyway. Even if my example doesn't make a lot of sense, what bothers me is that it seems I can't be really sure the const value(s) I'm passing by address to a function won't get changed. This is confusing and disturbing, especially considering the fact that this wasn't an issue before (when most of the examples we were dealing with previously were passed to functions by value).

        • Anastasia

          *Sorry for a bit of misinformation. The conclusion I've mentioned above belongs to the chapter 6.10 'Pointers and const', not this one. I went ahead and read that chapter, because it wasn't clear to me how to deal with const and pointers(in particular with values passed to functions by address), but it is still confusing.

        • > I don't see any warnings
          There aren't warnings for everything.

          > I can't be really sure
          CPP is a low enough language to modify the entire program at run-time, you can't be sure about anything if you go by that.
          If you assume that the code is well-defined (Yours isn't), then `const` variables cannot be changed.
          Line 9 most likely changes the value of `array[0]` to `100`, but it might as well shut off your computer or start playing a song.

          • Anastasia

            I wondered why my snippet compiled at all, it seemed dangerous to do something like that even to a newbie like me. Well, I won't call that reassuring, but thank you for clarifying this a bit.

  • Harshit

    Hi Alex,

    Here in my code, I can see that an array in char does not decay into pointers whereas an array of int type does. Why does this happen?

  • Jeff

    Hi Alex.

    You mentioned that Arrays in structs and classes don’t decay... But I have some code here that shows me the array is decaying. What am I not understanding here?

  • Behzad

    I would suggest adding this code to clarify the effect of address-of operator (&) on an array:

  • Lakshya Malhotra

    Hi,
    I can't explain what's going on with my code. So I am trying to print the length of a C-string and then output the string itself. My code is outputting the length fine but it is not printing the string.

  • Jagadeesh Takkalaki

    What is the problem with above code? I am not getting entire string as output.It is printing only characters of string until space, not printing character after space.
    Expected value of ptr1:RAM SHAM BAM
    Present Output:RAM

    • * Line 7, 18, 19, 20, 32, 33: Initialize your variables with brace initializers. You used copy initialization.
      * Line 2: Initialize your variables with brace initializers.
      * Use ++prefix unless you need postfix++.
      * Don't use "using namespace".
      * Use the auto-formatting feature of your editor.
      * Line 22: Should be &&, not ||.
      * Enable compiler warnings, read them, fix them.

  • Now since <type>* and <type>[] will as parameters just pass the pointer, I did put things on the test with <type>[<length>].

    And the result was this:

    I suppose that this method DOES copy the array as a whole into the function, or am I wrong?

    • Alex

      Built-in arrays work strangely in C/C++. In such a case, the array parameter is treated as a pointer to an array, and the size information is discarded. So the array isn't copied, just the pointer to the array is copied.

      If you want to retain the type information, pass the array by reference, or better, use std::array.

  • Bad_at_Coding

    Not that this one is going to be important, but we can actually change the following

    to

  • Doug

    "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:"

    I'm not sure this is correct.  That program demonstrates decay to a pointer, nothing more.

    An array variable no more contains an address than a structure variable contains the address of the structure or an integer variable contains the address of the integer.  Instead, an array variable contains the contents of the array, just like a structure variable contains the contents of the structure.

  • Minh

    hey, i want to ask a pretty simple thing
    let us have a data structure like

    I have the pnode A's left is B and right is C. A's parent is X.
    I want to swap B to be the parent of A. So B->(A)->C
    When I do this

    What I want to do is target's parent will point to X's parent but X remain X, but the node X change into X's parent too, so how to point target's parent to X's parent. :(
    Thank you in advance.

  • Jack

    Hi guys,
    Can you just tell me what does this mean in more detail?
    (strings being an exception because they’re null terminated).
    and how could I pass the length of an array in a seperate parameter?

    • Alex

      Because strings always end in a '\0' character, we don't necessarily need to know the length of a string to know where it ends. Instead, we can keep going until we run into the '\0'.

      This doesn't work for other arrays because those arrays don't typically have an element that signals termination.

      > how could I pass the length of an array in a seperate parameter?

      Add an "int length" parameter to your function. If you know the array's length already, you can pass it as an argument. If you don't know it but your array is a fixed array, you can use the sizeof(array)/sizeof(array[0]) trick to get the length. If you have a dynamic array and you don't know the length, you're out of luck.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]