Search

10.11 — Pointer arithmetic and array indexing

Pointer arithmetic

The C++ language allows you to perform integer addition or subtraction operations on pointers. If ptr points to an integer, ptr + 1 is the address of the next integer in memory after ptr. ptr - 1 is the address of the previous integer before ptr.

Note that ptr + 1 does not return the memory address after ptr, but the memory address of the next object of the type that ptr points to. If ptr points to an integer (assuming 4 bytes), ptr + 3 means 3 integers (12 bytes) after ptr. If ptr points to a char, which is always 1 byte, ptr + 3 means 3 chars (3 bytes) after ptr.

When calculating the result of a pointer arithmetic expression, the compiler always multiplies the integer operand by the size of the object being pointed to. This is called scaling.

Consider the following program:

On the author’s machine, this output:

0012FF7C
0012FF80
0012FF84
0012FF88

As you can see, each of these addresses differs by 4 (7C + 4 = 80 in hexadecimal). This is because an integer is 4 bytes on the author’s machine.

The same program using short instead of int:

On the author’s machine, this output:

0012FF7C
0012FF7E
0012FF80
0012FF82

Because a short is 2 bytes, each address differs by 2.

Arrays are laid out sequentially in memory

By using the address-of operator (&), we can determine that arrays are laid out sequentially in memory. That is, elements 0, 1, 2, … are all adjacent to each other, in order.

On the author’s machine, this printed:

Element 0 is at address: 0041FE9C
Element 1 is at address: 0041FEA0
Element 2 is at address: 0041FEA4
Element 3 is at address: 0041FEA8

Note that each of these memory addresses is 4 bytes apart, which is the size of an integer on the author’s machine.

Pointer arithmetic, arrays, and the magic behind indexing

In the section above, you learned that arrays are laid out in memory sequentially.

In the previous lesson, you learned that a fixed array can decay into a pointer that points to the first element (element 0) of the array.

Also in a section above, you learned that adding 1 to a pointer returns the memory address of the next object of that type in memory.

Therefore, we might conclude that adding 1 to an array should point to the second element (element 1) of the array. We can verify experimentally that this is true:

Note that when performing indirection through the result of pointer arithmetic, parenthesis are necessary to ensure the operator precedence is correct, since operator * has higher precedence than operator +.

On the author’s machine, this printed:

0017FB80
0017FB80
7
7

It turns out that when the compiler sees the subscript operator ([]), it actually translates that into a pointer addition and indirection! Generalizing, array[n] is the same as *(array + n), where n is an integer. The subscript operator [] is there both to look nice and for ease of use (so you don’t have to remember the parenthesis).

Using a pointer to iterate through an array

We can use a pointer and pointer arithmetic to loop through an array. Although not commonly done this way (using subscripts is generally easier to read and less error prone), the following example goes to show it is possible:

How does it work? This program uses a pointer to step through each of the elements in an array. Remember that arrays decay to pointers to the first element of the array. So by assigning ptr to name, ptr will also point to the first element of the array. Indirection through ptr is performed for each element when we call isVowel(*ptr), and if the element is a vowel, numVowels is incremented. Then the for loop uses the ++ operator to advance the pointer to the next character in the array. The for loop terminates when all characters have been examined.

The above program produces the result:

Mollie has 3 vowels

Because counting elements is common, the algorithms library offers std::count_if, which counts elements that fulfill a condition. We can replace the for-loop with a call to std::count_if.

std::begin returns an iterator (pointer) to the first element, while std::end returns an iterator to the element that would be one after the last. The iterator returned by std::end is only used as a marker, accessing it causes undefined behavior, because it doesn’t point to a real element.

std::begin and std::end only work on arrays with a known size. If the array decayed to a pointer, we can calculate begin and end manually.

Note that we’re calculating name + nameLength, not name + nameLength - 1, because we don’t want the last element, but the pseudo-element one past the last.

Calculating begin and end of an array like this works for all algorithms that need a begin and end argument.

Quiz time

Question #1


Why does the following code work?

Show Solution

Question #2


Write a function named find that takes a pointer to the beginning and a pointer to the end (1 element past the last) of an array, as well as a value. The function should search for the given value and return a pointer to the first element with that value, or the end pointer if no element was found. The following program should run:

Tip

