Search

6.7 — Introduction to pointers

In lesson 1.3 -- a first look at variables, we noted that a variable is a name for a piece of memory that holds a value. When our program instantiates a variable, a free memory address is automatically assigned to the variable, and any value we assign to the variable is stored in this memory address.

For example:

When this statement is executed by the CPU, a piece of memory from RAM will be set aside. For the sake of example, let’s say that the variable x is assigned memory location 140. Whenever the program sees the variable x in an expression or statement, it knows that it should look in memory location 140 to get the value.

The nice thing about variables is that we don’t need to worry about what specific memory address is assigned. We just refer to the variable by its given identifier, and the compiler translates this name into the appropriately assigned memory address.

However, this approach has some limitations, which we’ll discuss in this and future lessons.

The address-of operator (&)

The address-of operator (&) allows us to see what memory address is assigned to a variable. This is pretty straightforward:

On the author’s machine, the above program printed:

5
0027FEA0

Note: Although the address-of operator looks just like the bitwise-and operator, you can distinguish them because the address-of operator is unary, whereas the bitwise-and operator is binary.

The indirection operator (*)

Getting the address of a variable isn’t very useful by itself.

The indirection operator (*) (also called dereference operator) allows us to access the value at a particular address:

On the author’s machine, the above program printed:

5
0027FEA0
5

Note: Although the indirection operator looks just like the multiplication operator, you can distinguish them because the indirection operator is unary, whereas the multiplication operator is binary.

Pointers

With the address-of operator and indirection operators now added to our toolkits, we can now talk about pointers. A pointer is a variable that holds a memory address as its value.

Pointers are typically seen as one of the most confusing parts of the C++ language, but they’re surprisingly simple when explained properly.

Declaring a pointer

Pointer variables are declared just like normal variables, only with an asterisk between the data type and the variable name. Note that this asterisk is not an indirection. It is part of the pointer declaration syntax.

Syntactically, C++ will accept the asterisk next to the data type, next to the variable name, or even in the middle.

However, when declaring multiple pointer variables, the asterisk has to be included with each variable. It’s easy to forget to do this if you get used to attaching the asterisk to the type instead of the variable name!

For this reason, when declaring a variable, we recommend putting the asterisk next to the variable name.

Best practice

When declaring a pointer variable, put the asterisk next to the variable name.

However, when returning a pointer from a function, it’s clearer to put the asterisk next to the return type:

This makes it clear that the function is returning a value of type int* and not an int.

Best practice

When declaring a function, put the asterisk of a pointer return value next to the type.

Just like normal variables, pointers are not initialized when declared. If not initialized with a value, they will contain garbage.

One note on pointer nomenclature: “X pointer” (where X is some type) is a commonly used shorthand for “pointer to an X”. So when we say, “an integer pointer”, we really mean “a pointer to an integer”.

Assigning a value to a pointer

Since pointers only hold addresses, when we assign a value to a pointer, that value has to be an address. One of the most common things to do with pointers is have them hold the address of a different variable.

To get the address of a variable, we use the address-of operator:

Conceptually, you can think of the above snippet like this:

This is where pointers get their name from -- ptr is holding the address of variable value, so we say that ptr is “pointing to” v.

It is also easy to see using code:

On the author’s machine, this printed:

0012FF7C
0012FF7C

The type of the pointer has to match the type of the variable being pointed to:

Note that the following is also not legal:

This is because pointers can only hold addresses, and the integer literal 5 does not have a memory address. If you try this, the compiler will tell you it cannot convert an integer to an integer pointer.

C++ will also not allow you to directly convert literal memory addresses to a pointer:

The address-of operator returns a pointer

It’s worth noting that the address-of operator (&) doesn’t return the address of its operand as a literal. Instead, it returns a pointer containing the address of the operand, whose type is derived from the argument (e.g. taking the address of an int will return the address in an int pointer).

We can see this in the following example:

On Visual Studio 2013, this printed:

int *

