Why is it so that
unsigned char k=-1
if(k==-1)
is false
unsigned int k=-1
if(k==-1)
is true
For the purpose of demonstration let's assume 8-bit chars and 32-bit ints.
unsigned char k=-1;
k is assigned the value 255.
if(k==-1)
The left-hand side of the == operator is an unsigned char. The right-hand side is an int. Since all possible values of an unsigned char can fit inside an int, the left-hand side is converted to an int (this is performed due the the integer promotions, quoted below). This results in the comparison (255 == -1), which is false.
unsigned int k=-1
k is assigned the value 4294967295
if(k==-1)
This time, the left-hand side (an unsigned int) cannot fit within an int. The standard says that in this case, both values are converted to an unsigned int. So this results in the comparison (4294967295 == 4294967295), which is true.
The relevant quotes from the standard:
Integer promotions: (C99, 6.3.1.1p2)
If an int can represent all values of the original type, the value is converted to an int; otherwise, it is converted to an unsigned int.
Usual arithmetic conversions: (6.3.1.8).
[For integral operands, ] the integer promotions are performed on both operands. Then the following rules are applied to the promoted operands:
- If both operands have the same type, then no further conversion is needed.
...
- Otherwise, if the operand that has unsigned integer type has rank greater or
equal to the rank of the type of the other operand, then the operand with
signed integer type is converted to the type of the operand with unsigned
integer type.
...
§6.3.1.1p2 of the C11 standard draft (n1570.pdf):
If an int can represent all values of the original type (as restricted
by the width, for a bit-field), the value is converted to an int;
otherwise, it is converted to an unsigned int. These are called the
integer promotions.58) All other types are unchanged by the integer
promotions.
In your second case, an int can't represent unsigned int k because that's out of range. Both operands end up being converted to unsigned int and compare equal.
As there is no sign extension in unsigned promotion the result is defferent.
unsigned char k is promoted from unsigned char (value=255) to int (value=255).
In the case of unsigned int k, -1 is promoted from int (value=-1) to unsigned int (value=2^32-1).
unsigned char k = -1;
-1 is an integer literal of type int, which is equivalent to signed int. When you assign a large signed integer to a smaller unsigned type, the result will get truncated in undefined ways, the C standard does not guarantee what will happen.
In the real world outside the C standard, this is what's most likely (assuming 32-bit CPU, two's complement):
-1 is 0xFFFFFFFF. The least significant byte of 0xFFFFFFFF will get assigned to k. k == 255. Try to print it using printf("%u") and see for yourself.
In the case of unsigned int k=-1, -1 is still a signed int. But it gets implicitly (silently) promoted to an unsigned int when it is stored in k. When you later compare k == -1, the right side -1 will once more get promoted to an unsigned type, and will compare equal with the data stored in k.
Related
I'm doing a few integer for myself, where I'm trying to fully understand integer overflow.
I kept reading about how it can be dangerous to mix integer types of different sizes. For that reason i wanted to have an example where a short would overflow much faster than a int.
Here is the snippet:
unsigned int longt;
longt = 65530;
unsigned short shortt;
shortt = 65530;
if (longt > (shortt+10)){
printf("it is bigger");
}
But the if-statement here is not being run, which must mean that the short is not overflowing. Thus I conclude that in the expression shortt+10 a conversion happens from short to integer.
This is a bit strange to me, when the if statement evaluates expressions, does it then have the freedom to assign a new integer type as it pleases?
I then thought that if I was adding two short's then that would surely evaluate to a short:
unsigned int longt;
longt = 65530;
unsigned short shortt;
shortt = 65530;
shortt = shortt;
short tmp = 10;
if (longt > (shortt+tmp)){
printf("Ez bigger");
}
But alas, the proporsition still evaluates to false.
I then try do do something where I am completely explicit, where I actually do the addition into a short type, this time forcing it to overflow:
unsigned int longt;
longt = 65530;
unsigned short shortt;
shortt = 65530;
shortt = shortt;
short tmp = shortt + 10;
if (longt > tmp){
printf("Ez bigger");
}
Finally this worked, which also would be really annoying if it did'nt.
This flusters me a little bit though, and it reminds me of a ctf exercise that I did a while back, where I had to exploit this code snippet:
#include <stdio.h>
int main() {
int impossible_number;
FILE *flag;
char c;
if (scanf("%d", &impossible_number)) {
if (impossible_number > 0 && impossible_number > (impossible_number + 1)) {
flag = fopen("flag.txt","r");
while((c = getc(flag)) != EOF) {
printf("%c",c);
}
}
}
return 0;
}
Here, youre supposed to trigger a overflow of the "impossible_number" variable which was actually possible on the server that it was deployed upon, but would make issues when run locally.
int impossible_number;
FILE *flag;
char c;
if (scanf("%d", &impossible_number)) {
if (impossible_number > 0 && impossible_number > (impossible_number + 1)) {
flag = fopen("flag.txt","r");
while((c = getc(flag)) != EOF) {
printf("%c",c);
}
}
}
return 0;
You should be able to give "2147483647" as input, and then overflow and hit the if statement. However this does not happen when run locally, or running at an online compiler.
I don't get it, how do you get an expression to actually overflow the way that is is actually supossed to do, like in this example from 247ctf?
I hope someone has a answer for this
How you avoid implicit conversion from short to integer during addition?
You don't.
C has no arithmetic operations on integer types narrower than int and unsigned int. There is no + operator for type short.
Whenever an expression of type short is used as the operand of an arithmetic operator, it is implicitly converted to int.
For example:
short s = 1;
s = s + s;
In s + s, s is promoted from short to int and the addition is done in type int. The assignment then implicitly converts the result of the addition from int to short.
Some compilers might have an option to enable a warning for the narrowing conversion from int to short, but there's no way to avoid it.
What you're seeing is a result of integer promotions. What this basically means it that anytime an integer type smaller than int is used in an expression it is converted to int.
This is detailed in section 6.3.1.1p2 of the C standard:
The following may be used in an expression wherever an int or unsigned int may be used:
An object or expression with an integer type (other than int or unsigned int) whose integer conversion rank is less than or equal to
the rank of int and unsigned int.
A bit-field of type _Bool, int, signed int, or unsigned int.
If an int can represent all values of the original type (as restricted
by the width, for a bit-field), the value is converted to an int;
otherwise, it is converted to an unsigned int. These are called the
integer promotions. All other types are unchanged by the integer
promotions
That is what's happening here. So let's look at the first expression:
if (longt > (shortt+10)){
Here we have a unsigned short with value 65530 being added to the constant 10 which has type int. The unsigned short value is converted to an int value, so now we have the int value 65530 being added to the int value 10 which results in the int value 65540. We now have 65530 > 65540 which is false.
The same happens in the second case where both operands of the + operator are first promoted from unsigned short to int.
In the third case, the difference happens here:
short tmp = shortt + 10;
On the right side of the assignment, we still have the int value 65540 as before, but now this value needs to be assigned back to a short. This undergoes an implementation defined conversion to short, which is detailed in section 6.3.1.3:
1 When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new
type, it is unchanged.
2 Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that
can be represented in the new type until the value is in the range of
the new type.
3 Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an
implementation-defined signal is raised.
Paragraph 3 takes effect in this particular case. In most implementations you're likely to come across, this will typically mean "wraparound" of the value.
So how do you work with this? The closest thing you can do is either what you did, i.e. assign the intermediate result to a variable of the desired type, or cast the intermediate result:
if (longt > (short)(shortt+10)) {
As for the "impossible" input in the CTF example, that actually causes signed integer overflow as a result of the the addition, and that triggers undefined behavior. For example, when I ran it on my machine, I got into the if block if I compiled with -O0 or -O1 but not with -O2.
How you avoid implicit conversion from short to integer during addition?
Not really avoidable.
On 16-bit and wider machines, the conversion short to int and unsigned short to unsigned does not affect the value. But addition overflow and the implicit conversion from int to unsigned renders a different result in 16-but vs. 32-bit for OP's values. For in 16-bit land, unsigned short to int does not implicitly occur. Instead, code does unsigned short to unsigned.
int/unsigned as 16-bit
If int/unsigned were 16-bit -common on many embedded processors, then shortt would not convert to an int, but to unsigned.
// Given 16-bit int/unsigned
unsigned int longt;
longt = 65530; // 32-bit long constant assigned to 16-bit unsigned - no value change as value in range.
unsigned short shortt;
shortt = 65530; // 32-bit long constant assigned to 16-bit unsigned short - no value change as value in range.
// (shortt+10)
// shortt+10 is a unsigned short + int
// unsigned short promotes to unsigned - no value change.
// Then since unsigned + int, the int 10 converts to unsigned 10 - no value change.
// unsigned 65530 + unsigned 10 exceeds unsigned range so 65536 subtracted.
// Sum is 4.
// Statment is true.
if (longt > (shortt+10)){
printf("it is bigger");
}
It is called an implicit conversion.
From C standard:
Several operators convert operand values from one type to another
automatically. This subclause specifies the result required from such
an implicit conversion, as well as those that result from a cast
operation (an explicit conversion ). The list in 6.3.1.8 summarizes
the conversions performed by most ordinary operators; it is
supplemented as required by the discussion of each operator in 6.5
Every integer type has an integer conversion rank defined as follows:
No two signed integer types shall have the same rank, even if they
have the same representation.
The rank of a signed integer type
shall be greater than the rank of any signed integer type with less
precision.
The rank of long long int shall be greater than the rank
of long int, which shall be greater than the rank of int, which shall
be greater than the rank of short int, which shall be greater than the
rank of signed char.
The rank of any unsigned integer type shall
equal the rank of the corresponding signed integer type, if any.
The
rank of any standard integer type shall be greater than the rank of
any extended integer type with the same width.
The rank of char
shall equal the rank of signed char and unsigned char.
The rank of
_Bool shall be less than the rank of all other standard integer types.
The rank of any enumerated type shall equal the rank of the
compatible integer type (see 6.7.2.2).
The rank of any extended
signed integer type relative to another extended signed integer type
with the same precision is implementation-defined, but still subject
to the other rules for determining the integer conversion rank.
For
all integer types T1, T2, and T3, if T1 has greater rank than T2 and
T2 has greater rank than T3, then T1 has greater rank than T3.
The
following may be used in an expression wherever an int or unsigned int
may be used:
— An object or expression with an integer type (other than int or unsigned
int) whose integer conversion rank is less than or equal to the rank
of int and unsigned int.
A bit-field of type _Bool, int, signed int,
or unsigned int. If an int can represent all v alues of the original
type (as restricted by the width, for a bit-field), the value is
converted to an int; otherwise, it is converted to an unsigned int.
These are called the integer promotions.58) All other types are
unchanged by the integer promotions.
The integer promotions preserve
value including sign. As discussed earlier, whether a ‘‘plain’’ char
is treated as signed is implementation-defined.
You cant avoid implicit conversion but you can cast the result of the operation to the required type
if (longt > (short)(shortt+tmp))
{
printf("Ez bigger");
}
https://godbolt.org/z/39Exa8E7K
But this conversion invokes Undefined Behaviour as your short integer overflows. You have to be very careful doing it as it can be a source of very hard to find and debug errors.
while writing the code I observe one thing in my code its related to the comparison of bit-field value with negative integers.
I have one structure member of unsigned of size one bit and one unsigned int. When I compare the negative value with unsigned int variable I am getting expected result as 1 but when I compare the structure member with the negative value I am getting the opposite result as 0.
#include <stdio.h>
struct S0
{
unsigned int bit : 1;
};
struct S0 s;
int main (void)
{
int negVal = -3;
unsigned int p = 123;
printf ("%d\n", (negVal > p)); /*Result as 1 */
printf ("%d\n", (negVal > s.bit));/*Result as 0 but expected 1 */
return 0;
}
My doubt is if I compare the negative value with unsigned int then balancing will happen (implicit type casting). But if I compare structure member of unsigned int why implicit type casting is not happening. Correct me if I miss any basics of bit fields?
(move my remark as an answer)
gcc promotes s.bit to an int, so (negVal > s.bit) does (-3 > 0) valuing 0
See Should bit-fields less than int in size be the subject of integral promotion? but your question is not a duplicate of it.
(negVal > p) returns 1 because negVal is promoted to unsigned producing a big value, see Signed/unsigned comparisons
For illustration, the following uses a 32-bit int and a 32-bit unsigned int.
In negVal > p:
negVal is an int with value −3.
p is an unsigned int with value 123.
C 2018 6.5.8 3, which is discusses > and the other relational operators, tells us that the usual arithmetic conversions are performed on the operands.
6.3.1.8 1 defines the usual arithmetic conversions. For integer types, the first step of the usual arithmetic conversions is to perform the integer promotions on each operand.
6.3.1.1 2 defines the integer promotions. int, unsigned int, and integer types wider than these are unchanged. For other integer types, it says: ”If an int can represent all values of the original type (as restricted by the width, for a bit-field), the value is converted to an int; otherwise, it is converted to an unsigned int.”
Since negVal is an int, it is unchanged by the integer promotions.
Since p is an unsigned int, it is unchanged by the integer promotions.
The next step in the usual arithmetic conversions is to convert one operand to the type of the other. For int and unsigned int, the int is converted to unsigned int.
Converting the int −3 to unsigned int results in 4,294,967,293. (The conversion is defined to add or subtracting UINT_MAX + 1, which is 4,294,967,296, to the value as many times as necessary to bring it in range. This is equivalent to “wrapping” modulo 4,294,967,296 or to reinterpreting the two’s complement representation of −3 as an unsigned int.)
After the conversions, the expression negVal > p has become 4294967293u > 123u.
This comparison is true, so the result is 1.
In negVal > s.bit:
negVal is an int with value −3.
s.bit is a one-bit bit-field with value 0.
As above, the usual arithmetic conversions are performed on the operands.
As above, the first step of the usual arithmetic conversions is to perform the integer promotions on each operand.
Since negVal is an int, it is unchanged by the integer promotions.
Since s.bit is a bit-field narrower than an int, it will be converted by the integer promotions. This one-bit bit-field can represent either 0 or 1. Both of these can be represented by an int, and therefore the rule “If an int can represent all values of the original type (as restricted by the width, for a bit-field), the value is converted to an int” applies.
Converting 0 to int results in 0.
The next step in the usual arithmetic conversions would be to convert one operand to the type of the other. Since both operands are now int, no conversion is needed.
After the conversions, the expression negVal > s.bit has become -3 > 0.
This comparison is false, so the result is 0.
This topic has been heavily discussed in many context. When I search and read some of posts. I was confused by following post.
Signed to unsigned conversion in C - is it always safe?
The following is the original question.
unsigned int u = 1234;
int i = -5678;
unsigned int result = u + i;
The answer simply quotes the "6.3.1.8 Usual arithmetic conversions" point 3, i.e.,
Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.
However, if my understanding is correct, the integer promotion should be done before considering "usual arithmetic conversions".
And the rules for that is
If an int can represent all values of the original type, the value is converted to an int . Otherwise, it is converted to an unsigned int . These conversion rules are called the integral promotions
So, that means the addition is completed with type of signed int than unsigned int. And the conversion to a large value occurs when assigning an negative to unsigned int result.
I am a bit non-confident on my understanding. Does anyone have similar confusion on that post?
Any reply or comment is welcome. Thanks advance!
Jeff
unsigned int result = u + i;
We have an additive operator. Additive operators with operands of arithmetic type perform usual arithmetic conversions first.
Usual arithmetic conversions are done in two steps:
First integer integer promotions are applied to each operand:
u is not promoted to int as it is unsigned int
i is already int, no need to promote it to int
As operands actually have a type rank equal to int, so no integer promotion is performed.
Second, bring operand to a common type. The common type is unsigned int.
u is already unsigned int
i is type int and is converted to unsigned int
After the usual arithmetic conversions the two values of type unsigned int are added and the result assigned to result.
However, if my understanding is correct, the integer promotion should be done before considering "usual arithmetic conversions".
Correct, but integer promotion, as defined by the integer promotion rule, only applies to small integer types, such as char and short. You only cited half of the paragraph, which might have made you confused. Let me cite that paragraph as whole:
C11 6.3.1.1/2
The following may be used in an expression wherever an int or unsigned
int may be used:
An object or expression with an integer type (other than int or unsigned int) whose integer conversion rank is less than or equal to
the rank of int and unsigned int.
A bit-field of type _Bool, int, signed int, or unsigned int.
If an int can represent all values of the original type (as restricted
by the width, for a bit-field), the value is converted to an int;
otherwise, it is converted to an unsigned int. These are called the
integer promotions. All other types are unchanged by the integer
promotions.
So integer promotion do not apply to int or unsigned int, because they do not have lesser conversion rank.
So, that means the addition is completed with type of signed int than unsigned int. And the conversion to a large value occurs when assigning an negative to unsigned int result.
Incorrect, the addition is done on usigned int, because of the usual arithmetic conversions.
I will use hexadecimal representation in order to explain the issue...
Assuming that the size of an int variable is 32 bits:
+1234 = 0x000004D2
-5678 = 0xFFFFE9D2
When doing a+b, it doesn't matter how each one of them is represented (signed or unsigned). If you add the two values above and store the result in an int variable, then it will hold a value of 0xFFFFEEA4, regardless of the sign of each operand:
If the output variable is signed, then it will be treated (in other operations) as -4444.
If the output variable is unsigned, then it will be treated (in other operations) as 4294962852.
I am learning the c language using the K&R book. In the second chapter book, the author talks about implicit conversion. There book says this:
Conversion rules are more complicated when unsigned operands are involved. The problem is that
comparisons between signed and unsigned values are machine-dependent, because they depend on the sizes of the various integer types. For example, suppose that int is 16 bits and long is 32 bits. Then -1L < 1U, because 1U, which is an unsigned int, is promoted to a signed long. But -1L >
1UL because -1L is promoted to unsigned long and thus appears to be a large positive number.
I tried the code below in two different scenarios:
compiled on an x86 64bits platform and executed. Where sizeof(-1L) -> 8byte and sizeof(1U) -> 4 bytes
compiled on an x86 32bits platform and executed. Where sizeof(-1L) -> 4byte and sizeof(1U) -> 4 bytes
The code:
int main() {
if(-1L > 1U)
printf("true");
else
printf("false");
return 0;
}
The results:
x86 64bits: false
x86 32bits: true
so I'm getting two different OP in each case.
As author says, for 2 different data sizes one being 16 and the other 32, it holds good in my x86-64 case.
But im not able to understand why in the second case for 32 bits, I'm getting true.
As author says unsigned int is promoted to signed long int, if this is true then both
should be 4 bytes wide, then why is it printing true instead of false? As now both should be signed long.
As the author says it is machine dependent, then both long and int should have same byte size, so how the implicit conversion is happening here?
My understanding is that -1 is stored as two's complement i.e 0xFFFFFFFF > 0x1 so in the second case it should be true.
But this explanation contradicts the 1st case.
Please correct me if what I think is wrong, as I am new to implicit conversion.
Can anyone please explain this behaviour?
lets explain the rank system first
6.3.1 Arithmetic operand(c99 standard)
A) The rank of a signed integer type shall be greater than the rank of any signed integer
type with less precision(more bytes higher precision higher rank)
B) The rank of long long int shall be greater than the rank of long int, which shall be
greater than the rank of int, which shall be greater than the rank of short int, which
shall be greater than the rank of signed char.
C) The rank of any unsigned integer type shall equal the rank of the corresponding signed
integer type, if any.
(in other words if your system unsigned int is 32bits and your int is 32bits then the
ranks of these are the same.)
the above explains the rank.
now coming to arithmetic conversions.
6.3.1.8 Usual arithmetic conversions (c99 standard)
1)If both operands have the same type, then no further conversion is needed.
2)Otherwise, if both operands have signed integer types or both have unsigned integer
types, the operand with the type of lesser integer conversion rank is converted to the
type of the operand with greater rank.(similar to 1)
3)Otherwise, if the operand that has unsigned integer type has rank greater or equal to
the rank of the type of the other operand, then the operand with signed integer type is
converted to the type of the operand with unsigned integer type.
4)Otherwise, if the type of the operand with signed integer type can represent all of the
values of the type of the operand with unsigned integer type, then the operand with
unsigned integer type is converted to the type of the operand with signed integer type
5)Otherwise, both operands are converted to the unsigned integer type corresponding to the
type of the operand with signed integer type.
2) compiled on an x86 32bits platform and executed. Where sizeof(-1L) -> 4byte and sizeof(1U) -> 4 bytes
in your case look at statement 3 & C. the unsigned value(4bytes) has rank equal to the signed value(4btyes) therefore the singed value is converted to an unsigned value, when this happens the, the sign bit makes this look like a extremely large value. -1L > 1U therefore is true
1) compiled on an x86 64bits platform and executed. Where sizeof(-1L) -> 8byte and sizeof(1U) -> 4 bytes
in this case, the unsigned value rank is less than the rank of the singed value. look at 4).
the signed integer(8bytes) can represent any 4byte unsigned value. therefore the unsigned 4byte value is converted to a signed value.(this will preserve the sign bit, sign bit is 0)
therefore -1L > 1U is false
But im not able to understand why in second case in 32 bit its OP-->true. As author says unsigned int is promoted to signed long int if so then both are 4 byte wide, why its printing true instead of false.? since now both are signed long.
The auther says, that if int and long have different size, then unsigned int is promoted to signed long.
If int and long have the same size, then long is too small to hold all values of unsigned int and therefore both are converted to unsigned long.
For binary arithmetic and relational operators:
If either operand has type long double, the other operand is converted to long double. Otherwise, if either operand has type double, the other operand is converted to double. Otherwise, if either operand has type float, the other operand is converted to float. Otherwise the integral promotions are performed on both operands.
(Integral promotion: A char, a short int, or an int bit-field, or their signed or unsigned varieties, or an enumeration type, may be used in an expression wherever an int or unsigned int may be used. If an int can represent all the values of the original type, the value is converted to an int; otherwise it is converted to an unsigned int.)
Then if either operand has type unsigned long int, the other operand is converted to unsigned long int. Otherwise, if one operand has type long int and the other has type unsigned int, if a long int can represent all values of an unsigned int the operand of type unsigned int is converted to long int; if a long int cannot represent all the values of an unsigned int, both operands are converted to unsigned long int. Otherwise, if either operand has type long int, the other operand is converted to long int. Otherwise, if either operand has type unsigned int, the other operand is converted to unsigned int. Otherwise, both operands have type int.
The sentence in bold explains your second case, where long int has the same width as unsigned int thus cannot hold all values of unsigned int.
(The above description lacks the type unsigned long long int and long long it, but the rules are basically the same.)
As all who answered above are correct, Just to add more clarity and my understanding writing here to get more clarity.
-->if one operand has type long int and the other has type unsigned int,
-->if a long int can represent all values of an unsigned int the operand of type unsigned int is converted to long int;
-->if a long int cannot represent all the values of an unsigned int, both operands are converted to unsigned long int.
So from above one operand has type long int i.e -1L and the other has type unsigned int i.e 1U
suppose sizeof -1L is --->8byte and sizeof 1U is 4 byte
0X0000-0XFFFFF values can be represented using in long int whose sizeof is 8 byte
so in this case long int can represent all values of an unsigned int i.e using 8byte ---> it can represent all the values unsigned int 1U.
so----> here operand of type unsigned int is converted to long int ---> -1L > 1U --> is false
coming 2nd case
if a long int cannot represent all the values of an unsigned int
i.e sizeof -1L -->4byte and sizeof 1U -->4byte
here long int cannot represent all the values i.e using 4 bytes--> it cannot represent all the values of unsigned int 1U. so both operands are converted to unsigned long int
-1L appears to large value since its unsigned now when compared to 1U.
i.e---->0xFFFFFFFF > 0x1 ---> its true
I am trying to compare an unsigned int with a signed char like this:
int main(){
unsigned int x = 9;
signed char y = -1;
x < y ? printf("s") : printf("g");
return 0;
}
I was expecting the o/p to be "g". Instead, its "s". What kind of conversion is done here?
Section 6.3.1.8, Usual arithmetic conversions, of C99 details implicit integer conversions.
If both operands have the same type, then no further conversion is needed.
That doesn't count since they're different types.
Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank is converted to the type of the operand with greater rank.
That doesn't count since one is signed, the other unsigned.
Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.
Bingo. x has a higher rank than y so y is promoted to unsigned int. That means that it morphs from -1 into UINT_MAX, substantially larger than 9.
The rest of the rules don't apply since we have found our match but I'll include them for completeness:
Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, then the operand with unsigned integer type is converted to the type of the operand with signed integer type.
Otherwise, both operands are converted to the unsigned integer type corresponding to the type of the operand with signed integer type.
The ranks relevant to this question are shown below. All ranks are detailed in C99, section 6.3.1.1, Boolean, character, and integers so you can refer to that for further details.
The rank of long long int shall be greater than the rank of long int, which
shall be greater than the rank of int, which shall be greater than the rank of short int, which shall be greater than the rank of signed char.
The rank of char shall equal the rank of signed char and unsigned char.
My guess is y is promoted to unsigned int which becomes a big value (due to wrapping). Hence the condition is satisfied.
Ran the following code:
int main(){
unsigned int x = 9;
signed char y = -1;
printf("%u\n", (unsigned int)y);
x < (unsigned int)y ? printf("s") : printf("g");
return 0;
}
The output is:
4294967295
s
After a casting, y takes a very large value. That is why output is s.
The char is promoted to unsigned int, with a value of MAX_UINT, which is greater than 9.