Paul Eggert wrote: > > uintN_t arithmetic is overflow-free, just like 'unsigned int' arithmetic. > > Not exactly. For example, this: > > uint16_t a = 46341, b = a * a; > > has undefined behavior on typical platforms with 32-bit int, because > 46341*46341 exceeds 2**31 - 1. Although many programmers would consider > this an example of uint16_t arithmetic, something else is going on. > > If uintN_t fits in int, a program cannot do uintN_t arithmetic directly, > as values are promoted to int before they become arithmetic operands. A > program can do only int arithmetic on these values, and the int > arithmetic can overflow. > ... > Unsigned int, unsigned long int, unsigned long long int, and uintmax_t > are the only types where fully portable code can assume wraparound overflow.
Oh, indeed, you're right. And while the gcc and clang UBSAN catches this (compile with -fsanitize=undefined,signed-integer-overflow,shift,integer-divide-by-zero), 'gcc -ftrapv' doesn't. [1] Fortunately we don't use multiplication on uint16_t frequently. (libunistring uses uint16_t a lot, but never multiplies two uint16_t.) Also good to know: A gnulib testdir of all modules, compiled with UBSAN, passes all tests. > > instead > > of using the 'u64' module in order to avoid uint64_t, it would be much > > simpler to make <stdint.h> define the missing int64_t and uint64_t types > > We could do something along those lines. I'd prefer it, though, if we > did that only on platforms where we tested that 'unsigned long long int' > is actually 64 bits, since the relevant code is already cautious in this > area due to concerns such as the above. Although platforms are currently > leery of integers wider than 64 bits, wider integers are commonly > supported under the covers now, and I expect it won't be forever before > they emerge from the shadows. Good point. Done as below. It will make our code more future-proof. [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54848#c2 2024-05-20 Bruno Haible <br...@clisp.org> stdint: Verify the width of 'long long' before using it as int64_t. Suggested by Paul Eggert in <https://lists.gnu.org/archive/html/bug-gnulib/2024-05/msg00315.html>. * lib/stdint.in.h (gl_int64_t): Verify that the number of bits of 'long long' is 64 before using it. (gl_uint64_t): Verify that the number of bits of 'unsigned long long' is 64 before using it. diff --git a/lib/stdint.in.h b/lib/stdint.in.h index fea7483b9c..cd3fbdd965 100644 --- a/lib/stdint.in.h +++ b/lib/stdint.in.h @@ -80,7 +80,7 @@ #define _@GUARD_PREFIX@_STDINT_H /* Get SCHAR_MIN, SCHAR_MAX, UCHAR_MAX, INT_MIN, INT_MAX, - LONG_MIN, LONG_MAX, ULONG_MAX, _GL_INTEGER_WIDTH. */ + LONG_MIN, LONG_MAX, ULONG_MAX, CHAR_BIT, _GL_INTEGER_WIDTH. */ #include <limits.h> /* Override WINT_MIN and WINT_MAX if gnulib's <wchar.h> or <wctype.h> overrides @@ -189,6 +189,10 @@ typedef __int64 gl_int64_t; # define int64_t gl_int64_t # define GL_INT64_T # else +/* Verify that 'long long' has exactly 64 bits. */ +typedef _gl_verify_int64_bits[ + _STDINT_MAX (1, sizeof (long long) * CHAR_BIT, 0ll) >> 31 >> 31 == 1 + ? 1 : -1]; # undef int64_t typedef long long int gl_int64_t; # define int64_t gl_int64_t @@ -210,6 +214,11 @@ typedef unsigned __int64 gl_uint64_t; # define uint64_t gl_uint64_t # define GL_UINT64_T # else +/* Verify that 'unsigned long long' has exactly 64 bits. */ +typedef _gl_verify_uint64_bits[ + _STDINT_MAX (0, sizeof (unsigned long long) * CHAR_BIT, 0ull) + >> 31 >> 31 >> 1 == 1 + ? 1 : -1]; # undef uint64_t typedef unsigned long long int gl_uint64_t; # define uint64_t gl_uint64_t