C was designed to implicitly and silently change the integer types of the operands used in expressions. ……These rules are often not even known by the average C programmer and therefore causing all manner of very subtle bugs.

——stackoverflow/implicit-type-promotion-rules

之所以踩到这个坑里,是在将C/C++代码改写为NEON时,忽略了整型提升,将原本应该提升为int的结果用short来保存了,导致结果错误。

从一个简单的例子开始吧:

int main()
{
	short s1 = 32767;
	short s2 = ((s1 + 5) >> 2);
	short s3 = ((short)(s1 + 5) >> 2);
	short s4 = ((unsigned short)(s1 + 5) >> 2);
	return 0;
}

最终s2的结果为8193,s3为-8191,s4为8193。
这里,在计算s2时,便发生了整型提升:s1+5得到32772,再右移2位便得到8193。而计算s3时,s1+5所得的32772(0x8004)先转换为short型即-32764(0x8004),再进行算数右移,高位补1,便得到-8191。计算s4时,与s3的不同之处是将0x8004转换为了unsigned short型,因此算数右移时高位补0,得到的也就是8193了。

P.S. 复习一下C/C++的移位运算:左移是逻辑移动(从末端移掉的位将被舍弃,包括符号位);右移是算数移动,对于无符号数字,因移位运算而空出的位上将用零填充。 对于有符号数字,符号位用于填充空出的位。

文章开头引用的stackoverflow上的文章对整型提升做了比较全面的介绍,还有usual arithmetic conversion,可参考。

Formally, the rule says (C11 6.3.1.1):

If an int can represent all values of the original type (as restricted by the width, for a bit-field), the value is converted to an int; otherwise, it is converted to an unsigned int. These are called the integer promotions.

This means that all small integer types, no matter signedness, get implicitly converted to (signed) int when used in most expressions.

This text is often misunderstood as: “all small, signed integer types are converted to signed int and all small, unsigned integer types are converted to unsigned int”. This is incorrect. The unsigned part here only means that if we have for example an unsigned short operand, and int happens to have the same size as short on the given system, then the unsigned short operand is converted to unsigned int. As in, nothing of note really happens. But in case short is a smaller type than int, it is always converted to (signed) int, regardless of it the short was signed or unsigned!