本人能力有限,难免有叙述错误或者不详细之处!希望读者在阅读时可以反馈一下错误以及不够好的地方!感激不尽!
目录
Release和Debug版本的一些区别
整形在内存中的存储
原码,补码,反码
大小端字节序
阶段与整型提升细述:
浮点数在内存中的存储:
C语言中,数据存储的概念非常重要,清楚的了解存储的规则可以很好的帮助我们在写代码的时候排除一些BUG,以下则是对C语言数据存储的概述。
首先则是平常我们可能不会注意到的部分,这个到底是什么?
Release和Debug版本的一些区别
在平常我们写代码时,通常使用的都是Debug版本,在这个版本下,编译器在代码运行的时候不会做出任何优化,代码会像一个没有包装好的机器一样直接运行,方便我们程序员进行调试,数据的存储则与release版本有一些区别。
release版本则会在代码运行起来的时候进行一些优化,比如运行速度或者是一些数据上存储的优化,下面则是优化效果的例子。
请观察一下这个有问题的代码。
int main ()
{
int i = 0;
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
for (i = 0; i <= 12 ; i++)
{
arr[i] = 0;
printf("Hello\n");
}
return 0;
}
仔细观察就可以发现,这个代码的问题出在了越界访问,运行之后程序进入了死循环。
为什么会进入死循环?它只是越界访问了不是吗?
解释如下:
但是,在release版本下,这段代码却可以正常的运行。
这个例子其实就为release版本的优化做了一定的说明,release版本下的数据存储是不同于Debug版本的,并且会对数据的存储进行一些优化。
整形在内存中的存储
我们先简单的创建两个整型变量,为其赋值。
int a = 1;
int b = -1;
我们都知道,编译器为这两个变量分配了4个字节,每个字节8个bit位,也就是有32位bit可以为变量存贮数值。
这32位存储的方式是二进制表示的,对于整形来说:数据存放内存中其实存放的是补码
什么是补码?那么就要了解原码,反码,补码的概念。
三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,符号位是最高的那一个bit位。
而数值位正数的原、反、补码都相同。
负整数的三种表示方法各不相同。
原码
直接将数值按照正负数的形式翻译成二进制就可以得到原码。
反码
将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码
反码+1就得到补码。
为什么内存里存放的是补码呢?
因为CPU里只有加法器,使用补码可以实现加法和减法而不需要额外的运算电路。
大小端字节序
在介绍大小端字节序之前,我们可能有一个小疑问如下:
内存里的数据用16进制表示出来的时候为什么是倒着存放的?
大端【字节序】存储:
把一个数据的低位字节序存放与内存的高地址处,把高位字节序存放在低地址处,就是大端字节序。
反之,就是小端字节序。
小端字节序则相反,VS里的存储就是小端字节序
比如1,正常的16进制表示是0x 00 00 00 01在内存里就会变成0x 01 00 00 00
大端字节序其实也就是我们平常默认的写法,从左到右依次写,地址由低处指向高处
小端字节序也就是我们在VS中看到的显示方式了,它是倒着存放的。
所以,有这样一道题,请判断这个机器的环境是小端字节序还是大端字节序?
#include
int check_sys()
{
int i = 1;
return (*(char *)&i);
}
int main()
{
int ret = check_sys();
if(ret == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0
}
其核心代码是这样的:
声明一个整型1,它的16进制在大端字节序下是:0x 00 00 00 01
在小端字节序下是:0x 01 00 00 00
一个整型指针里存放的地址可以访问4个字节,也就是这四个字节即00 00 00 01都可以取出来,要判断是大端小端,我们只需要取出第一个字节,看看是0是1就知道了
那么,我们利用字符类型指针只能访问一个字节的特点来定向取出第一个字节进行判断:
*p=&a---->取出地址,存入*p
强制类型转换成字符类型:(char*)*p = &a
现在存的还只是个访问了一个字节的地址而已,还是要解引用的:
*(char*)*p = &a;
判定返回值即可。
阶段与整型提升细述:
有时候,数据的打印会因为与数据的类型不匹配或者是数据超出范围而发生数据的截断与整型提升,下面我们借由几道有些刁钻的题目来更好的了解这一过程。
有如下一串代码:
int main ()
{
char a = -1;
signed char b = -1;
unsigned char c = -1;
printf ("a=%d,b=%d,c=%d",a,b,c);
return 0;
}
请问打印结果是什么?
答案是-1,-1,255
从数据取值范围来看,有符号的char的类型取值范围是-128~127,容纳下-1合情合理,但是注意,打印的时候我们要求其以整型进行打印,此时发生了整型提升,其过程如下:
对于第一个char来讲,一个整型有整整32位,这还是个负数,它自己只能存8位,那么就只能截断了
也就是变成:11111111 存放于char里面。
这个时候,我们又要求其以整型进行打印,这个时候需要整型提升,整型提升时,需要查看原本的类型到底是什么,char是个有符号的类型,这个8位没法够到32位,11111111整型提升,符号位为1则补1
补到32位为止。
1111111
整型提升:以符号位扩充至32位
11111111111111111111111111111111
目前的这个形式只是补码而已,我们还要计算以下其原码才知道它到底是个什么数
那么,对于负数来说,转换成原码可以先全部除符号位取反,然后+1
则:
1111111111111111111111111111
1000000000000000000000000000 取反
1000000000000000000000000001 +1
打印出来就还是-1
signed char 也是一样的。
unsigned char 则有些不同
还是同上的步骤,先截断得到:11111111
但是,整型提升时,这个类型是一个无符号类型,此时整型提升补的就是0,补到32位
00000000000000000000000011111111
现在这就是个正数了,不在需要求原码,因为正数正反补相同
转换为10进制输出,得到255.
再来一个:
int main()
{
unsigned char a = 200;
unsigned char b = 100;
unsigned char c = 0;
c = a + b;
printf(“%d %d”, a+b,c);
return 0;
}
答案是300,44.
这玩意为啥输出300,44?
printf在传入参数的时候如果是整形会默认传入四字节,所以a+b的结果是用一个四字节的整数接收的,不会越界。而c已经在c = a + b这一步中丢弃了最高位的1,所以只能是300-256得到的44了。
※由于printf是可变参数的函数,所以后面参数的类型是未知的,所以甭管你传入的是什么类型,printf只会根据类型的不同将用两种不同的长度存储。其中8字节的只有long long、float和double(注意float会处理成double再传入),其他类型都是4字节。所以虽然a + b的类型是char,实际接收时还是用一个四字节整数接收的。另外,读取时,%lld、%llx等整型方式和%f、%lf等浮点型方式读8字节,其他读4字节。
再来一个!
int main()
{
char a[1000] = {0};
int i=0;
for(i=0; i<1000; i++)
{
a[i] = -1-i;
}
printf("%d",strlen(a));
return 0;
}
答案是255
这玩意为啥输出255?
首先不难看出来,这玩意的for循环从0开始不断自增直到999,对于数组里的数来讲则是从-1到-1000
而strlen则是计算遇到'\0'之前的字符量,而\0本身的数值就是0,这段需要打印的本质则是这个数组0之前有几个数字。
但这个数组本身是个char类型的数组,对于char类型的数组来讲,它本身所能存储的数据大小是-128 - 127
为什么?char类型本身只能存放1个字节,即8bit位0000000
这样的话,对于char类型的数据,不管怎么增大。都只会因为截断的存在而无法打破存放大小的限制。
那么,根据如上的这个圈圈,这个数组从-1开始不断-1,到达0之前有128+127个数字,strlen就会计数到255停止,所以本题输出255.
浮点数在内存中的存储:
任意一个二进制浮点数V可以表示成以下形式:
(-1)^S*M*2^E
(-1)^S表示符号位,当S=0,V为正数,当S=1时,V为负数
M表示有效数字,大于等于1,小于2
2^E表示指数位
举例来说:
十进制的5.0,写成二进制是 101.0 ,相当于 1.01×2^2 。
那么,按照上面V的格式,可以得出S=0,M=1.01,E=2。
十进制的-5.0,写成二进制是 -101.0 ,相当于 -1.01×2^2 。那么,S=1,M=1.01,E=2。