Question about C programming - c

int a, b;
a = 1;
a = a + a++;
a = 1;
b = a + a++;
printf("%d %d, a, b);
output : 3,2
What's the difference between line 3 and 5?

What you are doing is undefined.
You can't change the value of a variable you are about to assign to.
You also can't change the value of a variable with a side effect and also try to use that same variable elsewhere in the same expression (unless there is a sequence point, but in this case there isn't). The order of evaluation for the two arguments for + is undefined.
So if there is a difference between the two lines, it is that the first is undefined for two reasons, and line 5 is only undefined for one reason. But the point is both line 3 and line 5 are undefined and doing either is wrong.

What you're doing on line 3 is undefined. C++ has the concept of "sequence points" (usually delimited by semicolons). If you modify an object more than once per sequence point, it's illegal, as you've done in line 3. As section 6.5 of C99 says:
(2) Between the previous and next sequence point an object shall have its stored value
modified at most once by the evaluation of an expression. Furthermore, the prior value
shall be read only to determine the value to be stored.
Line 5 is also undefined because of the second sentence. You read a to get its value, which you then use in another assignment in a++.

a++ is a post-fix operator, it gets the value of a then increments it.
So, for lines 2,3:
a = 1
a = 1 + 1, a is incremented.
a becomes 3 (Note, the order these operations are performed may vary between compilers, and a can easily also become 2)
for lines 4,5:
a = 1
b = 1 + 1, a is incremented.
b becomes 2, a becomes 2. (Due to undefined behaviour, b could also become 3 of a++ is processed before a)
Note that, other than for understanding how postfix operators work, I really wouldn't recommend using this trick. It's undefined behavior and will get different results when compiled using different compilers
As such, it is not only a needlessly confusing way to do things, but an unreliable, and worst-practice way of doing it.
EDIT: And has others have pointed out, this is actually undefined behavior.

Line 3 is undefined, line 5 is not.
EDIT:
As Prasoon correctly points out, both are UB.
The simple expression a + a++ is undefined because of the following:
The operator + is not a sequence point, so the side effects of each operands may happen in either order.
a is initially 1.
One of two possible [sensible] scenarios may occur:
The first operand, a is evaluated first,
a) Its value, 1 will be stored in a register, R. No side effects occur.
b) The second operand a++ is evaluated. It evaluates to 1 also, and is added to the same register R. As a side effect, the stored value of a is set to 2.
c) The result of the addition, currently in R is written back to a. The final value of a is 2.
The second operand a++ is evaluated first.
a) It is evaluated to 1 and stored in register R. The stored value of a is incremented to 2.
b) The first operand a is read. It now contains the value 2, not 1! It is added to R.
c) R contains 3, and this result is written back to a. The result of the addition is now 3, not 2, like in our first case!
In short, you mustn't rely on such code to work at all.

Related

Difference between ++*p++ and *++p++?

