数据在内存中的存储
🕯️前言
这一章节的我们主要探究数组在内存中如何进行存储、运算,还是挺简单的,那我们就开始吧⌨️
1. 整形
1.1.整形家族
- 整形家族的类型有有符号和无符号的区分
char (字符存储的是ASCII码值,是整形)
unsigned char
signed char
short
unsigned short [int]
signed short [int]
int
unsigned int
signed int
long
unsigned long [int]
signed long [int]
long long
整形表示的范围在<limits.h>
中定义:
#define SHRT_MIN (-32768)
#define SHRT_MAX 32767
#define USHRT_MAX 0xffff
#define INT_MIN (-2147483647 - 1)
#define INT_MAX 2147483647
#define UINT_MAX 0xffffffff
#define LONG_MIN (-2147483647L - 1)
#define LONG_MAX 2147483647L
#define ULONG_MAX 0xffffffffUL
#define LLONG_MAX 9223372036854775807i64
#define LLONG_MIN (-9223372036854775807i64 - 1)
#define ULLONG_MAX 0xffffffffffffffffui64
- 从中可以看出,对于
short、int、long、longlong
类型,VS中都默认为singned
- 比较特殊的是char类型,<limit.h>中是这样定义的
#define SCHAR_MIN (-128)
#define SCHAR_MAX 127
#define UCHAR_MAX 0xff
#ifndef _CHAR_UNSIGNED
#define CHAR_MIN SCHAR_MIN
#define CHAR_MAX SCHAR_MAX
#else
#define CHAR_MIN 0
#define CHAR_MAX UCHAR_MAX
#endif
- 其中
#ifndef _CHAR_UNSIGNED
说明宏_CHAR_UNSIGNED
不存在 则编译#ifndef
到#endif
直接的代码
为什么会有这种定义呢?
- ANSI C 提供了3种字符类型,分别是
char、signed char、unsigned char
。而不是像short、int一样只有两种(int默认就是unsigned int).
C标准中对char是Impementation Defined
,即未明确定义,不同的编译器对char有不同定义
VS编译器、x86上的GCC定义char为signed char
而arm-linux-gcc却把char定义为unsigned char
2.2. 整形在内存中的存储
2.2.1. 原码、反码、补码
计算机中的整数有三种2进制表示方法,即
原码、反码和补码
三种表示方法均由符号位
和数值位
两部分,符号位用0表示“正”,用1表示“负”
数值位:
正数的原、反、补码都相同
负整数的三种表示方法各不相同
通过补码获得原码可以-1后取反,也可以取反后+1
原码
直接将数值按照正负数的形式翻译成二进制就可以得到原码。
反码
将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码
反码+1就得到补码。
举个栗子:
- 对于整形来说:数据存放内存中其实存放的是
补码
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统 一处理;
同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程 是相同的,不需要额外的硬件电路。
cpu中只有加法器,那怎么计算相减呢
我们以1-1为例,实际计算的是1-(-1)
1+(-1)
00000000 00000000 00000000 00000001 --> 1的补码
11111111 11111111 11111111 11111111 --> -1的补码
//相加
00000000 00000000 00000000 00000000 --> 等于0
2.2.2. 大小端
为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元 都对应着一个字节,一个字节为8 bit。但是在C语言中除了8 bit的char之外,还有16 bit的short 型,32 bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32 位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就出现了大端存储模式和小端存储模式。
字节序-以字节为单位讨论存储顺序
小端字节序存储:把一个数据的低位字节
的内容存放在低地址处
,高位字节
内容存放在高地址处
大端字节序存储:把一个数据的低位字节
的内容存放在高地址处
,高位字节
内容存放在低地址处
- 如何判断当前环境下的大小端?
我们定义一个int类型的数据,初始化为1
那么它在内存中的存储有以下两种情况
而后我们将其强制类型转换为char类型,打印
由此可以看出,如果打印1,就是小端存储;打印0,就是大端存储
int main()
{
int i = 1;
if(*(char *)&i == 1)
printf("小端\n");
else
printf("大端\n");
return 0;
}
2.2.3. 几道练习题
下面的几个程序输出什么?
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;
}
答案是:a=-1,b=-1,c=255
是不是有点怪?
-
虽然我们初始化的都是-1,但是abc的数据类型不同,我们打印时按照
%d
的格式,即十进制的形式打印有符号整数
,所以才会有这样的差异 -
-1的原码、反码、补码分别是
1000000 00000000 0000000 0000001
11111111 111111111 11111111 11111110
11111111 111111111 11111111 11111111 -
而要存放在char类型的数据中,char类型只有8个字节,所以会发生截断
11111111 -a、b、c、中存储的补码 -
前面我们说过VS编译器定义char为
signed char
以%d:十进制的形式打印有符号整数
打印char类型的数据,会进行整形提升
对于a和b来说都是signed char
,整形提升符号位为1,高位补1
11111111111111111111111111111111 –a、b整形提升后的补码
所以a、b打印的是-1
-
而c是
unsigned char
,高位补0
00000000000000000000000011111111 –c整形提升后的补码
所以c打印的为255
#include <stdio.h>
int main()
{
char a = -128;
printf("%u\n",a);
return 0;
}
答案是:4294967168
是不是比上一道更离谱了🤣
-
-128的原码、反码、补码:
10000000 00000000 00000000 10000000
111111111 111111111 111111111 011111111
111111111 111111111 111111111 10000000 -
存放在char类型的数据中,发生截断
10000000 -
以
%u:无符号十进制整数
打印,a是signed char
,高位补符号位,整形提升
111111111 111111111 111111111 10000000 –补码
以无符号形式打印,所以补码也是原码,对应结果就是4294967168
unsigned char i;
for(i = 9; i >= 0; i--)
{
printf("%u ",i);
}
通过前面两题相信大家已经能猜到这种情况了
- i是无符号的整形,不会小于0,死循环
那为什么等于0后继续减1得到的是255呢?
- 借助这两张图,我们就能快速理解char的数据会在什么时候发生变化了
2. 浮点型数据
常见的浮点数:
3.14159
1E10
浮点数家族包括: float、double、long double 类型。
浮点数表示的范围:float.h中定义
2.1. 浮点数存储规则
根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:
(-1)^S * M * 2^E
- (-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数。
M表示有效数字,大于等于1,小于2。
2^E表示指数位。
对于32位的浮点数,最高的1位是符号位S,接着的8位是指数E,剩下的23位为有效数字M。
对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
指数E从内存中取出还可以再分成三种情况
- E不全为0或不全为1
这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将 有效数字M前加上第一位的1。 比如: 0.5(1/2)的二进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位,则为 1.0*2^(-1),其阶码为-1+127=126,表示为 01111110,而尾数1.0去掉整数部分为0,补齐0到23位00000000000000000000000,则其二进 制表示形式为:
- E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值, 有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于 0的很小的数字。
- E全为1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)
2.2. 浮点数存储的例子
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;
}
我们定义一个int类型的变量n并初始化为9
再将其取地址,强制类型转化成 一个folat* 类型的指针pFloat
分别打印n的值和pFloat指向的值
此时打印的结果:
n的值为:9
*pFloat的值为:0.000000
- n在内存中的存储:
0 00000000 00000000000000000001001 - 对应float类型的S E M
0 00000000 00000000000000000001001
S E M
0 -126 0.00000000000000000001001 - 表示的浮点数即为:
(-1)^0 * 0.00000000000000000001001 * 2^-126 - E在内存中是全0,所以表示的就是±0
而后我们赋值*pFloat = 9.0;
此时打印的结果:
n的值为:1091567616
*pFloat的值为:9.000000
- 浮点数形式表示9
1001.0
1.001 * 2^3 - 对应的S E M:
(-1)^0 * 1.001 * 2^3
S=0 E=3 M=1.001 - *pFloat在内存中的存储:
0 10000010 00100000000000000000000
🗝️总结
- 这样的话大家是不是就对程序内存中的运行过程理解更深了呢😎
本节完~~,如果你在实现过程中遇到任何问题,欢迎在评论区指出或者私信我!💕 |
新人博主创作不易,如果有收获可否👍点赞✍评论⭐收藏一下?O(∩_∩)O
THANKS FOR WATCHING |