Thread: (gcc) conversion warning on ternary operator

  1. #1
    (?<!re)tired Mario F.'s Avatar
    Join Date
    May 2006
    Location
    Ireland
    Posts
    8,446

    (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?
    Originally Posted by brewbuck:
    Reimplementing a large system in another language to get a 25% performance boost is nonsense. It would be cheaper to just get a computer which is 25% faster.

  2. #2
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,612
    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. #3
    (?<!re)tired Mario F.'s Avatar
    Join Date
    May 2006
    Location
    Ireland
    Posts
    8,446
    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.
    Last edited by Mario F.; 10-12-2016 at 03:14 PM.
    Originally Posted by brewbuck:
    Reimplementing a large system in another language to get a 25% performance boost is nonsense. It would be cheaper to just get a computer which is 25% faster.

  4. #4
    Registered User
    Join Date
    Jun 2015
    Posts
    1,640
    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. #5
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,612
    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).
    Last edited by whiteflags; 10-12-2016 at 04:03 PM.

  6. #6
    Registered User
    Join Date
    May 2010
    Posts
    4,632
    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. #7
    (?<!re)tired Mario F.'s Avatar
    Join Date
    May 2006
    Location
    Ireland
    Posts
    8,446
    Quote Originally Posted by algorism View Post
    n = number < 0 ? number + 1u : number;
    Mind you, that won't get you the twos-complement.

    Quote Originally Posted by algorism View Post
    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.
    Last edited by Mario F.; 10-12-2016 at 05:18 PM.
    Originally Posted by brewbuck:
    Reimplementing a large system in another language to get a 25% performance boost is nonsense. It would be cheaper to just get a computer which is 25% faster.

  8. #8
    Registered User
    Join Date
    Jun 2015
    Posts
    1,640
    Quote Originally Posted by Mario F. View Post
    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. #9
    (?<!re)tired Mario F.'s Avatar
    Join Date
    May 2006
    Location
    Ireland
    Posts
    8,446
    Quote Originally Posted by algorism View Post
    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.

    Quote Originally Posted by algorism View Post
    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.
    Last edited by Mario F.; 10-12-2016 at 08:54 PM.
    Originally Posted by brewbuck:
    Reimplementing a large system in another language to get a 25% performance boost is nonsense. It would be cheaper to just get a computer which is 25% faster.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Ternary operator
    By kkk in forum C Programming
    Replies: 12
    Last Post: 07-03-2011, 11:35 PM
  2. Ternary Operator to choose which operator to use
    By cncool in forum C Programming
    Replies: 7
    Last Post: 06-27-2011, 01:35 PM
  3. bug in ternary operator ?:
    By wiglaf in forum C Programming
    Replies: 14
    Last Post: 03-31-2011, 10:26 PM
  4. Is the PHP ternary operator broken?
    By mike_g in forum Tech Board
    Replies: 13
    Last Post: 07-18-2009, 05:04 PM
  5. ternary operator
    By abhijith gopal in forum C Programming
    Replies: 37
    Last Post: 07-10-2006, 11:58 AM

Tags for this Thread