What is the difference between ++*p++ and *++p++ (where p is a pointer) in C?
I keep getting an error when I do the second one; can somebody explain, as it's been in my head for days. I checked on Quora and other websites but I couldn't find anything useful.
I want to know why the first one is acceptable but not the latter.
#include <stdio.h>
int main()
{
int arr[]={3,9,0,4,5};
int *ptr=arr;
printf("%d ",*++ptr++);
printf("%d ",*ptr);
return 0;
}
The issue here is a question of operator precedence and the nature of the result of the postfix increment operator (i.e. p++).
In C, that postfix increment operator has the highest priority of all; then, the prefix increment and indirection (*) operator have equal priority, and have right-to-left associativity.
So, adding parentheses to your expressions, to clarify the order of evaluation, we get the following:
*++ptr++ becomes *( ++(p++) )
++*ptr++ becomes ++( *(p++) )
Now remember that the result of the postfix operation is a so-called "rvalue" § – that is, something that can be used on the right-hand side of an assignment but not on the left-hand side. (For example, the constant, 3 is an rvalue: x = 3 is a valid operation but 3 = x is clearly not.)
We can now see that, in your first expression, inside the outer brackets that I have added, we are trying to increment the result of the p++ operation – and that is not allowed. However, in the second case, we are only dereferencing that result (which is a pointer) and then (outside the outer brackets) incrementing the pointed-to variable – which is allowed.
When I compile your code with clang-cl, the error is:
error : expression is not assignable
As (hopefully) explained above, the "expression" referred to is p++.
§ Formally, the result of the postfix increment operator is (a copy of) the value of its operand; that value is not modifiable or assignable.
This is definitely a strange and surprising result. To understand what's going on, it will help to take a closer result at what the "autoincrement" operators ++x and x++ really do.
Most expressions simply compute new values. If I say
a = b * 3;
that means, "take the value of the variable b, multiply it by 3, and that's the value we'll assign to a". Similarly, if I say
a = a + 1;
that means, "take the (old) value of the variable a, add 1 to it, and that's the new value we'll assign back to a".
But ++ is special, because it has the "assign the value back" part built in. Any time you use ++ (or --), two things are happening: we're computing a new value, but we're also modifying the variable whose value we just fetched.
To make this very clear, if I say
a = ++b;
that means, "take the value of the variable b, add 1 to it, assign that new value back to b, and that's the value we'll assign to a". That's for the "prefix" form ++b. For b++, it's a little different:
a = b++;
That means, "take the value of the variable b, add 1 to it, assign that new value back to b, but the value we'll assign to a is the old value of b, before we added 1 to it." In other words, the value of the subexpression b++, the value that "pops out" to participate in the larger expression, is the old value of b.
The other thing to keep in mind here is that when it comes to assigning values, we obviously need a variable to assign the value to. We can't say
3 = b * 3; /* WRONG */
On the right-hand side of the = sign, we fetch b's value and multiply it by 3, but then where we store the new value? On the left-hand side of the = sign, 3 is not the name of a variable, nor is it any kind of a location where we can store a value. So an assignment like this is illegal.
(Formally, what we've been talking about here is the difference between an rvalue and an lvalue. Those are interesting and useful terms that you might want to learn about some day, perhaps even today, but I'm not going to say anything more about them for now.)
But now we have almost enough information to answer your original question. Let's look at the expression that worked:
++*ptr++
What the heck does that mean?
In one sense, it's kind of meaningless, because it's not something that you would probably ever write in a real program. It has very little practical value, which is actually kind of good, which means it's not so bad that, at first glance, it's pretty badly cryptic, in that it's not obvious what it should do.
To understand what it does, we have to be clear about the precedence. Which operands bind more tightly to their operands? Precedence is what tells us that if we write
1 + 2 * 3
the multiplication operator * binds more tightly, meaning that the expression is evaluated as if we had written
1 + (2 * 3)
Now, it happens that the autoincrement operator ++ binds more tightly that the unary contents-of operator *. That is, when we write
++*ptr++
the expression is evaluated as if we had written
++ *(ptr++)
So the first thing we're going to do, inside the parentheses, is ptr++. This means, as we saw before, "take the value of the variable ptr, add 1 to it, assign that new value back to ptr, but the value that pops out to the larger expression is the old value of ptr, before we added 1 to it."
And then the next thing that happens in the "larger expression" is the * or contents-of operator. * works on a pointer, and accesses the object pointed to by the pointer. In your original program, the object pointed to by the pointer ptr was the first cell of the array arr, that is, arr[1]. So * is going to operate on whatever the old value of ptr was, whatever ptr used to point to. And that's arr[0]. So what we end up doing is the equivalent of
++(arr[0])
We're going to take arr[0]'s old value, add 1 to it, store it back in arr[0], and (since this is prefix ++ we're talking about), the value that will "pop out" to the larger expression would be the new value of arr[0]. So, if you had written
printf("%d\n", ++*ptr++);
it would have printed the new value of arr[0], or 4.
The bottom line is that although ++*ptr++ is a complicated-looking expression that's hard to understand and does something so obscure that it might not even be useful, it does do something, and is legal.
So now, finally, it's time to look at
*++ptr++
What does that do?
The first thing we have to know is whether prefix ++ or postfix ++ binds more tightly. It's a question that hardly ever comes up (and we're about to see why), but the answer is that postfix ++ binds more tightly. So this expression is interpreted as if you had written
* ++(ptr++)
So, once again, the first thing we're going to do is take ptr's value, add 1 to it, store that new value back into ptr, and then the value that's going to "pop out" to the larger expression is going to be the old value — but only the old value — of ptr.
Let me say that again. The value that "pops out" to the larger expression is just the old value of ptr. By that time we no longer know or care that it was the variable ptr that we got this value from.
So then we come to the prefix ++. And now we have a serious problem. Remember, ++ wants to fetch a value from an object, add 1 to it, and store the new value back into an object. But at this point we don't have an object to fetch from or store to, we just have a value — remember, the old value of the variable ptr.
This will be easier to understand if we think about integer variables, instead of pointer-to-integer. Suppose I said
int a;
int b = 5;
a = ++(b++);
So we fetch b's value, which is 5, and add 1 to it, and store the new value — which is 6 — back in b, and the value that "pops out" to the larger expression s the old value of b. So now it's as if we had written
a = ++5; /* WRONG */
And this makes no sense. We can't "fetch the old value from the variable 5", because 5 isn't a variable. It's just as wrong as when we said 3 = b * 3;, and for the same reason.
You might also be interested in Question 4.3 in the C FAQ list.

