1

I am confused as to why would this result in an undefined behavior. Let me copy and paste the explanation from the textbook first and then show my own code and program which runs perfectly.

Precedence specifies how the operands are grouped. It says nothing about the order in which the operands are evaluated. In most cases, the order is largely unspecified. In the following expression* int i = f1() * f2();:

*We know that f1 and f2 must be called before the multiplication can be done. After all, it is their results that are multiplied. However, we have no way of knowing whether f1 will be called before f2 or vice versa. For operators that do not specify evaluation order, it is an error for an expression to refer to and change the same object. Expressions that do so have undefined behavior (§ 2.1.2, p. 36). As a simple example, the << operator makes no guarantees about when or how its operands are evaluated. As a result, the following output expression is undefined.

-- C++ Primer - Page 193 by Stanley B. Lippman

So, I tried to apply this by writing my own code and I never get an undefined behavior? Can someone please explain what does this mean?

#include <iostream>

using std::cout;
using std::endl;

int f1() { return (5 + 5 * 4 / 2 - 3); } // 12
int f2() { return (10 + 2 * 10 / 2 - 5); } // 15

int main()
{
    int i = f1() * f2();
    cout << i << endl;
    return 0;
}
Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
Programming Rage
  • 403
  • 1
  • 4
  • 18
  • what exactly does the author refer to when they write "the following output expression"? `cout << i << endl;` ? Is the example in the book the same as yours? – 463035818_is_not_an_ai May 21 '21 at 18:11
  • 3
    Neither of your functions modify the same variable so it doesn't matter what order they are evaluated in. – Retired Ninja May 21 '21 at 18:11
  • 7
    `it is an error for an expression to refer to and change the same object` well, neither `f1` nor `f2` change the same object. – Stack Danny May 21 '21 at 18:11
  • Three is no UB in your code. The quote is referring to if `f1` or `f2` modify a variable in common to them both. – ChrisMM May 21 '21 at 18:11
  • 3
    First: How do you expect to detect undefined behavior? That is, if you were getting undefined behavior, what would you expect to see as output or program behavior that would indicate this to you? Second: Why do you think your example code invokes undefined behavior? Neither `f1` nor `f2` modifies `i`, or anything else for that matter. – Nathan Pierson May 21 '21 at 18:12
  • @largest_prime_is_463035818 He does not refer to anything `f1()` and `f2()` are some arbitrary functions that return an int. – Programming Rage May 21 '21 at 18:14
  • 1
    You can experimentally verify *presence* of UB, but never *absence* of UB. (Posted code is OK but you cannot prove that just by running it). – n. m. could be an AI May 21 '21 at 18:14
  • Even if both functions would modify the same variable, it should not matter assuming that the variable is globally defined right? – Programming Rage May 21 '21 at 18:15
  • 1
    By the way Lippman is dead wrong here. It is NOT undefined behaviour for f1() and f2() to access and modify the same variable. The order is not specified, but either order is well-defined. You can get f1() called after f2() or f2() called after f1(), but nothing else. – n. m. could be an AI May 21 '21 at 18:19
  • 1
    @n.'pronouns'm. it should have said "unspecified behavior" instead of "undefined behavior", right? – Aykhan Hagverdili May 21 '21 at 18:23
  • 1
    @AyxanHaqverdili Yes it should have. If you replace function calls with other arbitrary expressions, then you might have UB. – n. m. could be an AI May 21 '21 at 18:30
  • 1
    @ProgrammingRage *"Even if both functions would modify the same variable, it should not matter assuming that the variable is globally defined right?"* -- you might want to see [Is cout << (i++) << (i++) Undefined behavior](https://stackoverflow.com/questions/62208618), The two `i++` expressions modify the same variable; are you saying it should not matter which increment happens first? – JaMiT May 21 '21 at 18:34
  • @ChrisMM Ok yea true – Programming Rage May 21 '21 at 18:45
  • @NathanPierson I do not expect UB from the code I posted nor do I believe that my code would possibly invoke UB. The way I comprehended the text was wrong and I thought that function `f1()` and function `f2() would return an integer. So as long as they were returning an integer value it should not create UB. But there were cases or certain situations that might lead to UB that I wanted to explore. – Programming Rage May 21 '21 at 18:47
  • @n.'pronouns'm. I think VS has memory window that I use to keep track of UB – Programming Rage May 21 '21 at 18:48
  • @n.'pronouns'm. I believe you made that statement by reading the code. This code is not given as an example in the text. The `f1()` and `f2()` functions are created by me to check why it would cause an UB. But apparently like the answers below if both these functions modified the same variable then it would lead to an undefined behavior due to unspecified order. Right? – Programming Rage May 21 '21 at 18:50
  • @JaMiT I thought that if it is i++ then cout would be evaluated first and then after that statement i would be incremented. But in that link's case would be twice. Similarly, ++i would evaluated first and then the statement. That is what I thought – Programming Rage May 21 '21 at 18:53
  • 1
    No, it would lead to *unspecified* behaviour rather than undefined behaviour. These are different things. – n. m. could be an AI May 21 '21 at 19:09
  • 1
    There are various UB detectors but none of them catch every single instance of UB. – n. m. could be an AI May 21 '21 at 19:10
  • 1
    @ProgrammingRage *"I thought that if it is i++ then [...]"* -- this is a good example of the kind of thinking that your textbook is attempting to correct. ;) *That is, what you thought is what a lot of people initially think even though it is not correct.* – JaMiT May 21 '21 at 19:22

3 Answers3

4

You're getting it wrong. The author means IF the order matters, it's unspecified. In your case, the order of evaluation doesn't matter. In fact, the function might as well be constexpr. But if you had something like this:

int i = 0;

int f1() { return (i++) * 3; }
int f2() { return (i++) * 4; }

int main() {
    int a = f1() + f2();
}

Now, if f1 is called first, the result is 4. If f2 is called first, the result is 3. Thus, it's unspecified.


I never get an undefined behavior?

You can't really know that by simply running the program.

Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
  • How is the result 4 and 3 respectively when `i` is not initialized? – Programming Rage May 21 '21 at 18:20
  • 2
    @ProgrammingRage static variables are initialized with zero by default. In this example `i` is a (global) static variable, thus its value is zero to begin with. – Aykhan Hagverdili May 21 '21 at 18:20
  • Ok so I tried out your code here: https://imgur.com/gh2ke8C Apparently `f1()` returned 0, `f2()` returned 4 and somehow `a` is 18???? – Programming Rage May 21 '21 at 18:35
  • @ProgrammingRage because you called the functions multiple times. Use your debugger to see what happens step-by-step. See [What is a debugger and how can it help me diagnose problems?](https://stackoverflow.com/q/25385173/10147399) – Aykhan Hagverdili May 21 '21 at 18:37
  • @ProgrammingRage welcome to space of functions with side-effects (aka, not pure functions) – Swift - Friday Pie May 21 '21 at 18:38
  • @AyxanHaqverdili Thats fine, I called the function only twice. But why did `f1()` return 0? – Programming Rage May 21 '21 at 18:57
  • @ProgrammingRage what would you expect it to return the first time you called it? – Aykhan Hagverdili May 21 '21 at 18:57
  • @Swift-FridayPie My apologies, I do not understand what you mean by space of functions with side-effects? – Programming Rage May 21 '21 at 18:57
  • @AyxanHaqverdili I would expect it to return 3? Because i is initialized to 0 and then (i++) * 3. Oooooh it increments `i` only after the statement! – Programming Rage May 21 '21 at 18:59
  • In the first `f1()` call it returns 0 but `i` is 1. In the first `f2()` call it returns 4 but `i` is 2. In the second `f1()` call it returns 6 but `i` is 3. In the second `f2()` call it returns 12 but `i` is now 4. So now a = 18. I see it now! – Programming Rage May 21 '21 at 19:03
  • So, I ran it every single time and I always ended up with the same value. It never changed. So this would differ from a compiler to a compiler right? – Programming Rage May 21 '21 at 19:05
  • @ProgrammingRage it *may*. It's unspecified whether the result is 3 or 4. See unspecified behavior here https://en.cppreference.com/w/cpp/language/ub – Aykhan Hagverdili May 21 '21 at 19:09
3

Your code is fine.

it is an error for an expression to refer to and change the same object

(bold mine)

Your don't change any objects in your expressions, so the rule doesn't apply.

Here's an example of when the rule would apply:

int a = 42;
int i = a++ * a++;

Note that it would not apply if the change happened in a function:

int a = 42;
int foo() {return a++;}
int i = foo() * foo();

That's because the UB only happens when two accesses to an object are unsequenced relative to each other, i.e. can happen in any order including in parallel. This doesn't necessarily mean "in parallel threads", but can also mean "single thread, but processor instructions performing the tasks may be interleaved".

But two function calls on the same thread can't happen in parallel (and can't have their instructions interleaved). Rather, in this case, they are indeterminately sequenced, i.e. one strictly after the other, but it's unspecified which one is first.

Also note that

the << operator makes no guarantees about when or how its operands are evaluated

is no longer true starting from C++17.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • Would `i = (a=2) * (a=3)` also be such an example? (Because, if so, then I think it's a simpler example than `i = i++ * i++` – Wyck May 21 '21 at 18:24
  • My apologies, I read your answer multiple times but I do not understand when you say ```indeterminately sequenced``` but cant be interleaved. On one hand you are saying that the sequence is indeterminable and can happen in any order but it cant be mixed??? – Programming Rage May 21 '21 at 18:41
  • @ProgrammingRage he(?) basically means that the functions are called synchronously. (i.e. the control doesn't go to the other function call until the first one returns). – Aykhan Hagverdili May 21 '21 at 18:49
  • @ProgrammingRage [This link](https://en.cppreference.com/w/cpp/language/eval_order) might be helpful. There are two kinds of "can happen in any order". One of them is "the one happens after the other, but it's unclear which one is first". This is "indeterminately sequenced". Then there's also "can happen in any order, and possibly instructions performing the tasks can be interleaved". This roughly means "can happen in parallel". This is what's called "unsequenced", and this is what can cause UB. – HolyBlackCat May 21 '21 at 18:53
  • @AyxanHaqverdili Ooh makes sense. – Programming Rage May 21 '21 at 18:55
  • @HolyBlackCat Wow C++ is so broken! – Programming Rage May 21 '21 at 18:55
  • 1
    @ProgrammingRage it's not a bug, it's a feature! – Aykhan Hagverdili May 21 '21 at 18:55
  • @AyxanHaqverdili How is that a feature? Its so confusing – Programming Rage May 21 '21 at 19:06
  • @ProgrammingRage it allows your compiler to further optimize the code. Do some googling if you're interested. Or ask a new question iff you can't find an answer. Comments are not for extended discussions. – Aykhan Hagverdili May 21 '21 at 19:11
  • @AyxanHaqverdili Even if I googled it I wouldnt understand compiler code and its optimizations. Im a newb – Programming Rage May 21 '21 at 19:13
  • 1
    @ProgrammingRage https://stackoverflow.com/a/2934909/10147399 – Aykhan Hagverdili May 21 '21 at 19:14
0

From C++ draft standard:

3.64 [defns.undefined] undefined behavior

behavior for which this document imposes no requirements [Note 1: Undefined behavior may be expected when this document omits any explicit definition of behavior or when a program uses an erroneous construct or erroneous data. Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message). Many erroneous program constructs do not engender undefined behavior; they are required to be diagnosed. Evaluation of a constant expression ([expr.const]) never exhibits behavior explicitly specified as undefined in [intro] through [cpp]. — end note]

Thus, almost everything is possible, even predictable behavior for a given implementation.

But, your program does not exhibit any UB. f1 and f2 do not have any border effect, thus the order of their evaluation has no impact.

Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69