0

I Google'd so hard before I wrote this question. I am an "ok" C & C++ programmer, but not expert level. Everything that I read tells me that unsigned integer overflow is safe in C. However, signed integer overflow is "undefined behaviour" (UB). Oh, dreaded UB!

Related: Why is unsigned integer overflow defined behavior but signed integer overflow isn't?

Win32 API:

LONG InterlockedIncrement(
  [in, out] LONG volatile *Addend
)

Ref: https://learn.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-interlockedincrement

To be clear, LONG is defined as signed 32-bit int.

By inspection (not defintion/docs), this Win32 API appears to support signed integer overflow.

Example code:

#include <stdio.h>
#include <windows.h>
#include <limits.h>

int main(int argc, char **argv)
{
    printf("INT_MAX: %d\n", INT_MAX);
    LONG zz = INT_MAX;
    // Be careful about: 1 + 2,147,483,647 -> -2,147,483,648
    const LONG zzz = InterlockedIncrement (&zz);
    printf("INT_MAX+1: %d and %d\n", ((LONG) 1) + INT_MAX, zzz);
    return 0;
}

When I compile on Cygwin with gcc, then run, I see:

INT_MAX: 2147483647
INT_MAX+1: -2147483648 and -2147483648

I am confused.

  1. Do I misunderstand the rules of signed integer overflow for C?
  2. Does Windows have special rules?
  3. Is this argument theoretical in 2022 for Real World systems -- everything is 2's complement now?
