移带负号的值是未定义的
我主演在原来的JPEG标准(ITU 81),特别是图中图F.12:移带负号的值是未定义的
参考:以V延伸的解码值的符号位在SLL
术语表示:shift left logical operation
(见PDF第15页)
现在著名的libjpeg执行决定实施它this way(相当直接转录):
/*
* Figure F.12: extend sign bit.
* On some machines, a shift and add will be faster than a table lookup.
*/
#ifdef AVOID_TABLES
#define HUFF_EXTEND(x,s) ((x) < (1<<((s)-1)) ? (x) + (((-1)<<(s)) + 1) : (x))
#else
#define HUFF_EXTEND(x,s) ((x) < extend_test[s] ? (x) + extend_offset[s] : (x))
static const int extend_test[16] = /* entry n is 2**(n-1) */
{ 0, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080,
0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000 };
static const int extend_offset[16] = /* entry n is (-1 << n) + 1 */
{ 0, ((-1)<<1) + 1, ((-1)<<2) + 1, ((-1)<<3) + 1, ((-1)<<4) + 1,
((-1)<<5) + 1, ((-1)<<6) + 1, ((-1)<<7) + 1, ((-1)<<8) + 1,
((-1)<<9) + 1, ((-1)<<10) + 1, ((-1)<<11) + 1, ((-1)<<12) + 1,
((-1)<<13) + 1, ((-1)<<14) + 1, ((-1)<<15) + 1 };
#endif /* AVOID_TABLES */
很明显移位负符号值UB,所以我想知道什么JPEG标准的原作者居然在这里的意思。 JPEG标准是否限于二进制补码表示?
JPEG标准是否限于二进制补码表示?
使用图表使用SLL
而不是*2
,流程图依赖于2的补码实现。 C,OTOH不限于2的补码,也不限于以某种“2的补码”方式使用移位。更好的编码没有这个假设。使用无符号类型是很好的第一步。
// ((-1)<<15) + 1
((-1u)<<15) + 1
需要查看HUFF_EXTEND()
的应用程序的更深的答案。
在C语言中转移负值只是未定义的行为。在汇编程序级别,这种转换非常好。逻辑/算术左移指令将MSB移入进位位,就是这样。 CPU不会以不确定的方式停止并着火。
也就是说,您可以通过将您签名的号码转换为无符号来避免UB。那么你只会有实现定义的行为。例如,(int)(-1u<<1)
确实不会调用UB,即使这样的代码看起来相当可疑。
安全的做法是对无符号数(uint32_t
)执行所有计算并仅在实际需要时将其转换为带符号。切勿对签名类型执行任何形式的按位操作。
我完全忽略了与不使用二进制补码的非常奇特/虚构系统的兼容性。关注与真实世界主流电脑的兼容性。
不,只移动_负性位移(或大于或等于左操作数大小位移)是未定义的behaviour_。由于左移相当于一个*乘以两个乘法*,对于'signed',C相同也是正确的移位(乘以除数)。实际发生的情况是'unsigned int'移动**不同于** signed int'值移动。为了保持签名并继续作为两次操作的分界线,右移是impl。因为_sign扩展除以两个操作_在'signed's。 'ASR' /'LSR'机器指令与'signed' /'unsigned's一起使用, –
很明显,变速负数量具有由两个作为乘法/除法升高到n次幂(为a <<n
/a>> n
其中a
是signed
)这是永远不会为负左操作数未定义的行为相同的效果,并对于C/C++来说已经很好并精确定义了。
那么,尽管接受的答案已被选中,并且关于运算符<<
和>>
的讨论的数量已经生成,但我会尝试澄清什么是未定义的,哪些不是。
<<
操作者上signed
和unsigned
左操作数的数量同样的行为,作为一个整数的净效应是在数值加倍,并且该过程是用于signed
和unsigned
数量是相同的,只是插入的一个0
数字的右侧部分,将所有数字位移到所指示的数字,作为正确的操作符。>>
操作行为不同的signed
和unsigned
左操作数,作为净效应,同样,像整数除法由两个提高到右边的操作数,这意味着,的二进制补码表示到延长最显着位(0
左边变成00
和1
左边变成11
)实现了除以2的除法的净效果,右边的操作数的权力提高(-24 >> 3
变为-3
,除以2^3,和24 >> 3
变成3
)当使用unsigned
整数时,此行为改变(0
变为00
和1
变为01
)使得再次像二分之一的移位提升到正确的运算符的能力。
由于标准说,行为是未定义如果您尝试左/右移位位(右操作者必须> 0
)负量或量大于或小于左操作物的大小相等位。但是这只影响右操作数,而不影响左操作数。
试图获得有关JPEG解码的解释,试图认为JPEG解码使用离散实数近似值编码为有符号数量的COSIN变换(它定义为实矢量),或者它使用带符号整数来评估低精度采样,因此他们从不处理无符号数量(库设计人员假定对整数进行操作比对浮点数进行操作要快)。
注-
-24
是二进制1111111...1111101000
,并且通过3
右移位使得它11111...11111101
,或-3
。如果我们再次移动它,它将变为111...1110
或-2
(-1.5
四舍五入至负无穷),并且再次变为1111...1111
或-1
。 -
24
是00000000....00011000
并右移它变成000000...0000011
这是3
。如果我们再次移动它,它将变为000...0001
或1
(1.5
四舍五入为负无穷),并且我们再次获得000...0000
或0
(0.5
四舍五入至负无穷大)。
(请注意这种方法制成的消极转变截断高达负无穷大,使得-1 >> 1
成为-1
,为-0.5
截断到-1
而不是0
。)
据我所知,移位一个负符号值是实现定义的(只要移位量为正数且在'sizeof(operator)* CHAR_BIT'内) – bolov
@bolov,* right * - 移动一个负值是实现定义的。 *另一方面,左移*移动签名类型的值的方法是***未定义的,除非原始值是非负数,并且数学结果可以在结果类型中表示。特别是,OP十分正确,左移-1产生UB。 –
@JohnBollinger谢谢。总有一些事情需要重新学习C或C++。 – bolov