0

I've been reading the book "Jumping into C++", and am currently reading up on pointers. This is an exercise from the book:

"What are the final values in x, p_int, and p_p_int in the following code:

int x = 0;
int *p_int = & x;
int **p_p_int = & p_int;
*p_int = 12;
**p_p_int = 25;
p_int = 12;
*p_p_int = 3;
p_p_int = 27;

And I get the error:

"Assigning to int * from incompatible type int"

On the last three lines.

I don't know why this is happening, and would appreciate any insight.

Furthermore, assuming that this code somehow works, I would think that since all pointers are just pointing towards a single memory (**p_p_int -> *p_int -> x), the last value assignment would dictate the final value of the memory location. However in the book, the answer is:

x = 25, p_p_int = 27, p_int = 3

Is this correct? If so, could anyone explain this to me?

anastaciu
  • 23,467
  • 7
  • 28
  • 53
  • 1
    Since this code is not, and never was, valid C++, it is meaningless to discuss what the output is. Perhaps you should try [a different book](https://stackoverflow.com/a/388282). – 1201ProgramAlarm Feb 07 '20 at 23:35
  • If the other examples in the book are similar to this one, I would pick a different book. There's a curated list [here](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) – Paul Sanders Feb 07 '20 at 23:36
  • That book is old (2013) and says in the introduction that it does not include anything from the C++11 standard. – 1201ProgramAlarm Feb 07 '20 at 23:40
  • Does this answer your question? [Why can't I directly assign an int to an int pointer like this: int *p = 6;?](/q/44966943/90527) – outis Nov 13 '22 at 21:47

4 Answers4

2

As you can see p_int has as static type int *, and on the last three line you are doing something like

p_int = 12;

where 12 is an int, and so can't be assigned to a pointer (same thing to the other 2 lines)

However, x p_int p_p_int have different values because x contains the integer value, p_int the address of x and p_p_int the address of p_int.

Also keep in mind that this is a """"compilable"""" C code, not C++, but you will get 3 warnings also on C compiler telling you that you are assigning to a pointer an incompatible type

Alberto Sinigaglia
  • 12,097
  • 2
  • 20
  • 48
  • Thanks for your answer, I'd just like a bit extra clarification. I understand what you say about them all having different values, but by dereferencing the pointers appropriately, they would all lead to the same value held in the memory, am I right? So for example, **p_p_int, *p_int, and x would yield the same value? – giannis101 Feb 07 '20 at 23:44
  • yes exactly, but they are themself a "variable" with a value inside, that change between each one – Alberto Sinigaglia Feb 07 '20 at 23:46
2

This part:

p_int = 12;
*p_p_int = 3;
p_p_int = 27;

There is no way this compiles with any confirming C++ compiler. It is just ill-formed. Even if you used casts to make it work, you'd cause undefined behavior. Try a different book.

Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
  • No, you're wrong. There is no undefined behavior. ["A value of any integral or enumeration type can be converted to a pointer type."](https://en.cppreference.com/w/cpp/language/reinterpret_cast#Explanation) There would be undefined behavior if the resulting pointer was de-referenced, but that does not occur here. – JaMiT Feb 09 '20 at 00:19
  • @JaMiT what you're saying is a major misconception. You *can* convert any integral type to a pointer, but then evaluating that pointer (`p_int = (int*)12;` evaluates `p_int`) causes undefined behavior if that pointer doesn't have a valid value categorized in [basic.compound](http://eel.is/c++draft/basic.compound#3). Even if you don't dereference it. Hence `p_int = (int*)12` causes undefined behavior unless `12` happens to fit in one of the aforementioned categories, which is unlikely. – Aykhan Hagverdili Feb 09 '20 at 07:05
  • Since you like quoting the standard, could you quote the part that says assigning an invalid pointer is undefined? Your current explanation is lacking. (For example, when `p_int` is evaluated as part of `p_int = (int*)12`, there is a valid pointer in `p_int`. It's not until after the assignment that it gets an invalid value.) – JaMiT Feb 09 '20 at 22:21
  • @JaMiT C17: §6.3.2.3/5 *"An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a **trap representation**.68)"* – Aykhan Hagverdili Feb 09 '20 at 23:55
  • @JaMiT Also C17 §6.2.6.1/5 *"Certain object representations need not represent a value of the object type. If the stored value of an object has such a representation and is read by an lvalue expression that does not have character type, the behavior is undefined. If such a representation is produced by a side effect that modifies all or any part of the object by an lvalue expression that does not have character type, the behavior is undefined.50) Such a representation is called a trap representation."* – Aykhan Hagverdili Feb 09 '20 at 23:55
  • @JaMiT *"t's not until after the assignment that it gets an invalid value"* - The result of `p_int = (int*)12` as a whole expression is `p_int` after it's been assigned the invalid value. Meaning it is evaluated with the invalid value. – Aykhan Hagverdili Feb 10 '20 at 00:02
  • _§6.3.2.3/5:_ Specifies defined behavior. _§6.2.6.1/5_ Describes a trap representation, but that need not be relevant (at best you've shown that a trap representation **might** come into play). _Evaluation:_ The result of `p_int = (int*)12` as a whole expression is an lvalue referring to `p_int` [[expr.ass](http://eel.is/c++draft/expr.ass#1)]. The evaluation of this lvalue expression determines the identity of an object [[basic.lval](http://eel.is/c++draft/basic.lval#1)]. The potential for UB comes after conversion to a prvalue (the *value* of that object), which does not occur here. – JaMiT Feb 10 '20 at 02:29
  • @JaMiT it is very relevant. It says forming bit patterns that are not valid is UB. And any value that does not fit to [1. Null Pointer Value, 2. Pointer to an object, 3. Pointer to 1 passed an object, 4. Pointer to an object that is now out of scope (invalid)] is not valid. A pointer formally cannot hold any other value. Pointers are abstractions, mind you. They are not mere ints. – Aykhan Hagverdili Feb 10 '20 at 07:39
  • @JaMiT I asked that question [here](https://stackoverflow.com/q/60147025/10147399). – Aykhan Hagverdili Feb 10 '20 at 09:10
  • No, it discusses bit patterns that are not **values of the object type**; there is no mention of "valid". Take another look at the lead-in to the list in [basic.compound]: "Every **value of pointer type** is one of the following:" (emphasis added). Being in the list, an invalid pointer value is a value of pointer type. Also, the name tells you it is a value of pointer type (invalid *pointer value*). To get a trap representation, you need something outside the list, something that is neither a valid nor an invalid pointer value because it is not a pointer value at all. – JaMiT Feb 11 '20 at 05:24
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/207568/discussion-between-jamit-and-ayxan). – JaMiT Feb 11 '20 at 06:03
1
int *p_int = & x;
int **p_p_int = & p_int;

These lines all point to the same memory in location (variable x).

OviOnet
  • 11
  • 1
  • 2
0

There are three variables in your code snippet. Here is a table showing the value each variable would have after the execution of each of the lines (if the code were to actually compile).

                         |  x | p_int | p_p_int |
-------------------------+----+-------+---------|
int x = 0;               |  0 | undef |  undef  |
int *p_int = & x;        |  0 |   &x  |  undef  |
int **p_p_int = & p_int; |  0 |   &x  |  &p_int | <-- begin: all variables lead to x
-------------------------+----+-------+---------|
*p_int = 12;             | 12 |   &x  |  &p_int |
**p_p_int = 25;          | 25 |   &x  |  &p_int |
-------------------------+----+-------+---------| <-- end: all variables lead to x
p_int = 12;              | 25 |   12  |  &p_int | <-- p_int now points to garbage
*p_p_int = 3;            | 25 |    3  |  &p_int |
-------------------------+----+-------+---------|
p_p_int = 27;            | 25 |    3  |    27   | <-- p_p_int now points to garbage
------------------------------------------------/

I've used undef to indicate that the variable does not yet exist (i.e. is undefined); &x and &p_int to represent the addresses of those variables (since an exact value is not knowable).

This is a thought exercise to see if you understand different levels of indirection, if you realize that *p_int and p_int refer to different values in memory. As you noticed, it does not compile. Assigning a numeric literal to a pointer is almost certainly an error, of no use outside this sort of thought experiment. In real code, a line like p_int = 12 is probably a typo (likely intended to be *p_int = 12) and the compiler will alert you to this.

Fortunately, the author seems to be aware of the inadvisability of trying to access the memory at addresses 12, 3, and 27, as neither p_int nor p_p_int was de-referenced while storing a bogus address. Still, it would be nice if the author acknowledged these limitations, or better yet devised an exercise that does not need such a disclaimer. (Hopefully this was an isolated slip. Unlike some people, I would not denounce an entire book on the basis of a single exercise. Books are long, C++ is complex, and mistakes happen. )

JaMiT
  • 14,422
  • 4
  • 15
  • 31
  • No, you're wrong. You cannot assign an `int` to a pointer without an explicit cast in C++. Moreover, evaluating a pointer like `p_int = (int*)12` causes undefined behavior so that is no longer valid C++. – Aykhan Hagverdili Feb 08 '20 at 22:50
  • @Ayxan Did you read the part where I wrote "if the code were to actually compile"? The part where I wrote "This is a thought exercise"?? The part where I wrote "it does not compile"??? The part where I wrote "the compiler will alert you to this"???? The part where I wrote "neither `p_int` nor `p_p_int` was de-referenced while storing a bogus address"????? – JaMiT Feb 08 '20 at 23:11
  • once you cause undefined behavior, you can no longer reason about the program in any meaningful way. Since your explanation assumes UB, you are not talking about C++ anymore. Even if this is a theoretical experiment. Pointers aren't just int`s. You can't just convert any old int to a pointer and expect well-defined behavior. – Aykhan Hagverdili Feb 09 '20 at 07:08
  • *For future reference:* I have reviewed the comments by Ayxan and determined that they are incorrect. The behavior is implementation-defined, not undefined. It is valid C++, and one can reason about it in a meaningful way. – JaMiT Feb 20 '20 at 03:24
  • I am not sure how you reviewed anything but the question I posted didn't get any satisfying answer, thus I didn't accept any of the answers. Even if it was just implementation defined, your "thought exercise" assuming no implementation whatsoever makes no sense in any meaningful way as I noted before (how can you assume implementation defined behavior for no implementation?). – Aykhan Hagverdili Feb 21 '20 at 11:10
  • @Ayxan I believe a more accurate assessment is that the question you posted did not get an answer saying that the result is UB, thus you did not accept any of the answers. You are wrong, but refuse to accept it. I am not changing my answer to match your major misconceptions. – JaMiT Feb 21 '20 at 19:09