# Thread: (gcc) conversion warning on ternary operator

1. ## (gcc) conversion warning on ternary operator

I'm curious as to why the ternary operator gives me a sign conversion warning (-Wsign-compare), when an if statement does not:

number is a long long
Code:
unsigned long long n = number < 0 ? ULLONG_MAX + number + 1 : number;
As opposed to:

Code:
unsigned long long n;
if (number < 0)
n = ULLONG_MAX + number + 1;
else
n = number;
The implicit conversion is safe, which gcc seems to understand from the if statement, but not from the ternary operator, which would require me to explicitly cast the last operand in order for the error to go away.

Is this just the compiler being a bit dumb, or is there something else to the ternary operator that makes it harder for the compiler?

2. The compiler is probably trying this implicitly, since no matter what the result is unsigned:
Code:
unsigned long long n = ((unsigned long long) number < 0) ? ULLONG_MAX + number + 1 : number;
since the condition must be evaluated first... if number is negative anyway, you get wrap around to a huge value, which triggers the warning.

Compare this to casting the last operand, which clarifies things for the parser.

3. Nice! That makes a lot of sense.

PS: Is integer sign conversion in the C11 standard defined or undefined? I haven't bought it yet. But the above almost makes me feel like getting rid of the whole thing and just declare unsigned long long n = number.

4. Maybe I'm missing something but it seems odd to me that it would convert number to unsigned before the comparison. It would always be false! Why would it do that?

I think the difference is that with the if/else you specify the receiving variable separately for each assignment, but with the conditional operator you are assigning to the same variable.

Simplified program giving the same results:
Code:
#include <stdio.h>

int main(void) {
int number = 5;
unsigned n;

n = number < 0 ? number + 1u : number;

//    if (number < 0) n = number + 1u;
//    else            n = number;

printf("%u\n", n);

return 0;
}

5. Maybe I'm missing something but it seems odd to me that it would convert number to unsigned before the comparison. It would always be false! Why would it do that?
It explains the warning though. I'm not saying I know for sure, I didn't read anything so if there is a competing reason with evidence, it's all good in the hood to share it.

PS: Is integer sign conversion in the C11 standard defined or undefined? I haven't bought it yet. But the above almost makes me feel like getting rid of the whole thing and just declare unsigned long long n = number.
Signed to unsigned is well defined. Going from unsigned to signed, it depends; you only have undefined behavior if the value would be outside of the range of the result type (i.e. you need to wrap around to a negative).

6. The implicit conversion is safe, which gcc seems to understand from the if statement, but not from the ternary operator, which would require me to explicitly cast the last operand in order for the error to go away.
From some documentation for the ternary operator:
Warning

If the types of the second and third operands are not identical, then complex type conversion rules, as specified in the C++ Standard, are invoked.

Jim

7. Originally Posted by algorism
n = number < 0 ? number + 1u : number;
Mind you, that won't get you the twos-complement.

Originally Posted by algorism
I think the difference is that with the if/else you specify the receiving variable separately for each assignment, but with the conditional operator you are assigning to the same variable.
I think that whiteflags maybe right. But you aren't off the mark either. I compared the assembly of the ternary and if statements in the following test code:

Code:
/* driver.c -- compile with -S and compare */
#include <stdio.h>
#include <limits.h>

int main(void)
{
int number = -1;
unsigned int n = (number < 0) ? UINT_MAX + number + 1 : number;

// unsigned int n;
// if (number < 0)
//     n = UINT_MAX + number + 1;
// else
//     n = number;
}
\$ gcc -S driver.c
\$ mv driver.c driver.c.ternary
// remove comments and comment ternary
\$ gcc -S driver.c

This is the assembly for the ternary operation:

Code:
main:
.LFB0:
.cfi_startproc
pushq    %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq    %rsp, %rbp
.cfi_def_cfa_register 6
movl    \$-1, -8(%rbp)
movl    -8(%rbp), %eax
movl    %eax, -4(%rbp)
movl    \$0, %eax
popq    %rbp
.cfi_def_cfa 7, 8
ret
And this is with the if statement:

Code:
main:
.LFB0:
.cfi_startproc
pushq    %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq    %rsp, %rbp
.cfi_def_cfa_register 6
movl    \$-1, -8(%rbp)
cmpl    \$0, -8(%rbp)
jns    .L2
movl    -8(%rbp), %eax
movl    %eax, -4(%rbp)
jmp    .L3
.L2:
movl    -8(%rbp), %eax
movl    %eax, -4(%rbp)
.L3:
movl    \$0, %eax
popq    %rbp
.cfi_def_cfa 7, 8
ret
My assembly is rusty, but i think the answer is that in the if statement the code is explicitly comparing with cmpl to decide wether to jump, with jns (jump no sign).

The ternary operator on the other hand simplifies everything and doesn't even compare. The whole expression must have been first evaluated by the compiler and the final result translated (an optimization that I didn't ask and must be intrinsic to the operator).

The thing to keep in mind is that cmpl does a unsigned comparison. So, the comparison can be read as "cmpl \$0, (unsigned)-8(%rbp)", or perhaps better, "if (unsigned)-8(%rbp) > \$0 then jump", which is exactly what whiteflags was on about and I bet is precisely what gcc is doing during its evaluation of the ternary operator.

8. Originally Posted by Mario F.
Mind you, that won't get you the twos-complement.
I just wanted the simplest program that demonstrates the oddity.

The thing to keep in mind is that cmpl does a unsigned comparison. So, the comparison can be read as "cmpl \$0, (unsigned)-8(%rbp)", or perhaps better, "if (unsigned)-8(%rbp) > \$0 then jump", which is exactly what whiteflags was on about and I bet is precisely what gcc is doing during its evaluation of the ternary operator.
I don't think the comparisons are signed or unsigned. It's the jump statements that determine that, and jns is obviously "signed".

I'm still obviously not getting it. If number is converted to unsigned before the comparison than the comparison will never be true. It makes no sense to me.

9. Originally Posted by algorism
I don't think the comparisons are signed or unsigned. It's the jump statements that determine that, and jns is obviously "signed".
Well, they aren't of course. That's why they are all "unsigned". I was being simplistic. Because ultimately what the comparisons the CMPx family of operations do is subtracting the bits of the second operand from the bits of the first operand and setting the flags, regardless of any high-level representations we may wish to give those bits. Converting from signed to unsigned does not change the bits in a twos-complement system.

The jumps can be indeed signed or unsigned. But there's a third family of jumps that are neither. They are the so-called flag jumps. And JNS is not a signed jump. It is a flag jump for the SF (sign flag). If SF is zero JNS jumps. SF becomes zero if any of the CMP operations returns a negative number. That is, the first operand is smaller than the second operand.

Originally Posted by algorism
I'm still obviously not getting it. If number is converted to unsigned before the comparison than the comparison will never be true. It makes no sense to me.
You are right. It doesn't in fact. Look what happens:

Code:
unsigned int n = (number < 0) ? 12 : number;  // no warning!
/**/
unsigned int n = (number < 0) ? 12U : number;  // warning!
/**/
unsigned int n = (number < 0) ? 12U : (unsigned) number;  // no warning!
So it seems gcc is just being silly with the ternary operator and issuing a useless warning under most circumstances, only because one of the branches requires a sign conversion (and its ok when both require it). Would fill in a bug report, if that crowd at the gcc wasn't so hostile.