Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

larswik

macrumors 68000
Original poster
Sep 8, 2006
1,552
11
I have a simple method I created to swap 2 images. I am trying to reduce the code down from the if statement to a ternary operator. The if statment functions as expected but for some reason when I comment out the If statement the ternary will not increment the value, and always has the value of 1. The m1 variable is a global int.

As I understand it, the condition in the ternary is evaluated first and then assigned back to the variable. I must be over looking something simple here.

Code:
-(IBAction)monthOneButton:(id)sender{
  
    m1 = (m1 < 2)? m1++ : 1;
    d1.image = [numImageArray objectAtIndex:m1];
   
    // Below is the old way, above is what I want.
   
    /*
    if (m1 == 1) {
        d1.image = [numImageArray objectAtIndex:2];
        m1 = 2;
    }
    else{
         d1.image = [numImageArray objectAtIndex:1];
        m1 = 1;
    }
     */
   
}
 

dylanryan

macrumors member
Aug 21, 2011
57
2
You understand the ternary operator correctly. However, you are using post-increment on m1 (i.e. m1++). This increments m1 (from 1 to 2), but then returns the original value. Stripping out the ternary operator, this:

Code:
m1 = m1++;

is equivalent to

Code:
int temp=m1;
m1=m1+1; // m1++
m1=temp; // m1=...

You can use pre-increment (i.e. ++m1), which increments m1 and then returns the new value. But that feels a little odd:

Code:
m1 = ++m1;

is equivalent to

Code:
m1=m1+1; // ++m1
m1=m1; // m1=...

The compiler will optimize that out so it isn't a problem, it just feels odd to me. I'd suggest just using m1+1 in that clause, since that is clearer.

Edit: If your values are only ever 1 or 2, you could just set it directly to 2 instead... i.e.
m1 = (m1 < 2)? 2 : 1;
 
  • Like
Reactions: balamw

subsonix

macrumors 68040
Feb 2, 2008
3,551
79
But you use post increment on the m1 variable, so it will be assigned first, then incremented.
 

balamw

Moderator emeritus
Aug 16, 2005
19,366
979
New England
Edit: If your values are only ever 1 or 2, you could just set it directly to 2 instead... i.e.
m1 = (m1 < 2)? 2 : 1;

This makes a lot of sense to me, and prevents unexpected values of m1 if 1 and 2 are the only choices. E.g. Would you want it to return 1 if m1 starts at 0?

B
 

larswik

macrumors 68000
Original poster
Sep 8, 2006
1,552
11
This makes a lot of sense to me, and prevents unexpected values of m1 if 1 and 2 are the only choices. E.g. Would you want it to return 1 if m1 starts at 0?

B
Thanks guys, now I see the problem. The 2:1 will work here, but my values will be 0-9 for other methods which is why I wanted to increment. m1++; is now m1 + 1;

Thanks!
 

gnasher729

Suspended
Nov 25, 2005
17,980
5,565
Thanks guys, now I see the problem. The 2:1 will work here, but my values will be 0-9 for other methods which is why I wanted to increment. m1++; is now m1 + 1;

m1 = (m1 < 2)? m1++ : 1;

is (most likely) undefined behaviour, because m1 is modified twice - at the least you don't know whether the ++ is performed before or after the assignment to m1, but the compiler is free to totally mess up your program because of the undefined behaviour.
 

dylanryan

macrumors member
Aug 21, 2011
57
2
m1 = (m1 < 2)? m1++ : 1;

is (most likely) undefined behaviour, because m1 is modified twice - at the least you don't know whether the ++ is performed before or after the assignment to m1, but the compiler is free to totally mess up your program because of the undefined behaviour.

Good catch. m1=m1++; is certainly undefined behavior in C/C++ (gotta love things that are defined as undefined!). I'm not so sure about this case though because the ternary operator does introduce a sequence point... Ah, but it is immediately after evaluating the conditional, so I think you are right, the assign and increment is still UB since there is no sequence point between them (that is, both occur after the sequence point the ternary created).
 

subsonix

macrumors 68040
Feb 2, 2008
3,551
79
Good catch. m1=m1++; is certainly undefined behavior in C/C++ (gotta love things that are defined as undefined!). I'm not so sure about this case though because the ternary operator does introduce a sequence point... Ah, but it is immediately after evaluating the conditional, so I think you are right, the assign and increment is still UB since there is no sequence point between them (that is, both occur after the sequence point the ternary created).

