2010-03-25 24 views
3

如果我用计算的中间部分高于那个变量类型的界限的变量进行计算,是否有一些平台可能不喜欢的危险?使用一个变量超出其界限的变量C

这是在问什么,我一个例子:

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

我编写这一点,它给的29297正确的答案(当然,内截断误差,反正)。但担心我的部分是30,000 * 32,000 = 960,000,000,这是一个30位的数字,因此无法存储在16位的int中。最终的结果完全在int的范围内,但我期望无论内存中的哪个工作部分都具有与最大源变量相同的大小,因此会发生溢出错误。

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

那么大多数处理器如何处理来自源和目标变量边界的计算?

回答

2

就我所知,大多数(如果不是全部的话)处理器将会保存一个双字*字乘法的结果 - 也就是说,一个8位* 8位存储在一个16位寄存器中一个8位处理器和一个32位* 32位操作存储在一个32位机器上的一个64位寄存器中。 (至少,这就是我用过的所有嵌入式微控制器的情况)

如果情况并非如此,处理器将严重受损,因为只允许半字*半字乘法。

0

AFAIK这种东西是正式的“undefined”。你必须做代数来防止溢出。这总是你的第一选择。数字稳定性不是偶然的,在决定何时以及如何进行分割和乘法时需要小心。或者,您必须保证您将使用足够大的中间结果缓冲区。

无论如何,使用大型中间缓冲区是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; 
+0

你的代码仍然假定'MAX_INT> = 960,000,000',这是提问者要求的一部分。 – 2010-03-25 22:59:53

+0

@Steve Jessop。在这种情况下,我们需要找到具有该属性的int版本。 '无符号长'? – 2010-03-25 23:37:12

+0

是的,'long'保证至少32位,所以'(a * 32000L)/ 32767'不会溢出,因为我们知道'a'是30000(或任何高达64M的东西)。 – 2010-03-26 01:54:43

0

带符号整数溢出是未定义的行为。因为(a)每个人都使用2的补码,其中算术运算对于相同大小的有符号和无符号类型按位相同,(b)环绕是无符号类型的C中定义的行为

因此,与16位int的实现,我希望结果0您计算(这是结果,它必须如果你已经使用一个无符号的16位int)。但是我会编写它可能会引发硬件异常,爆炸等的可能性。

请注意,如果您有32位int做两个16位short变量计算的机器上,那么你通常会得到“正确”的答案29297,因为中间值(a*32000)int,而只得到最后截断为short。我说“一般”是因为将一个超出范围的整数值转换为一个有符号的整数类型可以给出一个未指定的结果,否则就会产生一个信号。但是,再次,你在礼貌公司遇到的任何实现只需要一个模数。

3

在16位编译器/ CPU上,您可以(几乎)计划该代码,从而得到不正确的结果。这有点令人伤心,因为几乎每个CPU(它都有一个乘法指令)将产生并存储中间结果,但是没有C编译器(我知道它)通常会使用它(如果你制作了ab未签名,不允许使用)。

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

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

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; 

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

0

您的编译器和目标处理器都必须处理各种数据类型的大小。 编译器通常会在变量计算过程中将变量提升为最容易处理大小的变量,然后将结果转换为结果所需的任何大小的结果。 也有C规则,规范促进尺寸更难以处理一些计算。如果您正在编译一个AVR,它有8位寄存器但定义了一个int为16位,许多计算最终使用的寄存器数量比您认为它们需要更多的寄存器,因为这种升级以及代码中的常数被认为是int或unsigned int,除非编译器可以证明它不会影响计算的结果。 尝试用各种不同大小的整数(short,int,long,long long)重写您的代码并查看它是如何发生的。您可能还想编写一个简单的程序,输出标准预定义类型的sizeof()。 如果您需要担心整数变量的大小和/或计算的中间结果,那么您应该在您的声明和类型转换中包含和使用诸如uint32_t和int64_t之类的内容。