kevinarpe
  • 20,319
  • 26
  • 127
  • 154
  • Undefined behaviour is undefined behaviour. It can work this way using this compiler options but it can work differently if you use newer version or if you pass another compiler options. – 0___________ Nov 09 '22 at 17:23
  • I don't know about C (or this Windows function) but in C++ the UB for signed integer overflow wasn't made into defined behavior even though C++20 requires integers to be in two's complement. – Ted Lyngmo Nov 09 '22 at 17:25
  • What happens if your compile this as a 64-bit binary, and the compiler uses the "as-if" rule to just put `zz` into a 64-bit register and never bothers to check if it overflows what should be a 32-bit value? Because it doesn't have to. Prove that's **never** going to happen. – Andrew Henle Nov 09 '22 at 17:37
  • 3
    In general, there's no real "Windows rules" here, if you compile with MSVC for example and disassemble https://i.imgur.com/E2bjvQw.png, you'll see InterlockedIncrement is just compiled as a macro that ends up doing LOCK XADD, there's nothing special at C level, it's all CPU rules and raw register copy. – Simon Mourier Nov 09 '22 at 18:04
  • 2
    *"Does Windows have special rules?"* - Yes. Windows' ABI has *always* mandated two's-complement representation of signed integers. While signed integer overflow is undefined according to language rules, a compiler implementation is free to assign well-defined behavior. A "Windows compiler" is well advised to treat signed integer overflow as well defined. [What clock do MSG.time and GetMessageTime use?](https://devblogs.microsoft.com/oldnewthing/20140122-00/?p=2013) has more information on the topic, for a different context. – IInspectable Nov 09 '22 at 18:07
  • @IInspectable Assigning "well-defined rules" to signed integer overflow [is nowhere near as easy as it seems](https://web.archive.org/web/20210118132942/https://hownot2code.com/2016/06/29/undefined-behavior-is-closer-than-you-think/). Simply putting a 32-bit signed value into a 64-bit register is enough to break any assumptions over what will happen should that 32-bit value overflow. Undefined behavior is undefined behavior - it ***NEVER*** "works". – Andrew Henle Nov 09 '22 at 18:37
  • @AndrewHenle putting it to 64 nothing change. – RbMm Nov 09 '22 at 18:49
  • @RbMm That is SOOO wrong. In a 32-bit register, if a 32-bit signed integer value overflows it will likely wrap, UB or not. In a 64-bit register, you get a value outside the range of a 32-bit integer. Do you **really** think that's not a change? Code that relies on any form of undefined behavior is fundamentally ***broken***. – Andrew Henle Nov 09 '22 at 19:10
  • @AndrewHenle - can you show concrete example ? no any problems here. max int is `7FFFFFFF` which is after increment is `80000000` (in 32 or 64) even if take `-1` in 32 ( `FFFFFFFF`) after extend (signed or unsigned) to 64 - it be `FFFFFFFFFFFFFFFF` or `FFFFFFFF` and after `+1` will be `0` or `100000000` - last anyway converted to 0 when trancate to 32. i even not say that api will operate with 32 registers only. so where you view problem ? (all what i wrote related to litle endian) – RbMm Nov 09 '22 at 19:21
  • @RbMm What rules in the C standard are you basing your assumption that a signed integer value will wrap when it overflows? Read this example of UB resulting in a 32-bit value indexing a 5 GB array, supposedly outside the range of a "32-bit" value: https://web.archive.org/web/20210118132942/https://hownot2code.com/2016/06/29/undefined-behavior-is-closer-than-you-think/ **Note well** that the 32-bit value **didn't wrap** in that example and even exceeded 32 bits of value. Because undefined behavior is **UNDEFINED**. – Andrew Henle Nov 09 '22 at 20:10
  • Note also this quote: "This is some theoretical nonsense. Well, yes, formally the ‘int’ overflow leads to an undefined behavior. But it’s nothing more but some jabbering. In practice, we can always tell what we will get. If you add 1 to INT_MAX then we’ll have INT_MIN. ..." That is **exactly** what you are trying to claim, and that simple example proves you're wrong. Adding `1` to signed `INT_MAX` can't be guaranteed to return **any** particular value. – Andrew Henle Nov 09 '22 at 20:13
  • @AndrewHenle - example from your link unrelated to this example. here i simply say that using 64 bit registers nothing change. how minimum little endian specific – RbMm Nov 09 '22 at 20:32
  • @RbMm Repeating that "nothing changes" doesn't make it true. You are assuming a 32-bit signed integer value will wrap when it exceeds `INT_MAX`. That assumption is flat-out unfounded and wrong. Compilers are free to put a 32-bit signed integer into a 64-bit register and let it keep getting larger and larger, well beyond the limits of `INT_MAX` **and never wrapping**. That's just **one** thing that can happen when you make literally unfounded assumptions about undefined behavior. And the example I linked to demonstrates exactly that. – Andrew Henle Nov 09 '22 at 20:40
  • @AndrewHenle *You are assuming a 32-bit signed integer value will wrap when it exceeds INT_MAX* - no. i not write this. `INT_MAX` this is `7FFFFFFF` and `+1` will be `80000000` - no overlap in 64 or 32. overlap will be only in case `-1` +1 and in both case it anyway will be 0 result. all i write already. – RbMm Nov 09 '22 at 20:44
  • @RbMm *`INT_MAX` this is `7FFFFFFF` and +1 will be `80000000`* ***NO***. Absolutely wrong. You keep assuming you can add `1` to `INT_MAX` when doing `singed int` calculations and predict the value. **YOU CAN NOT**. That is undefined behavior. You are a perfect example of this quote from what I linked earlier: "An interesting thing – when I or somebody else says that this is an example of undefined behavior, people start grumbling. I don’t know why, but it feels like they assume that they know absolutely everything about C++, and how compilers work. ..." – Andrew Henle Nov 09 '22 at 20:56
  • (cont) "But in fact they aren’t really aware of it. If they knew, they would’t say something like this (group opinion): *This is some theoretical nonsense. Well, yes, formally the ‘int’ overflow leads to an undefined behavior. But it’s nothing more but some jabbering. In practice, we can always tell what we will get. If you add `1` to `INT_MAX` then we’ll have `INT_MIN`. ...*" That is exactly what you are claiming. But if you want to be too stubborn to learn, you'll keep writing bad C code. – Andrew Henle Nov 09 '22 at 20:57
  • @AndrewHenle - in your concrete example not UB on first place but logical error - code use `int index` for fill array with `size_t` size more than 32bit size. here no any assumptions which will be on overlow, etc. simply error in select `int index` instead `size_t index` – RbMm Nov 09 '22 at 21:12
  • @RbMm *in your concrete example not UB on first place* Seriously!?!?! That is **painfully** wrong. If there's no UB, how did that example code manage to fill an entire 5 GB array using what's **supposed to be** a 32-bit `signed int` value? And I'll go back to my earlier question: What section(s) of any version of the C standard support your assertion that you can add `1` to `INT_MAX` using signed `int` calculations and get any specific result? Or are you going to insist on not learning? – Andrew Henle Nov 09 '22 at 21:22
  • 1
    @AndrewHenle - code not make any assumption on overlow behaviour of `int index` . i say that here logical error on first place - use `int` instead `size_t` on index. and else `size_t i` used - compiler optimize this 2 indexes to 1 ( `size_t i` , `rcx`) . and result. here, i comment only on concrete example and in concrete implementation ( InterlockedIncrement ) - *putting it to 64 nothing change*. i nothing say about languages rules. but again - my comment from where we vegin *putting it to 64 nothing change* **here** – RbMm Nov 09 '22 at 21:29
  • 1
    @AndrewHenle what's interesting about the example at the link is that it's not even about overflow. but that the compiler combined 2 indices into 1. used one variable (register) for both. because the programmer himself did not take care of this (you could use one variable instead of 2) and did not think about the size of the variables (by the way, the compiler did not think either). – RbMm Nov 09 '22 at 21:56

0 Answers0