Home > Blockchain >  Remove undefined behavior from overflow of signed integers in constant expressions?
Remove undefined behavior from overflow of signed integers in constant expressions?

Time:09-05

EDIT In the actual example, it appears possible that negative overflow can happen, I've also added an example to demonstrate the error there

I'm using C 20 and trying to convert a library which relies on signed integer overflow in Java and C# into C code. I'm also trying to generate the tables it uses at compile time, and allow those to be available at compile time.

In my code I get errors in reference to code that looks like this (Minimal example to reproduce the error, the solution to this will solve my problem as well):

#include <iostream> 

constexpr auto foo(){
    std::int64_t a = 2; 
    std::int64_t very_large_constant = 0x598CD327003817B5L; 
    std::int64_t x = a * very_large_constant; 
    return x; 
}
 
int main(){
    std::cout << foo() << std::endl; 
    return 0; 
}

https://godbolt.org/z/TvM45vd8d

Negative overflow version

#include <iostream> 

constexpr auto foo(){
    std::int64_t a = -2; 
    std::int64_t very_large_constant = 0x598CD327003817B5L; 
    std::int64_t x = a * very_large_constant; 
    return x; 
}
 
int main(){
    std::cout << foo() << std::endl; 
    return 0; 
}

https://godbolt.org/z/7zoE9r18E

I get 12905529061151879018 is out side of range representable by long long and -12905529061151879018 respectively.

I understand that undefined behavior here is not allowed, I also recognize that GCC and MSVC do not error here, and you can put a flag to make clang compile this anyway. But what am I supposed to do to actually solve this issue with out switching compilers or applying the flag to ignore invalid constexpr?

Is there some way I can define the behavior I expect and want to happen here?

CodePudding user response:

You cannot do this with signed integers. However, there are some things you can rely on in C 20:

  1. Unsigned integer overflow is well-defined.

  2. Signed integers are required to be represented as 2's complement.

  3. Conversions between corresponding sized and unsigned integers preserve the bitpattern.

So you can do all of your overflow-based math using explicitly unsigned types and literals, then cast them to signed values when you need to. This conversion is required to leave the bits unchanged.

CodePudding user response:

Signed integers have two's complement layout in any implementation that you could name. It's also guaranteed to use two' s complement layout since C 20.

This means that you can perform your math on unsigned integers and get well-defined overflow behavior that matches what you want your signed integers to do.

#include <iostream> 
#include <bit>

constexpr auto foo(){
    std::uint64_t a = 2; 
    std::uint64_t very_large_constant = 0x598CD327003817B5L; 
    std::uint64_t x = a * very_large_constant; 
    return static_cast<std::int64_t>(x); 
}
  • Related