It's an interesting question, but it seems like the conditional operands are only evaluated if the condition is true. So in that regard it should be equivalent to an if/then statement. From the standard section 6.5.15

The second operand is evaluated only if the first compares unequal to 0; the third operand is evaluated only if the first compares equal to 0; the result is the value of the second or third operand (which ever is evaluated), converted to the type described below. 95)

But then comes a bit that could be relevant.

If an attempt is made to modify the result of a conditional operator or to access it after the next sequence point, the behavior is undefined.

In this case an attempt is made to modify the result no? And why would it matter if (m1 + 1) or (++m1) is used if that's the case?
 

dylanryan

macrumors member
Aug 21, 2011
57
2
It's an interesting question, but it seems like the conditional operands are only evaluated if the condition is true. So in that regard it should be equivalent to an if/then statement. From the standard section 6.5.15



But then comes a bit that could be relevant.



In this case an attempt is made to modify the result no? And why would it matter if (m1 + 1) or (++m1) is used if that's the case?


m1+1 should be safe: that doesn't modify m1 or the result of the ternary. m1 is only modified once by the assignment. Basically, m1=m1+1; is safe (or at least, it had better be!). And the reult of the ternary will be the value of m1+1 (let's say that that happens to be 2). That value (2) is then never modified. So no UB there, even though there were two chances to find some.



Pre-increment is interesting, though. I suspect (but am unsure) it is technically UB but all the obvious ways to implement it have the same result; it seems to me that a compiler would have to be deliberately malicious to find a way to generate code for m1=++m1; that doesn't work as expected. Though I guess you should never bet against that possibility... But it still looks bizarre and is something I would avoid regardless of the UB. Though I think this is UB only because of the assignment and modification of m1. The result of the ternary is not modified or accessed past the last sequence point (the result is the value of ++m1, which again is just a number, like 2, that is never modified). The UB is because the incr hasn't necessarily updated the actual value of m1 yet by the time the assign happens.

At least I think. I'm not an expert.
 

subsonix

macrumors 68040
Feb 2, 2008
3,551
79
m1+1 should be safe: that doesn't modify m1 or the result of the ternary. m1 is only modified once by the assignment. Basically, m1=m1+1; is safe (or at least, it had better be!). And the reult of the ternary will be the value of m1+1 (let's say that that happens to be 2). That value (2) is then never modified. So no UB there, even though there were two chances to find some.

Pre-increment is interesting, though. I suspect (but am unsure) it is technically UB but all the obvious ways to implement it have the same result; it seems to me that a compiler would have to be deliberately malicious to find a way to generate code for m1=++m1; that doesn't work as expected.

Well, I think I'm mistaken because the result is which ever operand is evaluated depending on the condition. But as far as I can tell, both m1 + 1 and ++m1 should evaluate to m1 + 1, it's just m1++ that evaluates to m1. But I too generally avoid using both postfix and prefix operators when there are side effects.

Edit:

6.5.3.1 said:
The value of the operand of the prefix ++ operator is incremented. The result is the new value of the operand after incrementation. The expression ++E is equivalent to (E+=1).
 
Last edited:

dylanryan

macrumors member
Aug 21, 2011
57
2
Well, I think I'm mistaken because the result is which ever operand is evaluated depending on the condition. But as far as I can tell, both m1 + 1 and ++m1 should evaluate to m1 + 1, it's just m1++ that evaluates to m1. But I too generally avoid using both postfix and prefix operators when there are side effects.

