使用C中其边界之外的变量进行计算

时间:2010-03-25 22:41:14

标签: c variables

如果我使用变量进行计算,其中计算的中间部分高于该变量类型的边界,是否存在某些平台可能不喜欢的危险?

这是我要问的一个例子:

int a, b;
a=30000;
b=(a*32000)/32767;

我编译了这个,它确实给出了29297的正确答案(好吧,在截断错误中,无论如何)。但令我担心的部分是30,000 * 32,000 = 960,000,000,这是一个30位数,因此不能存储在16位int中。最终结果完全在int的范围内,但我期望内存的任何工作部分都将具有与最大源变量相同的大小,因此会发生溢出错误。

这只是一个显示我的问题的小例子,我试图通过使分数成为能够存储在该变量中的最大量的一小部分来避免使用浮点(在这种情况下,有符号整数,所以32767正面),因为我使用的嵌入式系统我相信没有FPU。

那么大多数处理器如何处理源和目标变量范围之外的计算?

6 个答案:

答案 0 :(得分:3)

在16位编译器/ CPU上,您可以(几乎)计划该代码,从而得出错误的结果。这有点令人伤心,因为几乎每个CPU(根本都有乘法指令)都会产生并存储中间结果,但是没有C编译器(我知道)通常会使用它(如果你做了{{ 1}}和a无符号,不允许使用它。)

你有几个选择来处理这个问题。一种是用汇编语言编写小b函数,它执行乘法(保留高位字)然后对它进行除法,最后在将值减少到范围时将值返回给C.

另一种选择是对无符号整数进行数学运算,这至少可以让你找出问题发生的时间。不幸的是,虽然我没有选择任何特别吸引人的选择......

答案 1 :(得分:2)

据我所知,大多数(如果不是所有)处理器都会在双字中保存字*字乘法的结果 - 意味着,8位* 8位存储在16位寄存器中8位处理器,32位* 32位操作存储在32位机器上的64位寄存器中。 (至少,这就是我用过的所有嵌入式微控制器的方式)

如果情况并非如此,处理器将严重削弱,只允许半字*半字乘法。

答案 2 :(得分:0)

AFAIK这种东西正式“未定义”。你必须做必要的代数来防止溢出。这永远是你的第一选择。数值稳定性并非偶然,在决定何时以及如何进行除法和乘法时需要谨慎。

或者,你必须保证你将使用足够大的中间结果缓冲区。

使用大型中间缓冲区是一些C编译器所做的事情。但是,这种语言没有任何保证。

所以,为了确保它有效,大多数人会这样做。

short a= 30000;
int temp= a;
int temp2= (a*32000)/32767;
// here you can check for errors; if temp2 > 32767, you have overflow.
short b= a;

答案 3 :(得分:0)

有符号整数溢出是未定义的行为。

几乎所有你可能遇到的实现都会绕过整数溢出,因为(a)每个人都使用2的补码,其中算术运算对于相同大小的有符号和无符号类型是按位相同的,并且(b)环绕是在C中定义了无符号类型的行为。

所以,对于一个16位int的实现,我希望你的计算结果为0(这就是必须的结果,如果你使用了无符号16位int)。但是我会对可能引发硬件异常,爆炸等的可能性进行编码。

请注意,如果在具有32位short的计算机上使用两个16位int变量进行计算,那么通常会得到“正确”的答案29297,因为中间值为{ {1}}是(a*32000),最后只会被截断回int。我说“通常”是因为将超出范围的整数值转换为有符号整数类型会产生未指定的结果,或者引发信号。但同样,你在礼貌公司遇到的任何实施都只需要一个模数。

答案 4 :(得分:0)

您确定您的编译器有16位整数吗?在当今的大多数系统中,整数是32位。你没有得到错误的另一个可能原因是一些编译器会认识到它可以在编译时计算这样的东西,并且会这样做。

如果你真的担心最终会出现溢出,你有时可能会对公式进行重新排序或计算,以免中间条件溢出。在您的示例中,由于您的所有术语都接近16位值的限制,因此很难做到。你需要数字是完全正确的,还是你可以近似?如果可以的话,你可以这样做:

int a, b;
a=30000;
//b=(a*32000)/32767 ~= a * (32000/32768) = a *(125/128)
b = (a / 128) * 125 // if a=30000, b = 29250 - about 0.16% error

另一种选择是将较大尺寸的类型用于中间术语。如果你的编译器有16位整数和32位长,你可以这样做:

int a, b;
a=30000;
b=((long)a*32000L)/32767L;

真的,如何处理溢出没有固定的答案。您需要自己评估每个案例并确定最佳解决方案。

答案 5 :(得分:0)

您的编译器和目标处理器都与各种数据类型的大小有关。 编译器通常会在计算过程中将变量推广到最大的易于使用的变量,然后将结果转换为最终分配所需的大小。 还有C规则可以控制对尺寸的推广,这些尺寸在某些计算中更难以使用。如果你正在编译AVR,它有8位寄存器但是将int定义为16位,那么很多计算最终会使用比你想象的更多的寄存器,因为这个促销以及代码中的常数都有被认为是int或unsigned int,除非编译器能证明自己不会影响计算的结果。 尝试使用各种不同大小的整数(short,int,long,long long)重写代码,看看它是怎么回事。您可能还想编写一个打印出标准预定义类型的sizeof()的简单程序。 如果您需要担心整数变量的大小和/或计算的中间结果,那么您应该为声明和类型转换包含和使用uint32_t和int64_t等内容。