首页 > 编程语言 >C++编译器对溢出的默认处理

C++编译器对溢出的默认处理

时间:2024-04-12 15:47:31浏览次数:22  
标签:10 cnt u8 max 默认 编译器 C++ 溢出

C++编译器对溢出的默认处理
在算数运算中,有一个比较头疼又必须要处理的事情:“溢出”,当我们有所疏忽,没有对溢出的情况做处理时,在我们不知情下就会产生很诡异的bug!

那么当我们没有做溢出处理时,编译器的默认处理方式是什么呢?下面我们探究一下这个问题。

测试环境

  • Linux 4.15.0 #16.04.1-Ubuntux 86_64 GNU/Linux
  • gcc 5.4.0 20160609
  • g++ 5.4.0 20160609

表一.C++基本类型的表示范围:

数据类型 占内存字节数 表示范围
char(signed char) 1 -128~127
unsigned char 1 0~255
short int(signed short int) 2 -32768~32767
unsigned short int 2 0~65,535
int(signed int) 4 -2147483648~2147483647(-231~ 231-1)
unsigned int 4 0~4294967295
long int(signed long int) 4 -2147483648~2147483647 (-231~ 231-1)
unsigned long int 4 0~4294967295
float 4 -3.4x10-38 ~ 3.4x1038
double 8 -1.7x10-308 ~ 1.7x10308
long double 8 -1.7x10-308 ~ 1.7x10308

1.整数溢出

1.1 无符号溢出

以下是无符号整型的溢出测试代码

#include <stdio.h>
#include <stdint.h>

int main()
{
    uint8_t u8_max = 255;
    uint16_t u16_max = 65535;
    uint32_t u32_max = 4294967295;

    uint8_t u8_cnt = u8_max + 10;
    uint16_t u16_cnt = u16_max + 10;
    uint32_t u32_cnt = u32_max + 10;
    
    printf("u8: %d\nu16: %d\nu32: %d\n", u8_cnt, u16_cnt, u32_cnt);
    printf("\n");

    u8_cnt = u8_max * 10;
    u16_cnt = u16_max * 10;
    u32_cnt = u32_max * 10;
    
    printf("u8: %d\nu16: %d\nu32: %d\n", u8_cnt, u16_cnt, u32_cnt);
    return 0;
}

输出结果

            //原值          -> 16进制值     -> 截断值       -> 十进制显示值
u8: 9       //265           -> 0x109        -> 0x09         -> 9
u16: 9      //65545         -> 0x10009      -> 0x0009       -> 9
u32: 9      //4294967305    -> 0x100000009  -> 0x00000009   -> 9

u8: 246     //2550          -> 0x9F6        -> 0xF6         -> 246
u16: 65526  //655350        -> 0X9FFF6      -> 0XFFF6       -> 65526
u32: -10    //42949673050   -> 0x9FFFFFFF6  -> 0xFFFFFFF6   -> -10

//gcc、 g++ -std=c++11、g++ -std=c++17 的输出结果均一致,同时没有溢出警告

我们对以上输出结果,换算为二进制后,可以明显的看出,可见对于无符号整型,当发生溢出时,编译器默认处理为截断,即舍弃高位的溢出部分

注意: 输出结果是十进制值,是其二进制存储格式的反应,整数以源码形式存储,负数为补码形式!

问题提出:0xFFF60xFFFFFFF6的最高位都为1,为什么0xFFF6 printf的打印值一个是是65526,另一个却是-10?printf函数是如何区分输入变量的正负符号的呢?

1.2 有符号溢出

以下是有符号整型的溢出测试代码

    int8_t i8_max = 127;
    int16_t i16_max = 32767;
    int32_t i32_max = 2147483647;

    int8_t i8_cnt = i8_max + 10;
    int16_t i16_cnt = i16_max + 10;
    int32_t i32_cnt = i32_max + 10;
    
    printf("i8: %d\ni16: %d\ni32: %d\n", i8_cnt, i16_cnt, i32_cnt);
    printf("\n");

    i8_cnt = i8_max * 10;
    i16_cnt = i16_max * 10;
    i32_cnt = i32_max * 10;
    
    printf("i8: %d\ni16: %d\ni32: %d\n", i8_cnt, i16_cnt, i32_cnt);

输出结果

//gcc、 g++ -std=c++11、g++ -std=c++17 的输出结果均一致,如下
                //原值          -> 16进制值     -> 截断值       -> 十进制显示值
i8: -119        //137           -> 0x89         -> 0x89         -> -199
i16: -32759     //32777         ->  0x8009      -> 0x8009       -> -32759
i32: -2147483639//同理

i8: -10         //1270          -> 0x4F6        -> 0xF6         -> -10
i16: -10        //同理
i32: -10        //同理

从结果看,有符号数的溢出默认处理也是截断,我们从十进制的角度看不出规律,但是以整型的二进制存储格式来看是很清楚的,gcc\g++编译器默认的溢出处理方式就是截断。

题外话: 从上面的测试例子已经可以看出,对C++编译器来说,有符号的类型和无符号类型实际上没有区别。 类型在C++里的作用,只是一个字节大小的声明,编译器不知道也不care里面的存放值是有符号还是无符号的,算数运算遵循最基本的二进制运算规则。

1.3 字面常量的运算溢出

以下用常量运算式进行赋值的测试代码

    uint8_t u8_cnt;
    const uint8_t u8_cnt2 = 255;
    const int8_t i8_cnt   = 127;

    u8_cnt = 255 + 10;
    uint8_t sum = u8_cnt2 + 10;
    int8_t sum2 = i8_cnt + 10;

编译输出:

overflow_test.cpp: In function ‘int main()’:
overflow_test.cpp:10:18: warning: unsigned conversion from ‘int’ to ‘uint8_t’ {aka ‘unsigned char’} changes value from ‘265’ to ‘9’ [-Woverflow]
   10 |     u8_cnt = 255 + 10;
      |              ~~~~^~~~
overflow_test.cpp:11:27: warning: unsigned conversion from ‘int’ to ‘uint8_t’ {aka ‘unsigned char’} changes value from ‘265’ to ‘9’ [-Woverflow]
   11 |     uint8_t sum = u8_cnt2 + 10;
      |                   ~~~~~~~~^~~~

打印输出

u8: 9
sum: 9
sum2: -119

可见,在常量运算和赋值时,编译器可以在编译期检查溢出情况,这与非常量运算编译器没有溢出警告是不一样的!因为非常量的运算实在运行时进行的,编译器无法做溢出检查。
但是这种编译期的溢出检查也是有限,只能检查超出类型字节长度的情况,其他的类型收窄的情况无法检查出,如把uint8类型赋值给一个int8类型。

2. 如何避免溢出

C++ 官方库并没有提供溢出检查的宏或者方法,自实现的话比较麻烦,同时在每个运算时加入检查语句也不现实,所以避免溢出的最好方式还是在编码阶段去规避,当硬件性能允许的条件下,多用int、long类型,并用合理的类型去存放运算后的值。合理的代码设计能规避大部分的溢出风险。

3. 对溢出的处理方式

  • 截断:自动舍弃高位
  • 饱和:返回该类型的最大或者最小值
  • panic: 如Rust编译器在debug下存在溢出则触发panic

4. 总结

  1. gcc g++编译器对整数溢出的默认处理方式为截断
  2. 编译器可对常量的运算、赋值在编译期做溢出检查,但仅能检查出超字节长度的情况
  3. 非常量的运算实在运行时进行的,编译器无法做溢出检查
  4. C++对溢出检查不友善,可在编码阶段通过合理的设计去规避

标签:10,cnt,u8,max,默认,编译器,C++,溢出
From: https://www.cnblogs.com/HuangLiDi/p/18131445

相关文章

  • C++观察者模式的实现
    C++观察者模式的实现观察者模式介绍观察者模式是软件设计模式里面一种很常用又很重要的一种设计模式,观察者模式又叫做发布-订阅(Publish/Subscribe)模式。也就是主题对象(subject)发布通知,订阅该主题的多个观察者(observer)可以收到通知从而更新自己。主题对象Subject发出通知时并不......
  • C++陷阱 — C++ auto变量类型推导
    问题描述C++使用auto类型声明一个单例对象的引用时,通过该auto变量来访问单例,是否等同于使用单例类::Instance()来访问单例呢?试看如下的例子:#include<stdint.h>#include<iostream>#include<string>#include<map>usingnamespacestd;classSingleClass{public:......
  • String类型转LPCTSTR -----理解C++中的字符串类型转换
    在看代码时,发现有时候会把string类型转换为LPCTSTR,刚开始不理解为什么要做这个转换,所以做了一些调查,现在记录如下是这样的,STRING是代表C++中的字符串string,而LPCTSTR代表的是Windows系统中的字符串类型。也就是说,这样转换的目的是为了把C++中的字符串string转换为Windows系......
  • 清除vue默认样式
    1:引入reset.scss/***ENGINE*v0.2|20150615*License:none(publicdomain)*/*,*:after,*:before{box-sizing:border-box;outline:none;}html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr......
  • 2022年蓝桥杯C++B组国赛-试题D-最大数字
    0.题目问题描述给定一个正整数N。你可以对N的任意一位数字执行任意次以下2种操作:将该位数字加1。如果该位数字已经是9,加1之后变成0。将该位数字减1。如果该位数字已经是0,减1之后变成9。你现在总共可以执行1号操作不超过A次,2号操作不超过......
  • C++——线性动态规划
    线性动态规划引入:1.爬楼梯爬楼梯类型的问题可谓是线性DP的入门题目以及经典中的经典。我们先来看一下题目。爬楼梯题目描述有一天,三萩实在太无聊了,竟然无聊到去数台阶了。有一个楼梯一共有m级,刚开始三萩在第一级,他就想,若每次只能跨上一级或者二级,要走上m级,共有多少种走法?......
  • C++ 引用和指针:内存地址、创建方法及应用解析
    C++引用和指针创建引用引用变量是对现有变量的“别名”,它是使用&运算符创建的:stringfood="Pizza";//食物变量string&meal=food;//对food的引用现在,我们可以使用变量名food或引用名meal来引用食物变量:cout<<food<<"\n";//输出Pizzacout<<mea......
  • EECE 6083/5183编译器项目
    EECE6083/5183编译器项目1编译器项目类项目是手工构建一个简单的递归体面(LL(1))编译器(不使用编译器诸如flex或antlr的构造工具)。你可以使用任何支持递归的命令式块结构编程语言,我可以为它安装一个标准的debian包在我的电脑上测试你的解决方案。学生在本课程中使用的语言示例包括:c、c......
  • 默认显示三张,第一张和第三张只显示一半,中间全部显示,点击可左右切换默认显示一二张
     项目地址:https://github.com/holiday0912/SwiperPreView   varswiper=newSwiper(".swiper-container",{autoplay:false,loop:true,initialSlide:2,spaceBetween:20,centeredSlides:true,slidesPerView:1.9,p......
  • echarts地图默认高度
    echarts地图如果不设置默认高度,地图绘制的时候一直有个默认100px,即使地图的父级都设置了width:100%;height:100%;地图还是不能在高度上继承父级高度。除非你给父级一个确定的高度,比如200px这种。所以解决办法来了,你可以试试width:100%;height:100vh:这里的100vh就拿到了浏览器......