(With gcc, this prints “pi” (pointer to int) instead).

This pointer can then be printed or assigned as desired.

Indirection through pointers

Once we have a pointer variable pointing at something, the other common thing to do with it is indirection through the pointer to get the value of what it’s pointing at. Indirection through a pointer evaluates to the contents of the address it is pointing to.

On the author’s machine, this printed:

0012FF7C
5
0012FF7C
5

This is why pointers must have a type. Without a type, when indirecting through a pointer, the pointer wouldn’t know how to interpret the contents it was pointing to. It’s also why the type of the pointer and the variable address it’s being assigned to must match. If they did not, indirection through the pointer would misinterpret the bits as a different type.

Once assigned, a pointer value can be reassigned to another value:

When the address of variable value is assigned to ptr, the following are true:

  • ptr is the same as &value
  • *ptr is treated the same as value

Because *ptr is treated the same as value, you can assign values to it just as if it were variable value! The following program prints 7:

A warning about indirection through invalid pointers

Pointers in C++ are inherently unsafe, and improper pointer usage is one of the best ways to crash your application.

During indirection through a pointer, the application attempts to go to the memory location that is stored in the pointer and retrieve the contents of memory. For security reasons, modern operating systems sandbox applications to prevent them from improperly interacting with other applications, and to protect the stability of the operating system itself. If an application tries to access a memory location not allocated to it by the operating system, the operating system may shut down the application.

The following program illustrates this, and will probably crash when you run it (go ahead, try it, you won’t harm your machine):

The size of pointers

The size of a pointer is dependent upon the architecture the executable is compiled for -- a 32-bit executable uses 32-bit memory addresses -- consequently, a pointer on a 32-bit machine is 32 bits (4 bytes). With a 64-bit executable, a pointer would be 64 bits (8 bytes). Note that this is true regardless of the size of the object being pointed to:

As you can see, the size of the pointer is always the same. This is because a pointer is just a memory address, and the number of bits needed to access a memory address on a given machine is always constant.

What good are pointers?

At this point, pointers may seem a little silly, academic, or obtuse. Why use a pointer if we can just use the original variable?

It turns out that pointers are useful in many different cases:

1) Arrays are implemented using pointers. Pointers can be used to iterate through an array (as an alternative to array indices) (covered in lesson 6.8).
2) They are the only way you can dynamically allocate memory in C++ (covered in lesson 6.9). This is by far the most common use case for pointers.
3) They can be used to pass a large amount of data to a function in a way that doesn’t involve copying the data, which is inefficient (covered in lesson 7.4)
4) They can be used to pass a function as a parameter to another function (covered in lesson 7.8).
5) They can be used to achieve polymorphism when dealing with inheritance (covered in lesson 12.1).
6) They can be used to have one struct/class point at another struct/class, to form a chain. This is useful in some more advanced data structures, such as linked lists and trees.

So there are actually a surprising number of uses for pointers. But don’t worry if you don’t understand what most of these are yet. Now that you understand what pointers are at a basic level, we can start taking an in-depth look at the various cases in which they’re useful, which we’ll do in subsequent lessons.

Conclusion

Pointers are variables that hold a memory address. The value they are pointing to can be accessed using the indirection operator (*). Indirection through a garbage pointer causes undefined behavior.

Best practice

When declaring a pointer variable, put the asterisk next to the variable name.

Best practice

When declaring a function, put the asterisk of a pointer return value next to the type.

Quiz time

Question #1


What values does this program print? Assume a short is 2 bytes, and a 32-bit machine.

Show Solution

Question #2


What’s wrong with this snippet of code?

Show Solution


6.7a -- Null pointers
Index
6.6a -- An introduction to std::string_view