Agreed. I think in this case the ternary itself is safe, so lets ignore it and focus just on m1=++m1; In this case, what I do not know is if the spec requires that the value of m1 in RAM actually be modified immediately, or if it can be deferred slightly. The computer obviously has to compute m1+1 in any event, and stash that in a register, but I don't know if it must immediately push that off to RAM or if it can wait. Lets just say that m1 was initially 1. In this case, the compiler will need to plant code to write the value of 2 back to m1 in RAM twice, and the order of those might be undefined. Obviously, though, since both writes are 2, it really doesn't matter. (i.e. it doesn't matter if it writes 2 before 2, because they are the same)

But consider a similar case: m1=++m1+1; If m1 is initially 1, now there might be overlapping writes of 2 (++m1) and 3 (m1=...) being sent to RAM. Since there are no sequence points involved, the order of those two writes is probably undefined behavior (unless the spec defines that ++m1 is atomic, which basically requires it to have a sequence point). In this case, if matters if it decides to write the 2 before the 3 or vice versa.

And even in the first case, where the UB is only on what order two equivalent writes happen, I still am cautions. In the face of undefined behavior, a compiler COULD plant code to format your hard drive (or do anything else it wants) without violating the spec. It won't, but it could. So, just because it is harmless in all the obvious implementations doesn't mean it always will be. (I always think that it would be amusing to write a compiler with a --hard-mode switch to enable format-hard-drive-on-undefined-behavior. Horrifying, but amusing if used in a sandbox/VM just to see how much UB we actually encounter.)
 

subsonix

macrumors 68040
Feb 2, 2008
3,551
79
Agreed. I think in this case the ternary itself is safe, so lets ignore it and focus just on m1=++m1; In this case, what I do not know is if the spec requires that the value of m1 in RAM actually be modified immediately, or if it can be deferred slightly. The computer obviously has to compute m1+1 in any event, and stash that in a register, but I don't know if it must immediately push that off to RAM or if it can wait. Lets just say that m1 was initially 1. In this case, the compiler will need to plant code to write the value of 2 back to m1 in RAM twice, and the order of those might be undefined. Obviously, though, since both writes are 2, it really doesn't matter. (i.e. it doesn't matter if it writes 2 before 2, because they are the same)

But consider a similar case: m1=++m1+1; If m1 is initially 1, now there might be overlapping writes of 2 (++m1) and 3 (m1=...) being sent to RAM. Since there are no sequence points involved, the order of those two writes is probably undefined behavior (unless the spec defines that ++m1 is atomic, which basically requires it to have a sequence point). In this case, if matters if it decides to write the 2 before the 3 or vice versa.

And even in the first case, where the UB is only on what order two equivalent writes happen, I still am cautions. In the face of undefined behavior, a compiler COULD plant code to format your hard drive (or do anything else it wants) without violating the spec. It won't, but it could. So, just because it is harmless in all the obvious implementations doesn't mean it always will be. (I always think that it would be amusing to write a compiler with a --hard-mode switch to enable format-hard-drive-on-undefined-behavior. Horrifying, but amusing if used in a sandbox/VM just to see how much UB we actually encounter.)

I agree that you shouldn't depend on UB. But in this case both ++m1 and m1 + 1 are equivalent and evaluate to m1 += 1. It's required to by the standard. The same whould be true for ++m1+1, that expression evaluates to (m1 + 1 + 1). It's only m1++ that applies to UB, since it modifies the value after it's been evaluated, which is undefined behaviour for the ternary operator. I don't think you need to think about what the compiler does, it's the result that matters as there could be many different implementations on different CPUs and compilers. It's the entire point of the C abstract machine and undefined behaviour to begin with.
 
Last edited:

dylanryan

macrumors member
Aug 21, 2011
57
2
I agree that you shouldn't depend on UB. But in this case both ++m1 and m1 + 1 are equivalent and evaluate to m1 += 1. It's required to by the standard. The same whould be true for ++m1+1, that expression evaluates to (m1 + 1 + 1). It's only m1++ that applies to UB, since it modifies the value after it's been evaluated, which is undefined behaviour for the ternary operator. I don't think you need to think about what the compiler does, it's the result that matters as there could be many different implementations on different CPUs and compilers.

Ah, you're right. ++m1 is equivalent to (m1+=1) which is equivalent to (m1=m1+1), and that equals sign does create a sequence point, and the added parens ensure that it is evaluated before the rest, which means that it has to be fully evaluated before other things on the line can happen.

So... Safe but suspicious-looking enough to avoid, if only so that you don't have to double-check the spec every time you look at the code!
 

theluggage

macrumors 604
Jul 29, 2011
7,585
7,679
So... Safe but suspicious-looking enough to avoid, if only so that you don't have to double-check the spec every time you look at the code!

More to the point forget the language lawyer stuff,

Code:
m1 = (m1 < 2) ? ++m1 : 1;

...is just unnecessarily obtuse, as illustrated by the fact that the original poster used the wrong operator the first time and couldn't spot the mistake.

Code:
m1 = (m1 < 2) ? m1 + 1 : 1;

Is much clearer - leave it up to the compiler to optimise the +1 to an increment, not that a button click handler needs much optimising!
 

zeppenwolf

macrumors regular
Nov 17, 2009
129
3
Code:
m1 < 2 ? m1++ : m1 = 1;

Meets OP's needs, is as clear as it gets, and no need to even care whether we use postfix or prefix increment operator.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.