语境
我们正在移植最初使用PIC单片机的8位C编译器编译的C代码。为了防止无符号全局变量(例如,错误计数器)回滚到零,使用了一个常见的习惯用法:
if(~counter) counter++;
此处的按位运算符将所有位取反,并且仅当counter
小于最大值时该语句才为真。重要的是,无论变量大小如何,此方法均有效。
问题
我们现在的目标是使用GCC的32位ARM处理器。我们注意到,相同的代码会产生不同的结果。据我们所知,按位补码运算返回的值与我们期望的大小不同。为了重现这一点,我们在GCC中进行编译:
uint8_t i = 0;
int sz;
sz = sizeof(i);
printf("Size of variable: %d\n", sz); // Size of variable: 1
sz = sizeof(~i);
printf("Size of result: %d\n", sz); // Size of result: 4
在输出的第一行中,我们得到的是:i
1个字节。但是,的按位补码i
实际上是四个字节,这会引起问题,因为与此进行比较现在无法给出预期的结果。例如,如果这样做(在哪里i
是正确初始化的uint8_t
):
if(~i) i++;
我们将看到i
从0xFF到0x00的“环绕”。与以前的编译器和8位PIC微控制器中预期的运行方式相比,GCC的行为有所不同。
我们知道我们可以通过如下方式解决此问题:
if((uint8_t)~i) i++;
或者,通过
if(i < 0xFF) i++;
但是,在这两种解决方法中,必须知道变量的大小,并且软件开发人员容易出错。这些上限检查在整个代码库中进行。有变量多种尺寸(例如,uint16_t
和unsigned char
等),并在工作,不然代码库改变这些并不是我们期待的东西。
题
我们对问题的理解是否正确,是否有可用的解决方案,而无需在使用这种习语的每种情况下都需要重新访问?我们的假设正确吗,像按位补码之类的操作应返回与操作数相同大小的结果?似乎这将中断,具体取决于处理器体系结构。我觉得自己正在服用疯狂药丸,而C应该比这更便携。同样,我们对此的理解可能是错误的。
从表面上看,这似乎不是一个大问题,但是这种以前使用的习惯用法已在数百个地点使用,我们渴望在进行昂贵的更改之前先了解这一点。
注意:这里有一个看似相似但并非完全相同的问题:char的按位运算给出32位结果
我没有看到这里讨论的实际问题的症结,即按位补码的结果大小与传递给运算符的结果大小不同。