293 comments to 6.7 — Introduction to pointers

  • TonyCheeze

    So, I'm just a little confused by the size of the pointer part of this section and it's really probably more of a semantics issue. You say that an executable compiled for a 32 bit system has "32 bit memory addresses" but I remember you stating that the standard for a memory address on most systems is 8 bits (1 byte). So, when you say 32 bit memory address, I'm guessing you simply mean "an address that can be represented by a 32 bit value", right? Typically in hexadecimal notation like 0x1234eeff? Thanks for any clarification!

    • nascardriver

      "a memory address on most systems is 8 bits (1 byte)."
      You're probably referring to the smallest addressable unit, which is 1 byte. A pointer can point to byte X or X+1, but it can't point to any bits between X and X+1. This is unrelated to the width of the pointer.

      • TonyCheeze

        Sorry, I think I may have just worded my question weirdly. I know that a byte is the smallest addressable memory unit, but isn't each of these units also considered a "memory address". Or at least, they all have a memory address applied to them, otherwise we wouldn't be able to access them. So that's why I'm confused, and again, I think it's a semantics issue. Forgetting about pointers for the sake of this discussion, when you say a memory address is 32 bits or 64 bits, you mean that the number (i.e. memory address) used to identify a specific addressable unit (a specific byte in memory) is represented by a 32 or 64 bit number, right? I'm certain the answer is yes, I just want to make sure that I have this crystal clear.

  • yeokaiwei

    "To look and feel like pointers, smart pointers need to have the same interface that pointers do: they need to support pointer operations like dereferencing (operator *) and indirection (operator ->). An object that looks and feels like something else is called a proxy object, or just proxy."

    "The indirection operator (*)
    Getting the address of a variable isn’t very useful by itself.
    The indirection operator (*) (also called dereference operator) allows us to access the value at a particular address:"

    I came across a confusing statement on http://ootips.org/yonat/4dev/smart-pointers.html

    Are dereferencing and indirection 2 separate things?

  • yeokaiwei

    1. *&*&*&*&*&*&*&*&*ptr = value
    &*&*&*&*&*&*&*&*ptr = memory address

    You can keep on alternating between indirect and address, ad nauseaum.

    Every pair of &* just cancels out.

    However, you can't have a && or ** next to each other.

    Might be an interesting quiz question.

    2. Quiz 2 was pretty confusing.

  • yeokaiwei

    1. Feedback. Perhaps, you would like to point out how the answers differ if you configure your compiler for x86 and x64 for the address-of operator (&)?

    x86: 004FFABC
    x64: 000000A65C31FCE4

    2. Pedagogy. In the first 2 examples, slowly building up on the prior example above is very clear and easy to understand.

    3. Quiz
    std::cout << *ptr << '\n';
    I got this part wrong.

    I seem to have been confused by this sentence
    "int *ptr{ &v }; // initialize ptr with address of variable v" and its answer "0012FF7C"

    I thought it would print the memory address but it actually prints 7.

    Here is how I went through the Quiz.

    I hope it helps with your pedagogy.

    Hopefully, you could create a simple multiple choice answer that will test our understanding.

  • winterson

    hi. you said : C++ will also not allow you to directly convert literal memory addresses to a pointer.
    but this program is compiled:

    why? please explain this . thankyou

  • Yousuf

    Specific number of bits are arranged to make a byte. And each byte has an address. Who is responsible for assigning addresses to bytes? The Operating System or Compiler? It seems that OS is responsible, then Compiler will know these address through the OS API or other mechanisms. Please correct me if I am wrong.

    Are these memory address get destroyed when we reboot our computer? If so then after each reboot all these bytes get another or different memory addresses?

    • nascardriver

      Addresses are like line numbers. They don't get created or assigned, they're just there. If you write something on the very top of a piece of paper, that's line 1, there's nothing you can do about it. It's just a way of telling where something is.

      The compiler generates code and it knows where it wrote what in relation to something. "Something" can be either the current instruction or the start of a memory segment.

      Your RAM has physical addresses, but those aren't directly accessible. Your OS performs a bunch of smart operations so that you don't have to worry about leaving pieces of RAM unused, because your program requires a contiguous chunk of memory and you can pretend to have 64-bit addresses (ie. 17179869184GiB of RAM). This allows applications to work independent of what hardware is used. Applications access virtual addresses, which the OS maps to physical addresses behind the scenes.

      When you launch an application, the OS loads it into memory. Thanks to virtual addresses, multiple processes can use the same addresses, because the OS maps them to different physical addresses. That way process A can't accidentally or intentionally access the memory of process B. It's as if each process had the entire memory for itself.

      The compiler generated code that accesses everything relative to something, so it doesn't matter at which address the OS loaded your program.

      OS used to always load programs (and parts thereof) to the same addresses, but that makes certain exploits easier. So now OS shuffle around parts of programs, which causes the addresses to be different every time you launch a program, or at least every time you reboot.

  • Karl Phazer

    In the example code of section "A warning about dereferencing invalid pointers" the function signature does not have a parameter name, is this on purpose or should 'void foo(int *&)' be 'void foo(int *&p)'?

    Also, the comment talks as you wouldn't have covered the adress-of operator above. Might this be from an earlier time when matters were discussed in different order?

    Thanks for the incredible effort this tutorial must've been!

    • nascardriver

      If you name a parameter but don't use it, you'll get a compiler warning. By leaving it unnamed, the compiler won't complain.
      The `&` in this code means that the type is a reference, it's not the address-of operator. References are covered later.

      • Karl Phazer

        I'n not going to even ask about unnamed parameters at this point but could you explain what the parameter int *& actually is? Is it a reference to a pointer to an int?

        Thanks!

        • nascardriver

          That's right. `int*&` is a reference to a pointer to an int. It would allow the function to modify the pointer passed in by the caller.

          Because the compiler doesn't look at what `foo` does, it has to assume that `p` can be valid and doesn't warn you about using an uninitialized variable.

  • ~~~~~ a little error ~~~~~
    What good are pointers?
    3) which is inefficient (covered in lesson 7.4)
    should be 'efficient'.

  • I can't quite get why we need pointers and how are they useful? If we already have a value, aren't the pointer to that value redundant?

    • samivagyok

      A good example would be, if we had a database with millions of names, and we would have to sort these names somehow (let's say alphabetic order), so our program can find it faster. Just think about it, it would be noticeably slow if our program would have to go through every name one-by-one, until it finds the desired name. With pointers we can link together names, for example by their starting letter; Aaron points to Albert points to Alex... until we run out of names starting with A. The same goes for the other letters too. This way, we can make our program more effective, as it only needs to go through a smaller segment of our database; let's say we need to find David, the program will look through only the names starting with D.

      Hope it helped, and you still keep going with learning! :D

  • As a complete newcomer to C++ I am struggling with pointers partly because I don't have the vocabulary to read expressions that use them. How are things like *ptr1; int *varname etc. pronounced?
    I know that I pronounce a+b as "a plus b" and I can get my head around &value as "address of value" but using pointers there's a difference between

    How should I read these (either aloud or just in my head)?

  • OverLordGoldDragon

    The tutorial leaves out that code can be *compiled into* a 32-bit binary/executable on a 64-bit machine, yielding 32-bit pointers (so pointers aren't *always* 32-bit on a 64-bit machine). This can be changed via Project > Properties > Configuration Manager > Platform > x64.

  • Silvet

    Hi, just wondering whether there was any reason the pointers in the examples here aren't direct initialized. I've tried them myself and it seems to work alright?

    • nascardriver

      No reason, old lesson. I updated the lesson to use list initialization, thanks for pointing out the inconsistency!

  • Sirdavos

    Just a feedback, "Best practice" sentences are in green box for other pages. They are written like others in this page.

  • Lifthra

    "but they’re surprisingly simple when explained properly.", that's a BIG flex ahah

  • Samira Ferdi

    Hi, Alex and Nascardriver!

    address-of operator returns a pointer. So, I guess it must be l-value!
    But, I can't do these things because my compiler complain that &value is not l-value!

  • May i clarify one point.
    Do pointers have their own address? I think they should as "pointers are variables that store address of a value"
    I'm trying to know this by:

    Sorry if it was said somwhere. Either i didn't understand or missed this point.

    • Alex

      Yes. You can get the address of a pointer in the same way you can get the address of any other variable (in your example, &ptr).

      • Thank you, Alex!!!
        It works)))
        The best tutorial ever!!!

        P.S.

        Why then I can't use a pointer to a pointer?

        P.P.S.
        Is the only way to donate are PayPall and bitcoin? As in Uzbekistan i can't use none of them. Is Visa possible?

        • nascardriver

          `int*` is a pointer to an int, but `&ptr` is a pointer to a pointer to an int.

          • Thank you, nascardriver!!!!

  • Zeni

    By far the best explanation of pointer then anywhere else I have seen. Thanks a lot.

    But just one point.

    In my humble opinion pointer can be understood more clearly if we initialize a pointer as int*  pntr rather then int   *pntr;.
    As to initialize an integer we use:
    Int   x;
    And then give it a value:
    x=9;
    Same way we initialize a pointer:
    Int*  pntr;
    And give it a value:
    pntr=&x;
    Now we derference it (get the value at pointer address):
    Int y=*pntr;
    Result:
    printf ("y= %d\n", y);    // 9

  • Charan

    Hey,
    We don't usually pass an uninitialised variable as a function argument.Can we do that with pointer variable?

  • Dear NASCAR driver,
    Please let me ask you: do you confirm that following program means that we can reinitialize a pointer? Regards.

  • Dear Teacher,
    Please let me point out that in first quiz variable name "value" is of same color as type "short", object "cout", and operator "sizeof". Other variable names are of different color.
    Regards.

    • Alex

      Must be a bug in the module that does the syntax highlighting. I've updated the variable name to something else so it colors correctly. Thanks!

  • Dear NASCAR driver,
    Please let me ask your instructive answer. Is *ptr another name of the variable ptr is pointing to? Regards.

  • Dear Teacher,
    Please permit me a suggestion: in the image below "Conceptually, you can think of the above snippet like this:" memory address should be below the right square so that left square points to address. Regards.

  • Dear Mr. NASCAR driver, please let me point out that pointer gets common variable's address even if it's not initialized. For example

    Regards.

  • Benur21

    Why when I print a char pointer I get this? Shouldn't it print an address?

    • Alex

      std::cout treats char pointers as C-style strings and tries to print the result as a C-style null-terminated string. Since your pointer only points to a single character (7) that isn't null-terminated, it's printing garbage beyond the first character.

  • Benur21

    In my system (Windows 10 and g++), adresses print like this: 0x71fecc

  • Benur21

    When did you talk about typeid() in previous lessons? Couldn't find it in the site index. But you used it here. Looks like it returns type name?

  • murat yilmaz

    I saw a video, they guy kept saying that a pointer is just an integer. But in this tutorial you are claiming that a pointer is just a variable. Can you please elaborate which is true?

    And is it also true that the type of a pointer doesn't change a thing about the size of a pointer? The pointer has always the same size (based on the number of bits of your computer)?

    And the type decides how to interpret the value. So If we have the type integer: we can change the value because we know the pointer starts at an address, and the next 3 bytes belong to it?

  • Ilia Lyschev

    Never ever put the asterisk next to variable name. Always put the asterisk next to type name,
    like int* somePtr.

    • Brandon

      More intuitive to put asterisk before the variable name because for example if you had the code - int* x, y - it seems like both x and y are pointers when that is not the case. Int *x, y makes it more obvious that x is a pointer and y is just an int making it the "better way", though both will work.

      • Miro

        I think it should be

        as it is part of type definition, which is different than dereferencing operator

        which operate on variable

        For the argumet about declaring more variables of same type in same line, that should be avoided anyway as it is better to declare each variables in it own line as it is easier to track changes with version control (git)

Leave a Comment

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