Dynamic Memory

Pages: 12
I was reading http://www.cplusplus.com/doc/tutorial/dynamic/ and trying to under stand dynamic memory, so I wrote a small program to try to understand it.

#1
1
2
3
4
5
6
7
8
9
int main(){
    int *p;
    p = new int [5];

    cout << p[0] << " - " << p[1] << endl;
    cout << *p << " - " << *(p+1) << endl;

    return 0;
}

ADDRESS1 - ADDRESS2
ADDRESS1 - ADDRESS2


#2
1
2
3
4
5
6
7
8
9
int main(){
    int *p;
    p = new int [5];

    cout << p[0] << " - " << p[1] << endl;
    cout << *p << " - " << *(p++) << endl;

    return 0;
}

ADDRESS1 - ADDRESS2
ADDRESS2 - ADDRESS1


Can anyone explain why changing the *(p+1) to *(p++) causes the output to reverse?
This hardly has anything to do with dynamic memory. It's just pointers and not even that.. What p++ does is (permanently) add 1 to p and return the old value. Apparently the arguments to cout << are evaluated in reverse. I can't say why that would make sense. If may be undefined. The rule of thumb is never to change a value on the same line you use it for something else.
What you observe has nothing to do with dynamic memory.

Instead, it is because of the expression evaluation sequence(from right to left) in C++.
As you know, << is an operator (http://www.cplusplus.com/reference/iostream/ostream/operator%3C%3C/).
In cout << *p << " - " << *(p++) << endl;

The last(counting from left to right) << operator gets executed first. --- result stuffed into the ostream
Then p gets ++.
...

Therefore, when the execution reaches *p, p is actually pointing to the second element(index 1) in your dynamically allocated array.




Oh, forgot to add:
*(p+1) means, you do the pointer arithmetics first, and you store the result in a temporary variable(the compiler handles that for you when translating your code into assembly code). Therefore, p itself didn't get changed.

++, on the other hand, changes p.
@hamsterman:

I can't say why that would make sense.


The apparent reason is l/r values. Read the link for more detailed explanations if you want. (http://capndurk.myweb.uga.edu/articles/insert_extract_ops.html )

In my opinion, however, the real underlying reason for the design choice is that most of us read from left to right.
In other words, if ;0 = b = a tni makes sense to you, you might like the other way around.
Last edited on
The expression cout << *p << " - " << *(p++) << endl; is an error in C++, it invokes undefined behavior because it attempts to access p (in the subexpression *p) and to modify p (in the subexpression p++) without intervening sequence points.
(or, using C++11 terminology, access and modification are unsequenced).

There is no way to predict what value is printed on the left because there is no way to predict whether *p will see p before p++, after p++, or at some point halfway.

There is no concept of "right to left" or "left to right" as previous answers suggest. This is simply an undefined expression.
@Cubbi:

Have you even tried to run the program?

In addition, who told you there would be no such concept as "right to left" and "left to right"?
If there're no such concepts, as you asserted, how is the compiler going to translate C++ code into assembly code? A line of instruction per translation?

What cubbi meant was that C++ standard doesn't enforce it one way or another. Error is as in "something you shouldn't do" rather than "something that doesn't compile".
Instead of posting multiple replies... can you just post one and edit it? Makes it less confusing for the person asking for help.
That wouldn't be very good for others that want to look through the conversation and understand what's going on. Posting multiple replies in the manner that they've been doing, and keeping the flow of the conversation healthy, is much better than the alternatives; it not only helps others to understand what's going on, but it helps people to learn more about the underlying issues that cause the language to act the way it does.
I meant the part where they have 3 consecutive posts in a row. That part can be edited.
Have you even tried to run the program?

Yes, it produces different results on different compilers, as expected. GCC diagnoses the error (with -Wall). Other compilers I've tried don't diagnose it, but undefined behavior is not something compilers are required to diagnose. Access to an array out of bounds is another example of undefined behavior, which the compilers are not required to diagnose, which also produces unpredictable results.

In addition, who told you there would be no such concept as "right to left" and "left to right"? If there're no such concepts, as you asserted, how is the compiler going to translate C++ code into assembly code? A line of instruction per translation?

The compiler may evaluate the subexpressions in any order it chooses (barring a few limits imposed by sequence points/sequenced-before rules), taking into account pipelining, memory alignment, locality of reference, and any other low-level details about the target platform and the wider context that would produce fast-executing code. This behavior is similar to C, but different from C# and Java, for example.
Last edited on
So which of you is correct? Is there a "left to right" and "right to left" OR is it an error and that is why it is being read right to left?
Also, what is the difference between p++ and p+1? I read from above that p++ is perm and p+1 is temporary?
p+1 returns the value of p plus 1.
p++ changes p to the value of p plus 1.
Thanks for explaining the p+1 and p++. Can someone explain the error/l-r or r-l concept?
Based on the below code, as long as there is a ++ in the code, everything gets evaluate from right to left, but gets printed from left to right (or as formatted).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main(){
    int *p;
    p = new (nothrow) int[5];

    cout << p[0] << " - " << p[1] << endl;
    cout << "(1)" << *p << " - (2)" << *(p++) << endl; //p++ changes value

    cout << "----------" << endl;

    int *x;
    x = new (nothrow) int[5];
    cout << x[0] << " - " << x[1] << " - " << x[2] << endl;
    cout << "(1)" << *(x++) << " - (2)" << *(x++) << " - (3)" << *x << endl;

    delete[] p;
    delete[] x;

    return 0;
}
@dalawh
Your new code has four errors:
line 6: undefined behavior due to sequence point violation
line 13: same
line 15: attempt to delete a pointer that was not obtained from new
line 16: same

If this program runs without crashing on your compiler, the output is unpredictable.
I am using Code::Blocks. Are you running this on Linux?
@Cubbi:

Yes, it produces different results on different compilers, as expected. GCC diagnoses the error (with -Wall). Other compilers I've tried don't diagnose it


No, man. Don't try to escape with this. You know, ALL compilers give you the SAME output. Of course, GCC might gives you a WARNING (Not error, okay? Do you really think ANY compiler will let you get away with an error?), which might not be produced by other compilers (I've not tried personally, though). That's not "different results", okay? That's only the slightly different choices among compiler implementations on certain optional warnings.


but undefined behavior is not something compilers are required to diagnose.

Interesting ... yes, you're right, the compiler might not be required to diagnose that. That is because it is not practically feasible to REQUIRE compilers to do that ALL THE TIME, not because that compilers don't want to check undefined behaviour. Okay? Everyone wants to get rid of undefined behaviour, and there's simply no way to make a machine to perform undefined behaviour. No matter what, even some undefined behaviour manages to escape the compiler checks, a run time error will be reported - e.g. divide by 0.

Okay, in this case, if
The expression cout << *p << " - " << *(p++) << endl; is an error in C++, it invokes undefined behavior
, as you said, how is it possible for the program to finish without at least run time errors?




The compiler may evaluate the subexpressions in any order it chooses (barring a few limits imposed by sequence points/sequenced-before rules), taking into account pipelining, memory alignment, locality of reference, and any other low-level details about the target platform and the wider context that would produce fast-executing code.

Generally speaking, you're not too wrong. But don't over-generalize the problem and then conclude with "Different compilers behave differently". That way, we're not going to have constructive discussions, because in the worst case, you can write your own compiler and say "look, it works". In the context (the << operator), I'm afraid you're way too wrong. Even if you want to build your worst case compiler, I'm afraid you can't. Let us assume that << operator is not evaluated from right to left, then you will never see a complete and correct string on your terminal.

Why is that?

<< takes parameters and return ostream object references. The ultimate purpose is to modify the cout ostream object such that the complete contents in the line of code can be shown on the screen. If

case 1: cout << XXX(a new ostream object reference) is not evaluated last, then you don't put everything into the cout ostream object. Therefore, you don't see the complete string.

case 2: cout << XXX(a new ostream object reference) is evaluated last, but some random order is followed - then we see a complete but wrongly ordered string.



Look, the idea is really simple: we are creating a bigger and bigger ostream object - by the time it reaches the cout object, a complete string has be constructed, in correct order. Then we see the correct and complete string on the terminal.



Pages: 12