Does a[a[0]] = 1 produce undefined behavior?

Does this C99 code produce undefined behavior?
#include <stdio.h>
int main() {
int a[3] = {0, 0, 0};
a[a[0]] = 1;
printf("a[0] = %d\n", a[0]);
return 0;
}
In the statement a[a[0]] = 1; , a[0] is both read and modified.
I looked n1124 draft of ISO/IEC 9899. It says (in 6.5 Expressions):
Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be read only to determine the value to be stored.
It does not mention reading an object to determine the object itself to be modified. Thus this statement might produce undefined behavior.
However, I feel it strange. Does this actually produce undefined behavior?
(I also want to know about this problem in other ISO C versions.)
the prior value shall be read only to determine the value to be stored.
This is a bit vague and caused confusion, which is partly why C11 threw it out and introduced a new sequencing model.
What it is trying to say is that: if reading the old value is guaranteed to occur earlier in time than writing the new value, then that's fine. Otherwise it is UB. And of course it is a requirement that the new value be computed before it is written.
(Of course the description I have just written will be found by some to be more vague than the Standard text!)
For example x = x + 5 is correct because it is not possible to work out x + 5 without first knowing x. However a[i] = i++ is wrong because the read of i on the left hand side is not required in order to work out the new value to store in i. (The two reads of i are considered separately).
Back to your code now. I think it is well-defined behaviour because the read of a[0] in order to determine the array index is guaranteed to occur before the write.
We cannot write until we have determined where to write. And we do not know where to write until after we read a[0]. Therefore the read must come before the write, so there is no UB.
Someone commented about sequence points. In C99 there is no sequence point in this expression, so sequence points do not come into this discussion.
Does this C99 code produce undefined behavior?
No. It will not produce undefined behavior. a[0] is modified only once between two sequence points (first sequence point is at the end of initializer int a[3] = {0, 0, 0}; and second is after the full expression a[a[0]] = 1).
It does not mention reading an object to determine the object itself to be modified. Thus this statement might produce undefined behavior.
An object can be read more than once to modify itself and its a perfectly defined behavior. Look at this example
int x = 10;
x = x*x + 2*x + x%5;
Second statement of the quote says:
Furthermore, the prior value shall be read only to determine the value to be stored.
All the x in the above expression is read to determine the value of object x itself.
NOTE: Note that there are two parts of the quote mentioned in the question. First part says: Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression., and
therefore the expression like
i = i++;
comes under UB (Two modifications between previous and next sequence points).
Second part says: Furthermore, the prior value shall be read only to determine the value to be stored., and therefore the expressions like
a[i++] = i;
j = (i = 2) + i;
invoke UB. In both expressions i is modified only once between previous and next sequence points, but the reading of the rightmost i do not determine the value to be stored in i.
In C11 standard this has been changed to
6.5 Expressions:
If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined. [...]
In expression a[a[0]] = 1, there is only one side effect to a[0] and the value computation of index a[0] is sequenced before the value computation of a[a[0]].
C99 presents an enumeration of all the sequence points in annex C. There is one at the end of
a[a[0]] = 1;
because it is a complete expression statement, but there are no sequence points inside. Although logic dictates that the subexpression a[0] must be evaluated first, and the result used to determine to which array element the value is assigned, the sequencing rules do not ensure it. When the initial value of a[0] is 0, a[0] is both read and written between two sequence points, and the read is not for the purpose of determining what value to write. Per C99 6.5/2, the behavior of evaluating the expression is therefore undefined, but in practice I don't think you need to worry about it.
C11 is better in this regard. Section 6.5, paragraph (1) says
An expression is a sequence of operators and operands that specifies computation of a value, or that designates an object or a function, or that generates side effects, or that performs a combination thereof. The value computations of the operands of an operator are sequenced before the value computation of the result of the operator.
Note in particular the second sentence, which has no analogue in C99. You might think that would be sufficient, but it isn't. It applies to the value computations, but it says nothing about the sequencing of side effects relative to the value computations. Updating the value of the left operand is a side effect, so that extra sentence does not directly apply.
C11 nevertheless comes through for us on this one, as the specifications for the assignment operators provide the needed sequencing (C11 6.5.16(3)):
[...] The side effect of updating the stored value of the left operand is
sequenced after the value computations of the left and right operands. The evaluations of the operands are unsequenced.
(In contrast, C99 just says that updating the stored value of the left operand happens between the previous and next sequence points.) With sections 6.5 and 6.5.16 together, then, C11 gives a well-defined sequence: the inner [] is evaluated before the outer [], which is evaluated before the stored value is updated. This satisfies C11's version of 6.5(2), so in C11, the behavior of evaluating the expression is defined.
The value is well defined, unless a[0] contains a value that is not a valid array index (i.e. in your code is not negative and does not exceed 3). You could change the code to the more readable and equivalent
index = a[0];
a[index] = 1; /* still UB if index < 0 || index >= 3 */
In the expression a[a[0]] = 1 it is necessary to evaluate a[0] first. If a[0] happens to be zero, then a[0] will be modified. But there is no way for a compiler (short of not complying with the standard) to change order of evaluations and modify a[0] before attempting to read its value.
A side effect includes modification of an object1.
The C standard says that behavior is undefined if a side effect on object is unsequenced with a side effect on the same object or a value computation using the value of the same object2.
The object a[0] in this expression is modified (side effect) and it's value (value computation) is used to determine the index. It would seem this expression yields undefined behavior:
a[a[0]] = 1
However the text in assignment operators in the standard, explains that the value computation of both left and right operands of the operator =, is sequenced before the left operand is modified3.
The behavior is thus defined, as the first rule1 isn't violated, because the modification (side effect) is sequenced after the value computation of the same object.
1 (Quoted from ISO/IEC 9899:201x 5.1.2.3 Program Exectution 2):
Accessing a volatile object, modifying an object, modifying a file, or calling a function
that does any of those operations are all side effects, which are changes in the state of
the execution environment.
2 (Quoted from ISO/IEC 9899:201x 6.5 Expressions 2):
If a side effect on a scalar object is unsequenced relative to either a different side effect
on the same scalar object or a value computation using the value of the same scalar
object, the behavior is undefined.
3 (Quoted from ISO/IEC 9899:201x 6.5.16 Assignment operators 3):
The side effect of updating the stored value of the left operand is
sequenced after the value computations of the left and right operands. The evaluations of
the operands are unsequenced.

