Search code examples
cinteger-overflow

What is the correct integer overflow builtin to use with the size_t type


For example:

size_t x;
...
__builtin_uaddll_overflow(x,1,&x);

Would the above code correctly guard against integer overflow regardless of compiler implementation?

What I know so far:

  • This reference states that size_t is an unsigned type.
  • According to this discussion, typedef unsigned long size_t; may be used to define size_t.

Is there a function listed at this reference that will always be correct? Or will it necessarily depend on the specific implementation? If so, how could I programatically choose the correct function?


Solution

  • size_t x;
    ...
    __builtin_uaddll_overflow(x,1,&x);
    

    Would the above code correctly guard against integer overflow regardless of compiler implementation?

    No. __builtin_uaddll_overflow() is not a specified C operator nor a C standard library function. It restricts code to select compilers. The functionality of __builtin_uaddll_overflow() is not specified by C.

    Instead, simply compare against SIZE_MAX for a portable implementation. It is portable regardless if size_t is the same as unsigned, unsigned long or some other unsigned type, even wider or narrower than unsigned.

    size_t x;
    size_t y;
    if (SIZE_MAX - x < y) Overflow();
    else size_t sum = x + y;
    

    This works for the various unsigned types too - even narrow ones. Use the same some_unsigned_type throughout.

    some_unsigned_type x;
    some_unsigned_type y;
    if (some_unsigned_type_MAX - x < y) Overflow();
    else some_unsigned_type sum = x + y;
    

    When working with unsigned types at least as wide as unsigned, code could use the following.

    some_at_least_unsigned_type x;
    some_at_least_unsigned_type y;
    some_at_least_unsigned_type sum = x + y;  // overflow behavior well defined
    if (sum < x) {
      Overflow();
    }
    else {
      // continue with non-overflowed sum
    } 
    

    In the case of size_t, size_t is very commonly as wide or wider than unsigned, although not specified to be so.


    The above usually works with unsigned types narrower then unsigned. Yet the x + y is done with int math then and unicorn platforms could then overflow int math. 1u*x + y or 0u + x + y forces the math to always be done as unsigned, regardless if some_unsigned_type is narrower/wider than unsigned. A good compiler will emit optimize code that does not perform an actual 1u* multiplication.

    some_unsigned_type x;
    some_unsigned_type y;
    some_unsigned_type sum = 1u*x + y;  // overflow behavior well defined
    if (sum < x) {
      Overflow();
    }
    else {
      // continue with non-overflowed sum
    } 
    

    Or in OP's + 1 example, to insure unsigned math addition:

    size_t x;
    size_t sum = x + 1u;  // overflow behavior well defined
    if (sum == 0) {
      Overflow();
    }
    else {
      // continue with non-overflowed sum
    }