前言
不知不觉,已经开学的第二周了。没错,是线上教学,可能因为我上次博客对开学的渴望,他妥妥的延期了。上次还欠了一次python的双下方法,这个明天再说,今天分享一个今天在c语言安全课程这门课上学到的好东西,也是很有意思。
简述
什么是整形提升呢?
整型提升是C程序设计语言中的一项规定:在表达式计算时,各种整形首先要提升为int类型,如果int类型不足以表示则要提升为unsigned
int类型;然后执行表达式的运算 也就是说我们的char,short等字节数小于int型的数据在CPU中处理时,都得转换为一个int型
而且整型提升是隐式转换:数据的类型转换由编译系统自动进行,不需要人工干预
整形提升的规则
那为什么我们会开展这个知识点在C语言安全编程中,因为如果忽略了整形提升就会出现很多麻烦,麻烦会一一举例,让我们先看看他的提升规则
-
若是有符号数,则前面补充的位补符号位,负数补1,正数补0
以char -1为例,一字节8位,而int型8字节32位,需要补充前面的位均为符号位1,提升为
1111 1111 1111 1111 1111 1111 1111 1111 -
若是无符号位,则前面补充的位补0
以char 1为例,一字节8位,而int型8字节32位,需要补充前面的位为0,提升为
0000 0000 0000 0000 0000 0000 0000 0001
示例1
#include<stdio.h>
int main(){
char a = 0x76;
short b = 0x7600;
int c = 0x76000000;
if(a == 0x76) printf("a is ok\n");
if(b == 0x7600) printf("b is ok\n");
if(c == 0x76000000) printf("c is ok\n");
printf("a = %d,b = %d,c = %d\n",a,b,c);
}
运行结果:int类型肯定不用考虑,显然相等
对于char 0x76以及 short 0x7600,他们转换为二进制均为不足32位的二进制码,需进行整形提升
以0x76为例:
它的最高位与0x7600一样均为0,当补其余高位时仍为0,与16进制转换为2进制的32位编码相同,因此a,b变量也相等
如上图转换为整形十进制数为118
但当76换为86时,一切结果都改变了
#include<stdio.h>
int main(){
char a = 0x86;
short b = 0x8600;
int c = 0x86000000;
if(a == 0x86) printf("a is ok\n");
if(b == 0x8600) printf("b is ok\n");
if(c == 0x86000000) printf("c is ok\n");
printf("a = %d,b = %d,c = %d\n",a,b,c);
}
运行结果:
因为不管char 0x86还是short 0x86转换为二进制码,最高位均为1,会被视为有符号位的提升,转换为32位的其余位均补为1
以char 0x86为例:
补全后的补码转换为10进制为-122,与int型16进制86转换为的十进制数206不等,也就出现了意料之外的运行结果可能这便是在安全编程中引入这个知识点的作用,是不是意料之外的错误
示例2
int main(){
char c,c1,c2,c3;
c1 = 100;
c2 = 3;
c3 = 4;
c = c1*c2/c3;
printf("c = %c %d",c,c);
}
这个题我们可能会认为100*3已经超出了一个8位数的范围了,毕竟2的8次方是256嘛
但因为整形提升的存在,让值的容量扩大,不会发生截断等来影响本来的结果
而char型数据在C语言中以ASCII码形式存储,故对应的字符值为75所对应的字符K
运行结果:
示例3
#include <limits.h>
#include<stdio.h>
int main(){
unsigned char uc = UCHAR_MAX;
printf("uc is %d %x\n",uc,uc);
int i;
i = ~uc;
printf("i is %d\n",i) ;
return 0;
}
运行结果:
在这里先是输出无符号char类型的最大值,他的int型输出结果为255
根据整形提升,此char类型uc提升存储的整型形式为:
00000000 00000000 00000000 11111111
而~uc是对二进制数的一个取反
因此整型i的补码为
11111111 11111111 11111111 00000000
因此i的值输出为256
整形转换
根据整形提升,我们也可以对整形转换做一个总结
- 安全转换:较小的无符号整数类型转换到较大的无符号整数类型或有符号整数类型
- 例如:short,char等型转换为int型
- 非安全转换:较大的无符号整数类型转换为较小的无(有)符号整数类型;无符号整数类型转换为对应等长的有符号整数类型
示例1
#include <limits.h>
#include<stdio.h>
int main(){
//安全转换
unsigned char a = 0x31;
unsigned int a1;
a1 = (unsigned int)a;
printf("a:%c----a1:%d\n",a,a1);
//不安全转换
unsigned int b = 0xAAAABBBB;
unsigned char b1;
b1 = (unsigned char)b;
char b2;
b2 = (char)b;
int b3;
b3 = (int)b;
printf("b:%u----b1:%d----b2:%d----b3:%d\n",b,b1,b2,b3);
return 0 ;
}
运行结果:
第一次输出b为他的无符号十进制位,直接转换便可得到
而后面几次的输出为何结果各不相同,便是因为发生了从int大字节型向char或unsigned等小字节型的改变
如上为无符号int型b
1、当转换为无符号char型时,只截获二进制原码的后8位
1011 1011 十进制输出为 187
2、当转化为有符号char型时,只截获二进制原码的后8位
仍为1011 1011
但作为有符号数,他以补码的形式转换为十进制 -69
3、当转换为有符号int型时,此二进制码在机器中对应的是他的补码形态
1010 1010 1010 1010 1011 1011 1011 1011转换为10进制数为**-1431651397**
示例2
就拿上面两个例子来说,第一个将有符号数直接赋值给了无符号数
第二个memcpy函数的第三个参数为要被复制的字节数,也等于将一个有符号数强制转换成无字符数,都是很常见的整形转换出现的问题
运行结果:
第二个程序更是直接崩溃
截断错误
随着上面的渗透,最后分享的这个也就很容易得到了
将一个较大的整数转换为较小的整数,并且该数的原值超出较小类型的表示范围,则会发生截断错误。
截断错误会引起数据精度的丢失。正常来说,原值的低位被保留下来而高位则被丢弃。
这也正是上面例子中会出现的问题