issue with assignment operator inside printf()

Here is the code
int main()
{
int x=15;
printf("%d %d %d %d",x=1,x<20,x*1,x>10);
return 0;
}
And output is 1 1 1 1
I was expecting 1 1 15 1 as output,
x*1 equals to 15 but here x*1 is 1 , Why ?
Using assignment operator or modifying value inside printf() results in undefined behaviour?
Your code produces undefined behavior. Function argument evaluations are not sequenced relative to each other. Which means that modifying access to x in x=1 is not sequenced with relation to other accesses, like the one in x*1. The behavior is undefined.
Once again, it is undefined not because you "used assignment operator or modifying value inside printf()", but because you made a modifying access to variable that was not sequenced with relation to other accesses to the same variable. This code
(x = 1) + x * 1
also has undefined behavior for the very same reason, even though there's no printf in it. Meanwhile, this code
int x, y;
printf("%d %d", x = 1, y = 5);
is perfectly fine, even though it "uses assignment operator or modifying value inside printf()".
Within a function call, the function parameters may be evaluated in any order.
Since one of the parameters modifies x and the others access it, the results are undefined.
The Standard states that;
Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored.
It doesn't impose an order of evaluation on sub-expressions unless there's a sequence point between them, and rather than requiring some unspecified order of evaluation, it says that modifying an object twice produces undefined behaviour.

C Pointers from Past Paper [duplicate]

