Hi please tell me why std::cout ouputs that?


#1

I think it should output “4 3 2 1”, but actually it outputs “1 2 3 4”;

sourse code:

int count = 4;
int f(int* ia){
    count--;
    if(count >= 0)
        return ia[count];
    else
        abort();
}
int ia[] = {1,2,3,4};
int main(){
    std::cout << f(ia) << ' ' << f(ia) << ' '\
     << f(ia) << ' ' << f(ia) << '\n';

#2

I think the compiler choose whatever is fit, as it might not be defined behavior otherwise. Because of that, the order of execution shouldn’t be relevant.

It’s best to write code that wouldn’t change if you were to change their order of execution. Think about it with local variable containing the result of each f(ia) instead - would their order of definition matter?

We tend to avoid such mutations (especially when they impact each other values) in single sequence. To be honest, I try to avoid putting x++ inside any kind of expression because that can lead to weird stuff, due to that order of evaluation. Compactness of code was never more important than readability, for me at least, but this is especially important as you can’t be always sure (except if you’re @Pharap) when the compiler will actually evaluate each parts.

I think a good rule of thumb is, avoid expressions that include two sub-expressions that can interact with each other - such as having x++ two times in the same expression, or even a single x++ with a x read.

Also, please provide a source between code quotes. Pasting your code led to a lot of errors related to characters ' being transformed into ’, etc


#3

@carbonacat’s evaluation is more or less correct.
I’ll try to be a bit more formal with my explanation though.

Essentially there are two forces at work here:
operator precedence and order of evaluation.

Firstly, all of the f(ia) are evaluated before any of the <<s are evalutated.
This is because function calls have a precedence of 2 while << has a precedence of 7.

Secondly, the calls to f(ia) are being evaluated from right to left rather than left to right.

This is entirely legal because C++ does not enforce the order in which arguments are evaluated.

As stated by cppreference:

Order of evaluation of any part of any expression, including order of evaluation of function arguments is unspecified (with some exceptions listed below). The compiler can evaluate operands and other subexpressions in any order, and may choose another order when the same expression is evaluated again.

It also states that:

There is no concept of left-to-right or right-to-left evaluation in C++. This is not to be confused with left-to-right and right-to-left associativity of operators: the expression a() + b() + c() is parsed as (a() + b()) + c() due to left-to-right associativity of operator+, but the function call to c may be evaluated first, last, or between a() or b() at run time

So basically, with the way your code is written and the rules of the language,
any perumtation of 1 2 3 4 would be valid.
Even 2 1 4 3 would be valid.

If you want specific behaviour, you must enforce it, e.g. like this:

std::cout << f(ia) << ' ';
std::cout << f(ia) << ' ';
std::cout << f(ia) << ' ';
std::cout << f(ia) << '\n';

(Though as @carbonacat hints at, in general you should try to avoid depending on state where possible.)

Your program could be written more simply like so:

const int ia[] = { 1, 2, 3, 4 };
int main()
{
	for(std::size_t i = 4; i > 0; --i)
	{
		std::size_t index = (i - 1);
		std::cout << ia[index];
		std::cout << (index == 0) ? '\n' : ' ';
	}
	std::cin.get();
}

Or as:

const int ia[] = { 1, 2, 3, 4 };
int main()
{
	for(std::size_t i = 4; i > 0; --i)
	{
		std::size_t index = (i - 1);
		std::cout << ia[index];
		if(index > 0)
			std::cout << ' ';
	}
	std::cout << '\n';
	std::cin.get();
}

Also I’d like to say that using abort is generally a bad idea.

Either try to make it impossible to encounter such a situation by not using a separate function in the first place, or throw an exception.
An uncaught exception will implicitly terminate the program whilst doing proper stack unwinding and calling destructors.

std::abort will not call destructors and may not even release any system resources!

Destructors of variables with automatic, thread local (since C++11) and static storage durations are not called. Functions registered with std::atexit() and std::at_quick_exit (since C++11) are also not called. Whether open resources such as files are closed is implementation defined. An implementation defined status is returned to the host environment that indicates unsuccessful execution.

(As stated by cppreference. See also this SO answer.)

Edit:
Oh, and you don’t need the \ in the code:

    std::cout << f(ia) << ' ' << f(ia) << ' '\

That \ is just noise, the program will compile without it.


The only way to be completely sure is to manually enforce the evaluation using other aspects of the language (e.g. storing results in variables, using more statements).

Most compilers seem to have one rule and stick to that rule (and most seem to prefer right to left),
but theoretically a compiler could pick the evaluation direction at random and still be following the rules.

I agree with this, but I’d like to point out that preincrement and postincrement are an entirely different minefield than functions with side effects.

There are some rules about the ordering of ++ and --,
and the rules were tightened up in C++11 (things that were previously undefined behaviour are now defined),
but even now certain uses of ++ or -- are flat-out undefined behaviour (i.e. such uses could do literally anything).

Here’s a short list of examples, borrowed from this SO answer:

  1. i = i++ * ++i; // Undefined Behaviour
  2. i = ++i + i++; // Undefined Behaviour
  3. i = ++i + ++i; // Undefined Behaviour
  4. i = v[i++]; // Undefined Behaviour
  5. i = v[++i]; // Well-defined Behavior
  6. i = i++ + 1; // Undefined Behaviour
  7. i = ++i + 1; // Well-defined Behaviour
  8. ++++i; // Well-defined Behaviour
  9. f(i = -1, i = -1); // Undefined Behaviour

Personally as a rule of thumb I always use ++ or -- as a separate statement,
and I only ever use the pre form (pre-increment or pre-decrement).
It saves me remembering a long list of rules and it avoids any nasty surprises.

Examples 4 & 5 and examples 6 & 7 demonstrate some of the reasons why I prefer preincrement and predecrement rather than postincrement and postdecrement,
though I’d never actually write such a muddled statement as i = v[++i] or i = ++i + 1.
Writing code like any of these examples is just asking for trouble in my opinion.

And to top it off, pre C++11 was even worse.

The first thing I did when I got here was edit a code block in.
In addition to ' being turned into , you get -- being turned into .


#4

@pharap where on earth do you get this information from? The order of execution etc?


#5

I do a lot of reading.

More specifically, I get my info mainly from cppreference and Stack Overflow,
and occaisionally I stumble on some interesting blog posts.

cppreference is more or less a condensed human-readable version of the standard.
Aside from being a good reference for all the various library utilities,
it also documents most of the language rules and features.
(A good entry point for that aspect is here.)

SO is better for when you want to know something very specific.
Usually I end up there by realising I don’t know something and then finding someone else has already asked that question.

It helps to be good at guessing what words you need to put in your search engine, sometimes that isn’t easy (e.g. a “member initialiser list” is not the same as a “default member initialiser” despite sounding very similar).

Technically it’s order of evaluation in this case.

I’m not sure if order of execution has a formal definition,
but I think usually it’s used in reference to the order of statements rather than the order that expressions are evaluated in (which is important when the compiler is allowed to reorder statements).


Interestingly I just stumbled upon this SO question:

None of the answers are quite as formal as my answer here,
though I like that the first answer attempts to draw a parallel between expression evaluation and assigning to a variable.
It’s not exactly equivalent but it’s quite close to what actually happens.