6

I've always taken this for granted before, but suppose I have:

uint8_t a;
uint8_t b;

if ((a - b) < 0) {
  ...
}

What is the data type of the expression (a - b)? Mr. Godbolt tells me that it's a signed value; is that guaranteed by the any of the C specifications?

AMENDMENT:

I now understand that type promotion will guarantee that (a-b) is an int when a and b are smaller than ints. What if instead a and b are unsigned ints?

unsigned int a;
unsigned int b;

if ((a - b) < 0) {
  ...
}
dbush
  • 205,898
  • 23
  • 218
  • 273
fearless_fool
  • 33,645
  • 23
  • 135
  • 217
  • 1
    Does this answer your question? [Implicit type promotion rules](https://stackoverflow.com/questions/46073295/implicit-type-promotion-rules) – Bob__ Jan 09 '23 at 23:07
  • @Bob__: thanks for the reference, but -- as you see in my amended question -- the expression won't be promoted if a and b are already full integers. – fearless_fool Jan 09 '23 at 23:18
  • `uint8_t` is guaranteed to be narrower than `int`. `unsigned` is guaranteed to be the same width as `int`. `uint32_t` is *probably* the same width as `int`, but it could be narrower (if `int` is, say, 64 bits) or wider (if `int` is, say, 16 bits). Promotion rules are defined in terms of `int` and `unsigned int`; the width relationship of `[u]intN__t` vs. `int` is implementation defined. – Keith Thompson Jan 10 '23 at 18:21
  • @KeithThompson Thanks for the clarification. I really should have initially asked my question using uint32_t rather than uint8_t, since that's what my application is really using. – fearless_fool Jan 10 '23 at 21:43

1 Answers1

8

This expression will have type int, which is signed.

Because both operands have a type smaller than int, both will be promoted to type int, and the result will have type int.

Integer promotions are defined 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.

So this means the expression ((a - b) < 0) could potentially evaluate as true.

Had the variables been defined like this:

unsigned int a;
unsigned int b;

Then there would be no promotion and a - b would have unsigned type, meaning ((a - b) < 0) would always be false.

dbush
  • 205,898
  • 23
  • 218
  • 273
  • Ah! I'm glad you mentioned type promotion. My ACTUAL use case involves uint32_t values, which -- as you point out -- won't be promoted on a 32 bit system. P.S.: I just amended the original question for your latter point. You have saved me some astonishment -- have a check mark! – fearless_fool Jan 09 '23 at 23:14
  • I note also that even when a and b are `unsigned int` values, `(int)(a-b)` will be treated as a signed value, so `if ((int)(a-b) < 0) ...` will be true. – fearless_fool Jan 09 '23 at 23:23
  • 1
    @fearless_fool Yes, although the result of the conversion could be implementation defined. – dbush Jan 09 '23 at 23:30
  • @fearless_fool Casting an `unsigned int` back to `int` has some implementation-defined (and perhaps undefined) aspects. While it will probably always work for you, why not write `if(a < b)`, which is guaranteed to work no matter what? – Steve Summit Jan 09 '23 at 23:31
  • @SteveSummit: `if (a < b) ...` is exactly the wrong thing if either a or b is subject to rolling over (which in my case, it is...). But I take your point about not casting -- I can assign the difference to a signed int so the compiler with `-Wall` can warn me if needed... – fearless_fool Jan 09 '23 at 23:34
  • @fearless_fool If it's unsigned wraparound you're looking for, you probably want `a - b > 0x80000000`, assuming 32 bit values. – dbush Jan 09 '23 at 23:36
  • @fearless_fool See my question on that topic: https://stackoverflow.com/questions/31967370/is-detecting-unsigned-wraparound-via-cast-to-signed-undefined-behavior – dbush Jan 09 '23 at 23:45