1. 26
  1. 6

    Interesting article, thanks for posting!

    very trivial to check if x * y would overflow though and you should just learn the extremely simple and obvious test:

    if (y && x > (T)-1/y)

    Kept staring at this for a while. For the rest of us dummies, this is because for integers:

    x*y > MAX <–> x > MAX/y.

    So the test is shorter form for (y > 0) && (x > UINTMAX/y).

    1. 3

      So the test is shorter form for (y > 0) && (x > UINTMAX/y).

      Well, yes, but it’s specifically short for y != 0 && ... to prevent division by 0.

    2. 4

      Some assorted observations regarding unsigned numbers:

      • Due to two’s-complement arithmetic, if a is an array, x and y are positive integers, and x<y, then a[x-y] will have the same result regardless of the signedness of x and y.

      • The above only holds if x and y are pointer-sized. If they are smaller than pointers (if, say, a pointer is 64 bits and you’re using 32-bit indices), the signed interpretation of the index will likely have a small magnitude and silently access out-of-bounds memory; while the unsigned interpretation will likely have a large magnitude that is far out of bounds and exposes the bug via a fault.

      • If you can afford bounds checking, you can afford overflow checking.

      • If an intermediate value would have underflowed and then overflowed in the unsigned representation, returning to a sane range, the same series of operations performed in the signed representation would result in the same (likely bogus) value. Meanwhile performing arithmetic on signed indices inhibits your ability to perform overflow checking in a consistent manner.

      • Regardless of whether you try to allocate an unreasonably large size (as from unsigned overflow) or a negative size, your allocator should be written blow up in a similar fashion. See rsize_t for inspiration.

      1. 2

        Regarding unsigned multiplication, there’s a false statement about Go:

        // But it also even has an explicit n * m form as well.
        make([]Object, n, m);

        That’s not a calloc-style n * m, that’s a length/capacity split. make([]Object, n, m) allocates memory to hold m items and sets the initial length to n.

        Really, make([]Object, n) is already n*m because it uses the size of Object and then n is a straight object count always.

        Also, Go is not based on LLVM, so limitations of LLVM do not apply; so the language claims are not proven factually incorrect.