This question already has answers here:
Why are these constructs using pre and post-increment undefined behavior?
(14 answers)
Closed 8 years ago.
I have another C pointers question.
Consider executing the following program:
int x[5] = {0,3,5,7,9};
int* y = &x[2];
*(y+2) = *(y--);
What values does the array x hold afterwards?
What the hell is going on with y--? I know how *(y+2) works, and understand the rest, but not how y-- ties in with the rest.
Also, the answer given is {0, 3, 5, 5, 9}.
There's no sequence point between y-- and y + 2 in *(y+2) = *(y--);, so whether y + 2 refers to &x[4] or &x[3] is unspecified. Depending on how your compiler does things, you can either get 0 3 5 5 9 or 0 3 5 7 5.
What it means that there is no sequence point between the two expressions is, in a nutshell, that it is not specified whether the side effects of one operation (--y in this case) have been applied by the time the other (y - 2) is evaluated. You can read more about sequence points here.
ISO/IEC 9899:201x
6.5 Expressions
p2: If a side effect on a scalar object is unsequenced relative to either a different side effect
on the same scalar object or a value computation using the value of the same scalar
object, the behavior is undefined. If there are multiple allowable orderings of the
subexpressions of an expression, the behavior is undefined if such an unsequenced side
effect occurs in any of the orderings.
You should not trust the answers given by your professor in this case.
Expanding on Wintermute's answer a bit...
The problem is with the statement
*(y+2) = *(y--);
The expression y-- evaluates to the current value of y, and as a side effect decrements the variable. For example:
int a = 10;
int b;
b = a--;
After the above expression has been evaluated, b will have the value 10 and a will have the value 9.
However, the C language does not require that the side effect be applied immediately after the expression has been evaluated, only that it be applied before the next sequence point (which in this case is at the end of the statement). Neither does it require that expressions be evaluated from left to right (with a few exceptions). Thus, it's not guaranteed that the value of y in y+2 represents the value of y before or after the decrement operation.
The C language standard explicitly calls operations like this out as undefined behavior, meaning that the compiler is free to handle the situation in any way it wants to. The result will vary based on the compiler, compiler settings, and even the surrounding code, and any answer will be equally correct as far as the language definition is concerned.
In order to make this well-defined and give the same result, you would need to decrement y before the assignment statement:
y--;
*(y+2) = *y;
This is consistently one of the most misunderstood and mis-taught aspects of the C language. If your professor is expecting this particular result to be well-defined, then he doesn't know the language as well as he thinks he does. Then again, he's not unique in that respect.
Repeating and expanding on the snippet from the C 2011 draft standard that Wintermute posted:
6.5 Expressions
...
2 If a side effect on a scalar object is unsequenced relative to either a different side effect
on the same scalar object or a value computation using the value of the same scalar
object, the behavior is undefined. If there are multiple allowable orderings of the
subexpressions of an expression, the behavior is undefined if such an unsequenced side
effect occurs in any of the orderings.84)
3 The grouping of operators and operands is indicated by the syntax.85) Except as specified
later, side effects and value computations of subexpressions are unsequenced.86)
84) This paragraph renders undefined statement expressions such as
i = ++i + 1;
a[i++] = i;
while allowing
i = i + 1;
a[i] = i;
85) The syntax specifies the precedence of operators in the evaluation of an expression, which is the same
as the order of the major subclauses of this subclause, highest precedence first. Thus, for example, the
expressions allowed as the operands of the binary + operator (6.5.6) are those expressions defined in
6.5.1 through 6.5.6. The exceptions are cast expressions (6.5.4) as operands of unary operators
(6.5.3), and an operand contained between any of the following pairs of operators: grouping
parentheses () (6.5.1), subscripting brackets [] (6.5.2.1), function-call parentheses () (6.5.2.2), and
the conditional operator ? : (6.5.15).
Within each major subclause, the operators have the same precedence. Left- or right-associativity is
indicated in each subclause by the syntax for the expressions discussed therein.
86) In an expression that is evaluated more than once during the execution of a program, unsequenced and
indeterminately sequenced evaluations of its subexpressions need not be performed consistently in
different evaluations.
Emphasis added. Note that this has been true since the C89 standard, although the wording has changed a bit since then.
"Unsequenced" simply means it's not guaranteed that one operation is completed before the other. The assignment operator does not introduce a sequence point, so it's not guaranteed that the LHS of the expression is evaluated before the RHS.
Now for the hard bit - your professor obviously expects a specific behavior for these kinds of expressions. If he gives a test or a quiz that asks what the result of something like a[i] = i--; will be, he's probably not going to accept an answer of "the behavior is undefined", at least not on its own. You might want to discuss the answers Wintermute and I have given with him, along with the sections of the standard quoted above.
The problem is in this statement.
*(y+2) = *(y--);
Because in C, reading a variable twice in an expression (in which it's modified) has undefined behavior.
Another example is:
i = 5;
v[i] = i++;
In this case the most likely to happen (AFAIK) is that the compiler first evalue RHS or LHS, if LHS is first evaluated, then we will have v[5] = 5; and after the assignment i will be equal to 6, if instead of that RHS is evaluated in the first place, then we will have that the evaluation of the right side will be equal to 5, but when we start evaluating the left side i will be equal to 6, so we will end up with v[6] = 5;, however, given the quote "undefined behavior allow the compiler to do anything it chooses, even to make demons fly out of your nose" you should not expect one of those options, instead of that you should expect anything, because it depends on the compiler what happens.
First of all int x[5] = {0, 3, 5, 7, 9} means
x[0] = 0, x[1] = 3, x[2] = 5, x[3] = 7, x[4] = 9
Next int *y = &x[2] Here you are trying to use pointer y to point the address of x[2]
Now here comes to your confusion *(y + 2) means you are pointing address of x[4]
and *(y--), here y-- is a post decrement operator, hence first of all the the value at *y must be used which is x[2] = 5 so now the value assigned is x[4] = 5.
The final output would be 0 3 5 7 5

Why is `x-- > 0` not undefined behaviour, while `x = x--` is?

As everyone knows, this loops through zero:
while (x-- > 0) { /* also known as x --> 0 */
printf("x = %d\n", x);
}
But x = x-- yields undefined behaviour.
Both examples need some 'return' value of x--, which is not there I guess. How can it be that x-- > 0 is defined but x = x-- is not?
Because in x = x-- you're modifying the value of x twice without an intervening sequence point. So the order of operations is not defined. In x-- > 0 the value of x is modified once, and it is clearly defined that result of evaluating x-- will be the value of x before the decrement.
I don't know where you got that idea about "need some 'return' value of x--, which is not there". Firstly, it is not exactly clear what you mean. Secondly, regardless of what you mean this doesn't seem to have anything to do with the source of undefined behavior in x = x--.
x = x-- produces undefined behavior because it attempts to modify x twice without an intervening sequence point. No "need" for any "return value" is involved here.
The underlying problem with x = x-- is that it has two side-effects that occur at undefined moments in undefined order. One side-effect is introduced by the assignment operator. Another side-effect is introduced by postfix -- operator. Both side-effects attempt to modify the same variable x and generally contradict each other. This is why the behavior in such cases is declared undefined de jure.
For example, if the original value of x was 5, then your expression requires x to become both 4 (side-effect of decrement) and 5 (side-effect of assignment) at the same time. Needless to say, it is impossible for x to become 4 and 5 at the same time.
Although such a straightforward contradiction (like 4 vs 5) is not required for UB to occur. Every time you have two side-effects hitting the same variable without intervening sequence point, the behavior is undefined, even if the values these side-effects are trying to put into the variable match.
In order to understand this you need to have a basic understanding of sequence points. See this link: http://en.wikipedia.org/wiki/Sequence_point
For the = operator there is no sequence point, so there is no guarantee that the value of x will be modified before it is again assigned to x.
When you are checking the condition in the while loop x-- > 0, x-- is evaluated and the value is used in the relational operator evaluation so there is no chance of undefined behaviour because x is getting modified only once.
Just to add something to other answers, try reading this wikipedia page about sequence points.
I suggest reading https://stackoverflow.com/a/21671069/258418. If you chuck together that = is not a sequence point, and the compiler is free to interleave operations, as long as they are not separated by a sequence point from the answers linked by you, you see that i.e. the following two sequences would be legal:
load i to reg
increment i
assign reg to i
=> i has previous value of i
load i to reg
assign reg to i
increment i
=> i has value of previous value of i + 1
In general: avoid assigning (this includes modiying by pre/post ++/--) to the same variable twice in one expression.

Resources