constexpr中的“整数常量溢出”警告

6

我正在尝试寻找一个适用于编译时字符串哈希的constexpr兼容哈希函数。字符串数量非常少(<10),而且我有一个单独的冲突检查,所以算法可以远离完美。我在互联网上找到了以下版本的FNV1A:

static constexpr unsigned int Fnv1aBasis = 0x811C9DC5;
static constexpr unsigned int Fnv1aPrime = 0x01000193;

constexpr unsigned int hashFnv1a(const char *s, unsigned int h = Fnv1aBasis)
{
    return !*s ? h : hashFnv1a(s + 1, (h ^ *s) * Fnv1aPrime);
}

但是,当我在MSVS 2015中编译时,会收到以下警告:

warning C4307: '*': integral constant overflow

由于函数中只有一个乘法运算,我认为警告来自于 (h ^ *s) * Fnv1aPrime。这是有道理的,因为将 0x811C9DC5 (Fnv1aBasis) 与任何东西相乘都会使32位整数溢出。

有没有什么方法可以解决这个问题?我尝试了几个我找到的用于哈希字符串的constexpr函数,但它们都有相同的问题。


1
你试过将类型更改为unsigned long long吗? - Rakete1111
我在使用 /W2/W4 分别在 rextesterrise4fun 上编译时,并没有收到这个警告。 - uh oh somebody needs a pupper
考虑使用 uint32_t 替代 intunsigned int 可能是 16 位)。此外,char 可能signedunsigned,如果是 signed,则可能是 1 的补码。 - Bathsheba
尝试使用(h ^ (static_cast<unsigned int>(*s)) - bolov
@bolov:根据提升规则,char 会被隐式转换为 unsigned - Cheers and hth. - Alf
显示剩余2条评论
3个回答

9
如果您不介意溢出,那么只需将警告静音即可。无符号整数算术保证是模2的n算术,其中n是值表示中的位数,因此无论如何都是明确定义的。该警告是一种愚蠢的警告;它警告您正在使用无符号整数的主要特性。
我发现对于该函数的每个使用,即使在函数中使用本地#pragma warning( disable: 4307 ),该警告仍然会出现。
重写代码可以消除32位哈希函数的警告:
constexpr auto hashFnv1a( char const* s, unsigned h = Fnv1aBasis )
    -> unsigned
{
    return !*s ? h : hashFnv1a(s + 1, static_cast<unsigned>( 1ULL*(h ^ *s) * Fnv1aPrime ));
}

即使通过广泛的谷歌搜索,也找不到任何方法来禁用有符号值的溢出警告,而保留无符号值的警告。因此,为了处理64位哈希函数,似乎唯一的途径是实现一个constexpr 64位无符号乘法函数。由于它是constexpr,所以它的效率并不重要。因此:

#include <stdint.h>

namespace b32 {
    static constexpr uint32_t Fnv1aBasis = 0x811C9DC5u;
    static constexpr uint32_t Fnv1aPrime = 0x01000193u;

    constexpr auto hashFnv1a( char const* s, uint32_t h = Fnv1aBasis )
        -> uint32_t
    { return !*s ? h : hashFnv1a(s + 1, static_cast<uint32_t>( 1ULL*(h ^ *s)*Fnv1aPrime )); }
}  // namespace b32

namespace b64 {
    static constexpr uint64_t Fnv1aBasis = 0xCBF29CE484222325uLL;
    static constexpr uint64_t Fnv1aPrime = 0x100000001B3uLL;

    constexpr auto lo( uint64_t x )
        -> uint64_t
    { return x & uint32_t( -1 ); }

    constexpr auto hi( uint64_t x )
        -> uint64_t
    { return x >> 32; }

    constexpr auto mulu64( uint64_t a, uint64_t b )
        -> uint64_t
    {
        return 0
            + (lo( a )*lo( b ) & uint32_t(-1))
            +   (
                    (
                        (
                            (
                                (
                                    hi( lo( a )*lo( b ) ) +
                                    lo( a )*hi( b )
                                )
                                & uint32_t(-1)
                            )
                            + hi( a )*lo( b )
                        )
                        & uint32_t(-1)
                    )
                    << 32
                );
    }

    constexpr auto hashFnv1a( char const* s, uint64_t h = Fnv1aBasis )
        -> uint64_t
    { return !*s ? h : hashFnv1a( s + 1, mulu64( h ^ *s, Fnv1aPrime ) ); }
}  // namepace b64

#include <assert.h>
#include <iostream>
using namespace std;
auto main()
    -> int
{
    constexpr auto x = b64::mulu64( b64::Fnv1aBasis, b64::Fnv1aPrime );

    #ifdef _MSC_VER
    #   pragma warning( push )
    #   pragma warning( disable: 4307 )
        constexpr auto y = b64::Fnv1aBasis*b64::Fnv1aPrime;
    #   pragma warning( pop )
    #else
        constexpr auto y = b64::Fnv1aBasis*b64::Fnv1aPrime;
    #endif

    cout << x << endl;
    cout << y << endl;
    assert( x == y );

    static constexpr const char* const s = "blah!";
    constexpr unsigned xs = b32::hashFnv1a( s );
    constexpr uint64_t ys = b64::hashFnv1a( s );

    int a[1 + xs%2];  (void) a;
    int b[1 + ys%2];  (void) b;
}

该函数在头文件中,将#pragma warning push + disable + pop放置在函数周围似乎没有任何作用。看起来我必须在所有以constexpr方式调用该函数的地方添加它,这远非理想。我不想全局禁用警告(或在包含该头文件后的任何位置)。 - user408952
@user408952:好的,让我看看。 - Cheers and hth. - Alf
@user408952:我更新了答案,同时解决了64位哈希器的警告。我认为你应该使用这个哈希器。;-) - Cheers and hth. - Alf
我真的觉得非常奇怪,为什么这个警告不是默认关闭的,考虑到其中一些(例如C4265,“可能是多态类具有非虚拟析构函数”)比“警告:无符号包装”的警告更有用。 - Justin Time - Reinstate Monica

4
您可以使用以下方法将数据显式转换为 unsigned long long 类型,然后再转回原类型:
constexpr unsigned int hashFnv1b(const char *s, unsigned int h = Fnv1aBasis)
{
    return !*s
           ? h
           : hashFnv1b(
               s + 1,
               static_cast<unsigned int>(
                 (h ^ *s) * static_cast<unsigned long long>(Fnv1aPrime)));
}

这个可以消除我的演示中的警告(第20行触发它,第21行不会)。


3
另一种方法是将其包装在宏中,并使用__pragma关闭警告:
#include <type_traits>
#if _MSC_VER
#define FNV_HASH( str ) \
  __pragma( warning( push ) ) \
  __pragma( warning( disable: 4307 ) ) \
  std::integral_constant<uint64_t, hashFnv1a( str )>::value \
  __pragma( warning( pop ) )
#else
#define FNV_HASH( str ) std::integral_constant<uint64_t, hashFnv1a( str )>::value
#endif

std::integral_constant强制编译器在编译时评估表达式,否则在非编译时上下文中是可选的。

与实现自己的constexpr 64位乘法相比,在64位版本中更容易些。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接