c语言的算术运算溢出问题

1、 关于溢出的结论:可能出现的情况是结果的数据类型定义的小了,导致结果不正确。

关于计算溢出,看书上说的c语言中有符号数计算溢出的话会不知道发生什么(溢出结果未定义),看编译器怎么处理。我在keil上试了下,溢出会把溢出部分砍掉,比如定义的是short型的,结果保留2个字节。

short aa=-32767;

short bb=32;

short cc;

cc= aa- bb;

cc=7fe1;

如果扩展到jint,结果是ffff7fe1.

检查是否溢出加 if(aa < 0 && aa -(-32768) >=bb)

参考:https://blog.csdn.net/weiqifa0/article/details/24320437?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

这篇文章中说的整数转型溢出感觉比较常见还容易忽略,谨记。主要是负数的时候。

示例二:整形转型时的溢出

1

2

3

4

5

6

7

8

9

10

11

12

13

int copy_something(char *buf, int len)

{

    #define MAX_LEN 256

    charmybuf[MAX_LEN];

     ... ...

     ... ...

 

     if(len > MAX_LEN){// <---- [1]

         return-1;

     }

 

     returnmemcpy(mybuf, buf, len);

}

上面这个例子中,还是[1]处的if语句,看上去没有会问题,但是len是个signed int,而memcpy则需一个size_t的len,也就是一个unsigned 类型。于是,len会被提升为unsigned,此时,如果我们给len传一个负数,会通过了if的检查,但在memcpy里会被提升为一个正数,于是我们的mybuf就是overflow了。这个会导致mybuf缓冲区后面的数据被重写。

uint8_t合并成uint16_t时   在keil上试的不加uint16_t也可以。

uint16_t LEBufToUint16(uint8_t *_pBuf)
{
    return (((uint16_t)_pBuf[1] << 8) | _pBuf[0]);
}

再试:

short  a = INT16_MAX;

shor b = 32;

int cc = a + b;

结果正确。再试:

int a = INT32_MAX;

int b = 32;

uint32_t  c = a+ b;

c=0x8000001f

并没有像链接中文章说的a+b的结果按int存会溢出.可能是编译器处理了,虽然在c语言上结果是未知的。

"我们来看一段代码:

1

2

3

4

5

void foo(int m, int n)

{

    size_ts = m + n;

    .......

}

上面这段代码有两个风险:1)有符号转无符号2)整型溢出。这两个情况在前面的那些示例中你都应该看到了。所以,你千万不要把任何检查的代码写在 s = m + n 这条语名后面,不然就太晚了。undefined行为就会出现了——用句纯正的英文表达就是——“Dragon is here”——你什么也控制不住了。(注意:有些初学者也许会以为size_t是无符号的,而根据优先级 m 和 n 会被提升到unsigned int。其实不是这样的,m 和 n 还是signed int,m + n 的结果也是signed int,然后再把这个结果转成unsigned int 赋值给s)"

3、用“UL”避免Keil C51大整数常量运算溢出错误

Keil C51是与ANSI C兼容的编译器,ANSI C规范规定十进制整数常量的默认数据类型是int、long int和unsigned long int的其中一种,对给定的常量是其中的哪一种要看这个常量的实际大小,如果常数在-32768~32767之间则按int类型处理,如果按int类型处理会溢出就考虑long int或更大的数据类型unsigned long int。总之,编译器总是按尽可能的原则指定常量的类型。
但这一原则并不总能奏效,当两个常量做运算时就可能导致溢出。如:
#define SYSCLK                        22118400    // SYSCLK in Hz (22.1184 MHz external crystal oscillator)
#define SLIDER_REST_TIME    100              // in ms,slider rest time
#define REST_DELAY               SYSCLK * SLIDER_REST_TIME / (65536 * 1000)
unsigned char i;
i = REST_DELAY;
在keil c51中运行i为0xE1,即225,并不是期望的结果22118400 * 100 / (65536 * 1000) = 33.75,取整为33。原因分析如下:
宏替换后为:i = 22118400 * 100 / (65536 * 1000);,编译器首先为22118400定义类型,因为22118400不在int的表示范围内,而在long int的范围-2147483648~2147483647内,所以22118400按long int类型处理,在做乘积运算时100被自动按long int处理,22118400 * 100将按两带符号长整型常量进行运算,运算结果仍为带符号长整型,结果写成十六进制是0x83D60000,其十进制是-2083127296,显然出现了溢出错误。keil编译器并没有给出任何错误或警告提示信息(VC++6.0还给出警告warning C4307: '*' : integral constant overflow),继续进行下一个运算65536 * 1000,结果为带符号长整型,十六进制为0x3E80000,十进制为65536000,最后按两长整型除法计算-2083127296 / 65536000,结果为0xFFFFFFE1,由于i为字符类型,取0xFFFFFFE1的最低有效字节为0xE1赋值给i,i的最终值为0xE1。
解决这种溢出错误的方法用C语言的一个术语就是“提升”(promotion),拿上例来说就是将22118400指定为无符号长整型,即:
#define SYSCLK                        22118400UL
注:虽然只要将22118400、100、65536和1000四个常数中的一个指定为无符号长整型即可得到正确的结果,但考虑到可读性及规范性,应选择大整数指定其类型。
小结:按C51编译器的默认类型整数常量运算可能出现溢出错误,对大整数应指定其数据类型以避免出现可能的运算错误。

转自:幽幽灵猫

http://www.cnblogs.com/civet/archive/2011/05/31/2064959.html

你可能感兴趣的:(c语言基础)