2016-04-28 73 views
6

让我们考虑一下将无符号短整数值(或任何其他无符号整数类型)的右N位置零的函数(它的可能实现之一)。可能的实现可能看起来像以下:左移位并丢弃位

template<unsigned int shift> 
unsigned short zero_right(unsigned short arg) { 
    using type = unsigned short; 

    constexpr type mask = ~(type(0)); 
    constexpr type right_zeros = mask << shift; // <-- error here 
    return arg & right_zeros; 
} 

int check() { 
    return zero_right<4>(16); 
} 

有了这个代码,我已经获得所有的编译器抱怨,以这种或那种方式,对可能的溢出。铛是最明确的一个,有以下明确的信息:

error: implicit conversion from 'int' to 'const type' (aka 'const unsigned short') changes value from 1048560 to 65520 [-Werror,-Wconstant-conversion]

此代码看起来明确界定,并明确为一天给我,但是当3个编译器抱怨,我变得很紧张。我在这里错过了什么吗?真的有机会发生腥吗?

P.S.虽然zeriong左侧X位的替代实现可能是受欢迎和有趣的,但这个问题的主要焦点是发布代码的有效性。

+0

@TavianBarnes,它们不能被晋升为无符号的参数签署整数。 – SergeyA

+0

不是你在问什么,但是你可能想要知道的东西(并且不要提防)是,如果你左移一个无符号整数'n'位,其中'n'>> =你转移的类型,那就是未定义的行为。 –

+0

@SergeyA但这仍然是问题:'<<'的结果是int,不短。在分配前将'mask << shift'的结果转换回'type'使错误消失。 –

回答

2

的消息似乎很平淡:

error: implicit conversion from 'int' to 'const type' (aka 'const unsigned short') changes value from 1048560 to 65520 [-Werror,-Wconstant-conversion]

mask << shift具有价值1048560(从65535 << 4产生),并且将其分配给unsigned short,其被定义为调整值mod 65536,给人65520

这最后一个转换是明确的。错误消息是因为您通过了编译器标记-Werror,-Wconstant-conversion,要求在这种情况下收到错误消息。如果你不想要这个错误,那么不要传递这些标志。


虽然此特定用法被明确定义的,有可能是某些输入(即,被shift16或更大,如果使用的是32位int系统)未定义的行为。所以你应该修复这个功能。

要解决此功能,您需要在unsigned short的情况下更加小心,因为无符号短整数提升整数提升令人讨厌的规则。

这里有一个解决方案与其他产品有点不同..完全避免换挡的问题,适用于任何平移尺寸:

template<unsigned int shift, typename T> 
constexpr T zero_right(T arg) 
{ 
    T mask = -1; 
    for (int s = shift; s--;) mask *= 2u; 
    return mask & arg; 
} 

// Demo 
auto f() { return zero_right<15>((unsigned short)65535); } // mov eax, 32768 
+0

这很有趣。我意识到我不能转移更多的比特,然后有类型。在我的真实应用中,这不会发生。除此之外,你说的是代码是定义明确的,并且会始终按照我期望的那样去做? – SergeyA

+1

你现在拥有它的方式依赖于2的补码,并且如果你有32位整数,那么无符号短小就是实现定义的15位移位 –

+0

@SergeyA,你可以尝试移位更多的位而不是宽度,但是Intel明确地说过它们掩盖* shift *操作数的高位。例如,对于uint32_t,移位是%32,所以'int32_t << 40' ==='int32_t << 8'。 **但**,要小心编译器 - 如果gcc在优化的编译时间看到shift> 32,它只会将结果归零! – BitWhistler

3

从C++ 11标准:整数提升施加到后mask

5.8 Shift operators [expr.shift]

1 ...

The operands shall be of integral or unscoped enumeration type and integral promotions are performed. The type of the result is that of the promoted left operand.

表达

mask << shift; 

评价。因此,如果sizeof(unsigned short)是2,则其评估为1048560,其解释来自铛的消息。

避免溢出问题的一种方法是在执行左移之前先右移,然后将其移至其自己的函数。

template <typename T, unsigned int shift> 
constexpr T right_zero_bits() 
{ 
    // ~(T(0)) performs integral promotion, if needed 
    // T(~(T(0))) truncates the number to T, if needed. 
    return (T(~(T(0))) >> shift) << shift; 
} 

template<unsigned int shift> 
unsigned short zero_right(unsigned short arg) { 
    return arg & right_zero_bits<unsigned short, shift>(); 
} 
+0

是的,它解释了消息,但doesn不要解释警告:)可能是我对我的问题不够清楚。问题是:我应该担心吗? :) – SergeyA

+0

@SergeyA这个警告与你使用'unsigned short mask = 1048560;'得到的警告是一样的,也就是说你不应该担心,但是你应该用明确的表达来压制它。 –

+0

@TavianBarnes,我很乐意相信你 - 但要成为一个合适的LanguageLawyer答案,它需要一些证实这一说法:) – SergeyA

2

我不知道这是否是你想要什么,但它编译:

template<unsigned int shift> 
unsigned short zero_right(unsigned short arg) { 
    using type = unsigned short; 

    //constexpr type mask = ~(type(0)); 
    type right_zeros = ~(type(0)); 
    right_zeros <<= shift; 
    return arg & right_zeros; 
} 

int check() { 
    return zero_right<4>(16); 
} 

UPDATE:

Seems like you simply hushed the compiler by making sure it has no idea what is going on with the types.

没有

网络连接你首先得到right_zeros,其值为FFFF(来自~0)。通常,~0FFFFFFFFFFFFFF...,但由于您使用的是u16,因此您会收到FFFF

然后,由4相移产生FFFF0 [计算扩展到32个比特],但是当存回,只有最右边的16位保持,因此该值是FFF0

这是完全合法的和定义的行为和你正在利用截断。编译器是而不是“被愚弄”。实际上,无论是否截断都可以正常工作。

你可以做right_zeros到U32或U64,如果你希望,但随后你需要添加right_zeros &= 0xFFFF

If there is an undefined behavior (the very essence of my question!) you simply made it undetectable.

没有 UB基于代码的全部,不管是什么编译器说。

其实,Tavian得到它。使用显式的转换:

constexpr type right_zeros = (type) (mask << shift); // now clean 

这是告诉编译器,除其他事项外,你截断到16位。

如果有UB,那么编译器应该还是抱怨。

+1

好像您只是简单地通过确保编译器不知道类型是怎么回事。如果有一个未定义的行为(我的问题的本质!),你只是无法察觉。 – SergeyA

+3

“如果有UB,那么编译器仍然应该抱怨。” - 不要指望它 –

3

是的,正如你怀疑,甚至抑制编译器的诊断之后,你的代码是严格来说,因为从符号短到signed int的推广不完全可移植的,位算术符号int正在做,然后INT被转换签署回到未签名的短。你已经设法避免未定义的行为(我认为,在快速浏览后),但结果并不能保证你所期待的。 (type)~(type)0不需要对应于“所有位一个”在type类型;在转变之前已经有了。

要得到的东西完全便携的,只是确保你做你的算术至少unsigned int类型(较宽的类型,如果有必要,但绝不窄)。那么就不会有任何促进签名类型的担心。

template<unsigned int shift> 
unsigned short zero_right(unsigned short arg) { 
    using type = unsigned short; 

    constexpr auto mask = ~(type(0) + 0U); 
    constexpr auto right_zeros = mask << shift; 
    return arg & right_zeros; 
} 

int check() { 
    return zero_right<4>(16); 
}