这个问题的动机是由我在C / C ++中实现加密算法(例如SHA-1),编写与平台无关的可移植代码以及彻底避免未定义的行为引起的。
假设标准的加密算法要求您实现此目的:
b = (a << 31) & 0xFFFFFFFF
其中a
和b
是无符号的32位整数。注意,在结果中,我们丢弃了最低有效32位以上的任何位。
作为第一个幼稚的近似值,我们可以假设int
在大多数平台上该宽度为32位,因此我们可以这样写:
unsigned int a = (...);
unsigned int b = a << 31;
我们知道该代码不会在任何地方都起作用,因为int
在某些系统上为16位宽,在其他系统上为64位,甚至可能为36位。但是使用stdint.h
,我们可以使用以下uint32_t
类型来改进此代码:
uint32_t a = (...);
uint32_t b = a << 31;
这样我们就完成了,对吧?这就是我多年以来的想法。... 不完全的。假设在某个平台上,我们有:
// stdint.h
typedef unsigned short uint32_t;
在C / C ++中执行算术运算的规则是,如果类型(例如short
)比窄int
,则将其扩展到int
所有值都适合的范围,unsigned int
否则。
假设编译器定义short
为32位(有符号)和int
48位(有符号)。然后这些代码行:
uint32_t a = (...);
uint32_t b = a << 31;
实际上将意味着:
unsigned short a = (...);
unsigned short b = (unsigned short)((int)a << 31);
请注意,a
之所以被提升为,是int
因为所有ushort
(即uint32
)都适合int
(即int48
)。
但是现在我们有一个问题:将非零位左移到有符号整数类型的符号位中是未定义的行为。发生此问题的原因是我们uint32
被提升为int48
-而不是提升为uint48
(这里可以左移)。
这是我的问题:
我的推理正确吗,这在理论上是合理的问题吗?
是否可以忽略此问题,因为在每个平台上,下一个整数类型都是宽度的两倍?
是一个好主意,以正确地抵御这种病态情况下通过屏蔽预这样?:输入
b = (a & 1) << 31;
。(这在每个平台上都必定是正确的。但是,这可能会使对速度要求严格的加密算法慢于必要。)
澄清/修改:
我会接受C或C ++或两者的答案。我想知道至少一种语言的答案。
预屏蔽逻辑可能会损害位旋转。例如,GCC将以汇编
b = (a << 31) | (a >> 1);
语言编译为32位的位旋转指令。但是,如果我们预先屏蔽了左移,则新逻辑可能不会转换为位旋转,这意味着现在执行4个操作而不是1个。