std::begin and std::end return an int*. The call to find is equivalent to

Show Solution


10.12 -- C-style string symbolic constants
Index
10.10 -- Pointers and arrays

286 comments to 10.11 — Pointer arithmetic and array indexing

  • ArmandoIG

    I know question #2 is for exercise, but is there any reason we would prefer to use pointers instead of normal array syntax to write the same function?

  • s1ngular1tea

    #include <iostream>
    #include <iterator> // for std::size

    bool isVowel(char ch)
    {
      switch (ch)
      {
      case 'A':
      case 'a':
      case 'E':
      case 'e':
      case 'I':
      case 'i':
      case 'O':
      case 'o':
      case 'U':
      case 'u':
        return true;
      default:
        return false;
      }
    }

    int main()
    {
      char name[]{ "Mollie" };
      int arrayLength{ static_cast<int>(std::size(name)) };
      int numVowels{ 0 };

      for (char* ptr{ name }; ptr != (name + arrayLength); ++ptr) //Im not sure how this works, here, 'ptr != "(name + arrayLength);"'
      {
        if (isVowel(*ptr))
        {
          ++numVowels;
        }
      }

      std::cout << name << " has " << numVowels << " vowels.\n";

      return 0;
    }

    Perhaps I have skimmed over a lesson, prior.

  • Emmanuel

    ```
    #include <iostream>

    int main()
    {
        int nums[]{ 1,2,3 };
        int arrayLength{ static_cast<int>(std::size(nums)) };
        std::cout << arrayLength << "\n";
        std::cout << std::size(nums);
        return 0;
    }
    ```
    The reference(https://en.cppreference.com/w/cpp/iterator/size) says std::size was implemented at C++17 which is my project's standard. The thing is I haven't included any of the headers it's defined in and it's running just fine. I'm working in Visual Studio 2019.

  • Derek

    I know the second quiz requirements are not flushed out since this is supposed to be a quick chapter quiz, but perhaps the solution given should actually be a while loop `while (begin < end)` in order to prevent infinite loops if the user provided begin/end in the wrong order. Unless I am mistaken, I think it would make for a better solution as an example.

    • jatrindo

      While `p != end` works for the solution, I agree with the sentiment of involving an operator< somewhere as a defensive programming measure.

      In the solution code:

      If you were to somehow make a programming error that has p point to somewhere beyond end, the loop would keep iterating (and probably crash at some point, when it dereferences a memory location it's not supposed to)

      But if you were to make the same programming error, and have your loop be (note the oeprator<) :

      The `p < end` should fail and things should be fine (I think).

      That being said, I do not trust myself _at all_ around pointers, so more caution is better than less for me.

      I would definitely sleep a little bit easier at night doing full bounds checks in my code (at the cost of some performance, sure):

    • nascardriver

      Hi Derek, we're using `!=` for compatibility with iterators, which are covered later.
      `p`, `begin`, and `end` could be non-pointer types that do not support `<`. While using `<` here wouldn't have a downside, it would cause inconsistencies with loops over iterators.

  • Ladyoftheroundtable

    actual question now. I was unsure how begin() and end() worked with multidimensional arrays, so I wrote the following code:

    this is my output:
    ╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠oº-ó@°▀
    00DFF7FC
    00DFF815
    ╠╠╠╠╠╠╠╠oº-ó@°▀

    but I would think that using the address of operator should work here, but instead I'm getting the contents of the address, and I'm unsure why.

  • Ladyoftheroundtable

    Feel pretty okay with this one. But I instantiated a variable named i for the for loop, which feels a little weird since I can just use start instead. But that feels even weirder to me. And I noticed you did similar although in your case, you used p. I also see I could cut my checks in half if I just put the failed return outside of the loop. Something to keep in mind in the future.

    also, there are a few sections in this chapter where you put the pointer operand on the variable and not the type.

    • Ladyoftheroundtable

      after some playing around I realized part of the problem was using a character array, and I'm not sure why that would be the case.

    • Ladyoftheroundtable

      So I played around a bit more. making it an int array worked better, not sure why that is. also I did find that std::end() took me right to map[5][5], but if I subtract 1 it takes me to [4][0]. How would I go about getting to [4][4] from end? As it seems that the pointer type is int[5] and goes down to the previous array.

  • Kadaj

    Is there a convention to use "p != end" instead of "p < end" when looping on pointers ?
    Everytime I see pointer/iterator examples, people tends to use the "!=" approach.

    I don't really understand how the "end()" is defined or implemented, but isn't it possible to actually get out of bounds this way ? If we do an increment by 2 or 3 for example wouldn't it be possible to access garbage data and leads to undefined behaviour ? Or it causes a runtime error ?

    I tried on my computer, but I'm not sure if I have an error because I go out of the sandbox or if it's the application which throws it.

    • Alex

      != is better as it should work with all kinds of iterator, whereas < will not work with iterators whose positions can't be compared relationally. If you're advancing by 2 or 3, you need to be careful not to go past end(), or you'll get undefined behavior. I see someone made a proposal (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4317.pdf) to address this, but it looks like it hasn't been incorporated yet.

  • Hi. I'm trying to run a char pointer, and the results are a bit strange. Do you know why?

    And the results are:

    x╠╠╠╠³ öbê°‼☺↔?L
    ╠╠╠╠³ öbê°‼☺↔?L
    ╠╠╠³ öbê°‼☺↔?L

    Thank you!

  • Is the third parameter of [code]std::count_if(std::begin(name), std::end(name), isVowel)[code], which is _isVowel_, also decay to function pointer that points to isVowel()?

  • Dante

    Hello, i can't understand one thing of the example in the section "Using a pointer to iterate through an array"

    In the next lines of code, i got it how the *ptr variable works, but not how the condition is doing it.

    Why there's no problem using two different variables in the for loop condition?

  • SuperNoob

    The site is going through some issue I guess. If I upload a question, it doesn't show the uploaded question after I reload the page. (yeah I have checked and rechecked using different browsers) I have posted two questions previously from "thedeathangeldrax....." email and none of the questions appears in the site sadly. Please fix it if there is a problem.

  • SuperNoob

    Consider the code below. I am having some confusions.

    Output:

    0xbed9c574
    0xbed9c578
    0xbed9c574
    0xbed9c57c

    Question: What's the difference between "arr" and "&arr"? Why do "arr+1" and "&arr+1" print different memory addresses?

    • kanaetochi E okiyi

      arr is a decayed array that now points to where the first element(arr[0]) is stored and &arr returns the memory address where arr is stored, which is also where arr[0] is stored, so as a result no difference. It's kind of like how std::cout << true; is also std::cout << 1; It is just a coincidence of similar results from different mechanisms.

      &arr+1 literally translates to (&arr)+1, so if you want to get the address of arr+1, you'd have to do something like this:

  • SuperNoob

    Explain me what's going on in the code below. I am having some confusions.

    Output:
    0xbef95574
    0xbef95578
    0xbef95574
    0xbef9557c

    Question: What's the difference between "arr" and "&arr"? Why do "arr+1" and "&arr+1" print different memory address.

    • Volatus

      It actually seems to be considering that (&arr)+1 a long-native expression for some reason. As you may see, it multiplies the addition by 4, and not two. I tested it with subtraction as well, and it also subtracts by eight.

  • Waldo Lemmer

    Here's my answer to Question #2:

    It works, but is it good?
    Could you explain your thought process when you wrote that function? Here's mine:
    1. I need to search for something, so I'll use a for loop

    2. I need to loop through all memory addresses between `begin` and `end`
    2.1 I'll use an integer offset as the loop variable
    2.2 The last address that needs to be ckecked is `end`, so I'll use `i <= (end - begin)` as the condition

    3. I need to test whether the value of the current address is equal to `value`
    3.1 The current address is `begin + i`
    3.2 The value of the current address is therefore `*(begin + i)`
    3.3 That needs to be equal to value, so the condition is `if (*(begin + i) == value)`

    4. If nothing is found, the loop will exit, and since the end pointer needs to be returned in this case, I'll `return end` after the loop.

    Your solution seems more elegant than mine, and while I understand how it works, I don't understand how you got to it.

    • Sahil

      Your thought process was very good imo, I did pretty much the same thinking, but my solution was the same as the one they've provided. I think at 2.1 you did a bit extra work since you wanted to use an integer offset but incrementing a pointer (++ptr) is the same as *(ptr+1) so you didn't really need to have an integer as your loop control variable, instead copy the begin to another pointer variable and use it to control the loop, and then return it if the condition is met.

      • Azazel

        I agree with Sahil, your code was very good. But both you and the original lesson example have unnecessary extra work, in your case you wouldn't need the integer 'i' to iterate through the loop, and the original lesson didn't need to assign the pointer 'begin' to another pointer called 'p', if they used the own 'begin' pointer to iterate it wouldn't be any problem, as changing it's value only changes to where it is point at, the value it's point at wouldn't change.

  • Martin Marek

    There is a reference to the lesson "6.8 -- Pointers and arrays" in the second section of this lesson. However, the lesson number is different now (9.10). Consequently, in lesson 9.10 you refer to lesson 6.1 -- Arrays (I) in the very beginning. Instead, it should be 9.1. I know, referencing can be a pain (upvote for LaTeX).

  • Patrick

    I read that each byte of memory is given an address. So for a 4 byte object, will it take up 4 addresses of memory? (with its actual address being the first address in the group of 4)

  • kio

    Hi Nascar and Alex,

    Your solution for second condition is spot on :D

  • So close on Question 2! Just had some scoping and syntax issues, but my initial answer was really close to the solution on first try.

    I added some functionality to receive the number as an user input and recycled some code from 9.3 to find the index of the number too! :)

  • Bryan

    This is my answer for question #2; is it ok to directly use the pointer to the beginning of the array that was passed in as an argument? I see that the official solution didn't do that. Also, is it ok to use operator< instead of "!="?

  • yeokaiwei

    1. Feedback. Quiz 2 was hard to start. This was my initial thoughts. It's junk. The main difficulty lay in what do I initialize the paramaters as, *ptr or ptr.

  • Uyph

    Hi. I'm sorry if the code looks messy, I'm still working on it but there's an error on line

    that prevents me from advancing further. It says 'cannot convert char** to char* in intialization'.

    Can you help me check?

    • nascardriver

      `theString` is a `char[]`, which is the same as a `char*`. The `&` operator returns a pointer to the variable. The variable is a `char*`, so a pointer to a `char*` is a `char**`.

  • Naïm Gómez Cano

    How can I get the value of a 2D array using  adding and indirection, for example: int arr[3][2] = {{7,2}, {3,4}, {5,6}};
    I have tried things like *(arr + 3) to get the value of position arr[1][0] but it doesn't work. It returns me a memory address.
    Sorry for the spelling, I don't speak English. Thanks!

    • nascardriver

      `*(arr + 3)` would return the subarray at index 3 (Which doesn't exist). Let's stick with `arr[1][0]`, because that's a valid element.
      First you need to find the subarray at index 1, that's `*(arr + 1)`.
      Then you get get element 0 from this subarray using another indirection `*(*(arr + 1) + 0)`.

  • Glenn

    Just a brief observation: you've begun to use the term 'dereference' in this lesson, but in a way that suggests we should know what it means. I don't *think* you've defined it for us previously, so something of an intro to the implications of the term might be useful here. Thanks!

    • nascardriver

      "Dereference" was briefly mentioned in lesson 6.7, it's a synonym for "indirection through". I must have forgotten to update this lesson, thanks for pointing out the inconsistency!

  • ali imanzadeh

    this program with ptr++ points to next memory address not next integer

    and this show me:
    0 : 0000001267EFF800
    1 : 0000001267EFF804
    2 : 0000001267EFF808
    3 : 0000001267EFF80C
    4 : 0000001267EFF810
    5 : 0000001267EFF814
    6 : 0000001267EFF818
    7 : 0000001267EFF81C
    8 : 0000001267EFF820
    9 : 0000001267EFF824

    These blocks are in a row but you said ptr++ points to next integer
    please explain me thanks

  • Alessandro Lodi

    On my machine, if I run this code

    I get the following output:

    the memory add of the first element is: 00F6FD98
    the memory add of the second element is: 00F6FD9C
    the memory add of the third element is: 00F6FDA0
    5 at the memory address: 00F6FD8C

    Why the two memory addresses for the second element are different?

    • nascardriver

      is the address of the `found` variable, not of the array element.

      `found` is a pointer to the found element. `&found` is a pointer to the pointer to the found element.

Leave a Comment

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