【问题标题】:"integer constant overflow" warning in constexprconstexpr 中的“整数常量溢出”警告
【发布时间】:2016-10-06 03:24:17
【问题描述】:

我正在尝试找到一个与 constexpr 兼容的哈希函数,用于在编译时对字符串进行哈希处理。字符串的数量非常少(

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 函数,但它们都有相同的问题。

【问题讨论】:

  • 您是否尝试将类型更改为unsigned long long
  • rextesterrise4fun 没有收到此警告,分别使用 /W2/W4
  • 考虑使用uint32_t 代替intunsigned int 可能是 16 位)。 char 可以signed unsigned,如果signed可以是1的补码。
  • 我可以将其更改为unsigned long long,但由于它对字符串中的每个字符进行乘法运算,因此只有在字符串足够短时才会起作用。如果我真的想要一个 64 位哈希(除非我要使用一些 128 位整数),它也行不通。我希望字符串的编译时散列是 constexpr 的一个非常常见的用例,但我看不出如果溢出触发警告它会如何工作,因为这么多散列算法依赖于溢出。
  • 试试(h ^ (static_cast<unsigned int>(*s))

标签: c++ c++11 visual-studio-2015 constexpr


【解决方案1】:

如果您不介意溢出,则只需使警告静音即可。无符号整数算术保证为模 2n 算术,其中 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 位哈希函数,似乎唯一的办法是实现 @ 987654323@ 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 警告 push + disable + pop 并没有任何作用。看起来我必须在所有以 constexpr 方式调用函数的地方添加它,这远非理想。我不想全局禁用警告(或包含标题后的任何地方)。
  • @user408952:好的,让我看看
  • @user408952:我更新了答案,也为 64 位散列器取消了该警告。我认为你应该使用哪个。 ;-)
  • 老实说,考虑到某些的警告(例如,C4265 , "probably-polymorphic class has non-virtual dtor") 比 "warning: unsigned wraparound" 更有用。
【解决方案2】:

您可以显式地转换为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)));
}

这个stops the warning in my live demo(第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 强制编译器在编译时评估表达式,否则它在编译时上下文之外是可选的。

    这对于 64 位版本来说比实现自己的 constexpr 64 位乘法要容易一些。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-11-07
      • 2019-10-11
      • 1970-01-01
      • 1970-01-01
      • 2021-04-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多