2

In a Linux kernel module there are definitions for a number of things that are in stdint.h/limits.h for user mode C but are conspicuously missing from the standard kernel headers. E.g. the following:

#define UINT16_MIN (uint16_t)0
#define UINT16_MAX (~UINT16_MIN)
#define INT16_MAX (int16_t)(UINT16_MAX >> 1)
#define INT16_MIN (-INT16_MAX - (int16_t)1)

However, when built with kbuild (i386 target) INT16_MAX is coming out to (int16_t)-1 and INT16_MIN to (int16_t)0. This implies that UINT16_MAX was converted to int16_t by the ~ operator before being right-shifted. If the definition of UINT16_MAX is changed to

#define UINT16_MAX (uint16_t)(~UINT16_MIN)

the result is correct. Is this some deep, dark, and brown-recluse-infested corner of the C language, or just a straight up compiler bug?

EDIT: As explained in comments I thought promotion only occurred when mixing argument types or assigning to a type different than the value, and did not know it could occur with unary operations. This question is a duplicate.

Justin Olbrantz
  • 647
  • 3
  • 11
  • those includes seem broken... – Jean-François Fabre Sep 18 '17 at 18:02
  • 1
    negating seems to promote the result as `unsigned int` (32 bits), so shifting doesn't remove the last set bit. – Jean-François Fabre Sep 18 '17 at 18:03
  • Promoting to unsigned int would result in 0xFFFFUL, yes? Width extension before sign conversion? – Justin Olbrantz Sep 18 '17 at 18:10
  • 1
    Where are these definitions for `UINT16_MAX` coming from? Your own module project? Why not just do something like `#define UINT16_MAX (65535U)` instead of messing with bit manipulation? – Michael Burr Sep 18 '17 at 18:19
  • The main reason not to do that is that basic data types and thus suffixes are not guaranteed width (though I suppose I could do something like (int16_t)65535ULL for everything). I'm porting some user mode C code of mine to the kernel for a class project. The original C code was designed to be highly portable, and ran on everything from embedded DSPs (e.g. 16-bit char/short/int, 32-bit long, 40-bit long long) to x64 (32-bit int, 64-bit long long, 32 or 64-bit long depending on what compiler is used). – Justin Olbrantz Sep 18 '17 at 18:26
  • In either case, discussion is really getting off topic. The question is whether this is correct behavior or a compiler bug. If the latter, it needs to be fixed. But before that it needs to be confirmed as a bug. – Justin Olbrantz Sep 18 '17 at 18:41
  • As stated in the standard (and in the dupe), "If an int can represent all values of the original type, the value is converted to an int". When building with `#define UINT16_MAX (~UINT16_MIN)` the type of the `UINT16_MIN` operand to the `~` operator can be always be represented by an `int`, so that operand to the `~` operator is promoted to an `int`. So there is not compiler bug - you need to do something to ensure the result is unsigned. – Michael Burr Sep 18 '17 at 19:05
  • So you're saying even unary operations cause type promotion (and the type is promoted prior to applying the unary operation)? – Justin Olbrantz Sep 18 '17 at 19:18
  • The operand of the unary arithmetic operators `+`, `-`, and `~` is promoted. The result has the promoted type. – Michael Burr Sep 18 '17 at 20:58
  • I see... I was not aware it occurred in the absence of mixed operands. Is this C version specific? In this project I should have run into this dozens of times in the last year, but only after switching to kbuild (previously it was built using MSVC++; also TI GCC though that's understandable as int16_t == int) did I first encounter it. – Justin Olbrantz Sep 19 '17 at 20:06

0 Answers0