目录
前言
1. 数据类型详细介绍
2. 整形在内存中的存储:原码、反码、补码
3. 大小端字节序介绍及判断
4. 浮点型在内存中的存储解析
前言
大家好,今天这篇文章来深入剖析一下数据在内存中的存储,包括整型和浮点型,中间会穿插一些相关内容的例题来帮助理解、巩固。
1. 数据类型详细介绍
首先,C语言中的基本数据类型包括:(C99标准也加入了bool类型)
注意C语言中并没有字符串类型。
那么数据类型有什么意义呢?首先,数据类型决定了使用这个类型时我们需要开辟的内存空间大小,如上面的大小(字节)分别为1、2、4、4、8、4、8。其次,数据类型也决定了我们访问该块内存空间时是以什么视角访问的。(如指针的使用和数据的打印等等)
下面我们把这些基本类型进行再分类
整型家族:
一般编译器下,我们写的char short int long 默认为signed char short int long,也就是带符号的整型
浮点数家族:
构造类型:
指针类型:
以及空类型(void),也算老相识了,在函数返回类型,函数参数,指针类型里经常用到。
2. 整形在内存中的存储:原码、反码、补码
在计算机中整数一般有三种表示方法,即原码、反码、补码,这三种方法均包含符号位和数值位两部分,其中最高位为符号位,用0表示+,1表示-
原码:直接按照二进制将十进制转换即可(包含符号)
反码:原码的符号位不变,其他位按位取反
补码:反码+1
正数的原码反码补码相同,负数的则需要按上述进行运算。
对于整型来说数据的存储其实是用的补码,是因为CPU中只有加法器,使用补码可以将符号位和数值域统一进行处理,加减法运算统一处理。此外,原码、补码可以互相转换,运算过程相同,不需要额外的硬件电路。
这里我们给出一个负数的例子,看一下它在内存中是怎么存储的
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a = -10;
return 0;
}
内存窗口中数据以16进制显示
首先a是一个int类型,那就开辟4个字节,32个比特位
-10的原码:10000000 00000000 00000000 00001010
-10的反码:111111111 111111111 111111111 111110101
-10的补码:111111111 111111111 111111111 111110110
16进制: ff ff ff f6
数据看来是一样的,验证了数据存储的是补码,但是为什么数据的存储顺序好像是反着的呢,下面来解决这个问题。
3. 大小端字节序介绍及判断
首先,什么是大小端字节序?
#include <stdio.h>
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; }
首先,假定一个变量i是正数1,原码(补码)为00000000 00000000 00000000 00000001
那么,如果我们只需要访问第一个字节的数据,如果是小端存储,那访问到的就是00000001 ,十进制也就是1,如果是大端存储,那访问到的就是00000000,也就是0,而因为i是一个整型,用整型指针访问时一次会访问4个字节,不管是大端还是小端都会返回1。因此返回的时候强制类型转换把i的地址存到char* 类型中再解地址访问,就可以访问一个字节。
理解了大小端字节序,我们来看几道题目来巩固理解,会先后给出代码和输出结果。分别判断下面的代码输出什么(需要首先知道计算表达式和存储数据的时候用数据的补码,printf打印的时候用原码)
1.
#include <stdio.h>
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; }
首先,上面已经说过,大多数情况下默认char就是signed char 因此a,b的定义是一样的,-1的补码为11111111 11111111 11111111 11111111,存储到char类型中发生截断,因此a,b中存储的数据为11111111,再打印a和b时打印的是%d形式又要发生整型提升(1字节到4字节),补最高符号位到32位,又变成了 11111111 11111111 11111111 11111111,注意这个二进制序列还是补码,打印的时候要还原成原码,补码-1再除符号位按位取反,变成10000000 00000000 00000000 00000001 也就是-1的原码,因此打印-1,那为什么打印c确实255呢 ?因为c在定义的时候类型是无符号char类型,因此-1的补码11111111 11111111 11111111 11111111截断后11111111就会被当成一个正数的补码来存储到c中,打印时发生整型提升,因为无符号,因此整型提升时补0, 00000000 00000000 00000000 11111111,而正数的原码和补码相同,因此这也是c中数据的原码,即十进制255,因此打印后就是255
2.
#include <stdio.h>
int main()
{
char a = -128;
printf("%u\n",a);
return 0; }
-128的补码是11111111 11111111 11111111 10000000,截断后存储到c中的就是10000000
打印时因为a是个有符号char,因此发生整型提升补最高位符号位1,11111111 11111111 11111111 10000000,以%u形式打印,认为内存中存储的是无符号数,那么原码又等于补码,因此就打印11111111 11111111 11111111 10000000对应的十进制数,也就是如图结果,可以用计算器进行验证
3.
#include <stdio.h>
int main()
{
char a = 128;
printf("%u\n",a);
return 0; }
结果和上面一样,这里只是把a变成了+128,128的补码也就是原码为10000000 00000000 00000000 10000000,发生截断,存储在a中的就是10000000,接下来发生和上一题同样的步骤,因此结果也是一样的,因为底层逻辑a中存储的数据是一样的,a数据的类型也都是有符号char
4.
#include <stdio.h>
int main()
{
int i = -20;
unsigned int j = 10;
printf("%d\n", i + j);
return 0;
}
-20的补码:11111111 11111111 11111111 11011000
10的补码:00000000 00000000 00000000 00001010
相加后补码 11111111 11111111 11111111 11110110
转换为原码 10000000 00000000 00000000 00001010 也就是-10
5.
#include <stdio.h>
int main()
{
unsigned int i;
for (i = 9; i >= 0; i--) {
printf("%u\n", i);
}
return 0;
}
首先,死循环是肯定的,因为i是无符号整型,肯定会一直大于等于0进入循环,当i=0时,进入循环,打印0,然后i--变成-1,-1的补码是32位全1,以无符号形式打印就会把-1的补码当成一个无符号数的补码,而无符号数的原码和补码相同,因此打印32位全1对应的十进制
也就是上图的4294967295,此后类推,就会逐步打印上述结果
6.
#include <stdio.h>
int main()
{
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d", strlen(a));
return 0;
}
首先我们知道strlen计算字符串长度遇到\0停止,而数组a是char类型的,也就是遇到一个字节8个比特位全为0的时候就停止计算长度,-1的补码,老生常谈了,32位全1,i是一个整型,对应的数据的二进制补码也是32位的,而数组a中的元素是char类型的,存储时发生截断,留下-1-i对应的低八位的补码,也就是我们要找的数字i就变成了补码低八位全为1,能和-1的补码相减后变成0的i,i还是个正数,补码原码相同,也就是要找i对应的二进制的低八位为全1的时候,那不就是00000000 00000000 00000000 11111111对应的i么,也就是255,因此本题答案就是字符串长度为255。
到这里这个题已经可以做出来了,但是我们再来深入剖析一下更深层的东西,来看一下有符号、无符号的char类型的十进制取值范围,下面的二进制序列都是补码,
无符号的char取值范围是0-255
有符号的char取值范围是-128~127,其中10000000对应的就是-128,那上面的题第一次出现低八位为全0的时候是不是就是127+128=255,也是一种思路。
7.
#include <stdio.h>
unsigned char i = 0;
int main()
{
for(i = 0;i<=255;i++)
{
printf("hello world\n");
}
return 0; }
结果应该是死循环,因为上面已经讨论过了,无符号char的取值范围是0-255,循环条件恒成立,因此无限循环
嗨呀累的一批就这几道题写了我好久啊
4. 浮点型在内存中的存储解析
浮点数的存储规则:
根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:
对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
int main()
{
int n = 9;
float *pFloat = (float *)&n;
printf("n的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
*pFloat = 9.0;
printf("num的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
return 0; }
首先n的值是9是肯定没有问题的,当把n的地址存储到pFloat中时,9的原码补码相同,但因为pFloat是float类型,因此存储时:
E为全0,因此打印的时候就是0.000000(默认保留6位小数)
再来看第三个打印结果,9.0转换为二进制是1001.0,(-1)^0*1.001*2^3,存储的时候S就是0,E就是3+127=30,M就是1.001,因此二进制序列为0 10000010 001 0000 0000 0000 0000 0000
这个二进制序列还原成十进制就是1091567616
结束
至此本文所有内容结束,从底层剖析了数据是如何存储的,其中浮点数的存储稍有拓展,好好掌握冲冲冲