9

The bcache source here contains the following line:

schedule_delayed_work(&dc->writeback_rate_update,
    dc->writeback_rate_update_seconds * HZ);

writeback_rate_update_seconds is defined as unsigned int, which appears to be 32 bit on x86_64, and I am not sure what type HZ has but I believe the value is 1000 and assume it is 32-bit or less.

If I set writeback_rate_update_seconds to 2147483647, what value actually gets passed to schedule_delayed_work? The second parameter of schedule_delayed_work appears to be a long, but that won't mean the operands are promoted to long prior to the multiplication overflow, will it?

  • No, the value isn't promoted until after the multiplication is done, which uses the types of the original parameters. – Barmar Jun 01 '17 at 09:44
  • Since it's `unsigned`, overflow wraps around using modular arithmetic. – Barmar Jun 01 '17 at 09:45
  • @Barmar excuse my ignorance, but what does "wraps around using modular arithmetic" mean exactly? Does it mean the multiplication is done as if they were `long` but just the least significant 32 bits are returned? –  Jun 01 '17 at 09:46
  • also how do I find the type of HZ, if that is significant? –  Jun 01 '17 at 09:46
  • Look up modular arithmetic in Wikipedia. `HZ` is presumably a macro, just look at the macro definition and you should be able to tell what type it is. – Barmar Jun 01 '17 at 09:48
  • "[retaining only the least significant bits of the result](https://en.wikipedia.org/wiki/Integer_overflow)" –  Jun 01 '17 at 09:51
  • Yes, it's basically the same as that. – Barmar Jun 01 '17 at 09:53
  • @Barmar I guess HZ is defined in `linux/sched/clock.h`, but I can't figure out what absolute path that relates to? –  Jun 01 '17 at 09:56
  • `/usr/include/linux/sched/clock/h` – Barmar Jun 01 '17 at 10:00

2 Answers2

2

Given:

#include <stdio.h>
#include <stdlib.h>

int schedule_delayed_work( unsigned long param )
{
    printf("value: %lu\n", param);
    return 0;
}

int main(int argc, char **argv)
{
    unsigned int writeback_rate_update_seconds;
    unsigned int HZ;
    writeback_rate_update_seconds = 2147483647;
    HZ = 1000;
    schedule_delayed_work( writeback_rate_update_seconds * HZ );
    return 0;
}

You will get 4294966296 passed to the function.

If you change the function call to cast:

schedule_delayed_work( (unsigned long) writeback_rate_update_seconds * HZ );

... you will get 2147483647000 passed to the function.

I've not looked in the C standard to see what the standard behaviour is, but this was tested with:

Apple LLVM version 8.1.0 (clang-802.0.38)
Target: x86_64-apple-darwin16.7.0
Phil
  • 2,392
  • 18
  • 21
  • Mind you, the behavior you see is for systems where `unsigned long` is 64 bits. With MSVC, `unsigned long` is 32 bits. This is allowed; `unsigned long long` is the only type which is guaranteed 64 bits. – MSalters Jun 01 '17 at 10:12
  • thanks, and I get the same result if I tweak HZ to be signed (I think it's a macro and so effectively a literal `1000`, which is signed, but [gets typecast to unsigned](https://stackoverflow.com/questions/16966636/signed-unsigned-integer-multiplication) in the multiplication) –  Jun 01 '17 at 10:14
1

If both operands fit in unsigned int (if HZ is constant 1000, it is of type int and fits in unsigned int) they're promoted to unsigned int. With unsigned integers the overflow is well-defined; the resulting value is the value of calculation modulo (UINT_MAX plus one). That is, the maximum result is UINT_MAX; UINT_MAX + 1 will result in 0, UINT_MAX + 2 will result in 1 and so on.

The type of the receiver (here, the type of the argument that receives the result) doesn't matter at all. To avoid wraparounds, cast one of the arguments as a wider integer type (for example unsigned long is 64 bits in 64-bit Linux; or even better, use a fixed-width type such as uint64_t).