C语言中数据的存储
一、类型的基本归类
1、整型
char
unsigned char (0-255)
signed char (-128-127)
short
unsigned short [int] (0-65535)
signed short [int] (-32768-32767)
int
unsigned int (0-65535)
signed int (-32768-32767)
long
unsigned long [int] (0-42亿)
signed long [int]
2.浮点形
float
double
3.构造类型(自定义类型)
> 数组类型
> 结构体类型 struct
> 枚举类型 enum
> 联合类型 union
4.指针类型
int *pi;
char *pc;
float* pf;
void* pv;
5.空类型
void 表示空类型(无类型)
通常应用于函数的返回类型、函数的参数、指针类型。
二.整型在内存中如何存储?
1.原码
原码是指一个二进制数左边加上符号位后所得到的码,且当二进制数大于0时,符号位为0;二进制数小于0时,符号位为1;二进制数等于0时,符号位可以为0或1(+0/-0)。
2.反码
反码是一种在计算机中数的机器码表示。对于单个数值(二进制的 0 和 1)而言,对其进行取反操作就是将 0 变为 1,1 变为 0。正数的反码和原码一样,负数的反码就是在原码的基础上符号位保持不变,其他位取反。
3.补码
补码是一种用二进制表示有符号数的方法。正数和 0 的补码就是该数字本身。负数的补码则是将其对应正数按位取反再加 1。
tips:正数的原码、反码、补码都相等
对于整形来说:数据存放内存中其实存放的是**补码****
why?
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理; 同
时,加法和减法也可以统一处理(CPU****只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需
要额外的硬件电路。
4.高位扩展
-
符号扩展:对于有符号数,扩展存储位数的方法。在新的高位字节使用当前最高有效位即符号位的值进行填充。
-
零扩展:对于无符号数,扩展存储位数的方法。在新的高位直接填0.
三.大端小端
1.定义
大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lWYeIdZZ-1621127561655)(C:\Users\14275\AppData\Roaming\Typora\typora-user-images\image-20210510182513049.png)]
小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1CE7tOpP-1621127561656)(C:\Users\14275\AppData\Roaming\Typora\typora-user-images\image-20210510182621445.png)]
2.为什么有大端和小端?
因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
例如一个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为高字节, 0x22为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中, 0x22 放在高地址中,即 0x0011 中。小端模式,刚好相反。我们常用的 X86 结构是小端模式,而 KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。
3.如何判断当前机器的字节序?
#include <stdio.h>
int judgement();
/**
* @author: 张翊
* @TIME: 2021/5/9 14:42
* @Description: 判断一个机器是小端字节序还是大端字节序
*/
int main()
{
int result = judgement();
if (result)
{
printf("小端");
}
else
{
printf("大端");
}
return 0;
}
int judgement()
{
int a = 1;
// char *b = (char *) &a;
// if (*b == 1)
// {
// return 1;
// }
// else
// {
// return 0;
// }
//简化1:
// if (*(char *) &a)
// {
// return 1;
// }
// else
// {
// return 0;
// }
//简化2
return (*(char *) &a);
}
四、浮点型在内存中的存储
1.概念
IEEE二进制浮点数算术标准(IEEE 754)是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用。主要规范如下:
这个公式中:
1、s(Sign):符号,表示是正数还是负数。0正数,1负数
2、E(Exponent):指数域,E是2的指数,类似于科学计数法中(a×10^n)中的n,如此E是负数就可以表示小数了。不过这里E是一个无符号整数,32位的时候,E的占用8位,取值范围0-255,存储E的时候我们称为e,它是E的值与一个固定值(32位的情况是127)的和(e=E+bias,E=e-bias,bias=127)。
3、M(Mantissa):尾数域,浮点数具体的数值. 1≤M<2,隐含的以1开头表示,M表示的二进制表达式为1.xxx…,第一位总是1,因此IEEE 754规定,保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。
其结构如下:
阶符± | 阶码e | 数符± | 尾数m |
---|
例:
1、转换二进制
3.25 这整数部分3转换为二进制是11,小数部分0.25 转换为二进制为2^-2,也可以按照乘以 2 取整数位的方法:
(1) 0.25 x 2 = 0.5 取整数位 0 得 0.0
(2) 0.5 x 2 = 1 取整数位 1 得 0.01
如此3.25转化为二进制为11.01
2、有效数(Mantissa)
上述规则我们知道,尾数部分最高有效位(即整数字)是1,也就是尾数有一位隐含的二进制有效数字。因此我们转换尾数的时候,始终保持最高位为1(小数点左边),通过二进制科学计数法转换,我们得到11.01=1.101*2^1。
3、IEEE 754 规约形式
通过上述2步,得到(-1)0*1.101*21。我们采用规约形式获取填充3个部分。
s(Sign):正数=0;
E(Exponent):E=1,e=E+bias=1+127=128。此处存储的e,是经过以127作为偏移量调整的。
M(Mantissa):1.101 中,我只获取有效数(舍去整数部分1,只取小数部分)101
**结果:**0 10000000 10100000000000000000000
从wiki上可以看到依据指数是否为0 ,还可以分为一下几种情况
**指数不全为0或者不全为1。**此时为规约形式,如上述的示例。尾数部分,默认整数部分是1,不存储,获取后第一位默认为1。指数部分有偏移量。
**E全为0 。**此时为非规约形式,E=1-127===-126==(1-1023=-1022) 。为何要引入非规则浮点数,当小于的数会下溢为0,对于高精度来说这是不能接受的,而引入不规则浮点数后,小于的数才会下溢为0 。
**E全为1 。**尾数为0,则表示无穷大。非零则表示NaN(浮点数排序这种特殊问题需要处理)
五.整型存储练习
1. 练习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;
}
结果为:a=-1,b=-1,c=255
分析
-1:在内存中的存储(补码)为:11111111 11111111 11111111 11111111
但是char只能存储8位二进制: 11111111
由于打印时为“%d”,所以打印时需要将char提升为int
对于a b,由于是有符号型,提升至整型时需要按照符号位补齐,所以高位补1,
因此存储为(补码)11111111 11111111 11111111 11111111
实际为(原码):10000000 00000000 00000000 00000001
即为:-1
对于c:由于是无符号型,提升至整型时高位需要补0
因此存储(补码)为: 00000000 00000000 00000000 11111111
实际为(原码):00000000 00000000 00000000 11111111
即为:2^8-1=255
2. 练习2
int main()
{
char a = -128;
printf("%u\n", a);//%u 打印无符号整型
return 0;
}
分析
char a = -128;
10000000 00000000 00000000 10000000 原码
11111111 11111111 11111111 01111111 反码
11111111 11111111 11111111 10000000 补码
char 中存储 10000000
整型提升,且原来类型为:有符号char,按照原符号位提升,高位补1
即 11111111 11111111 11111111 10000000
打印时 ,为无符号整型,所以无需求原码,按照内存中数据直接打印,所以结果为:4294967168
3. 练习3
int main()
{
char a = 128;
printf("%u\n", a);
return 0;
}
分析
char a = 128;
00000000 00000000 00000000 10000000 原码
由于为正数,所以原码、反码、补码相同
char中存储 10000000
整型提升,且原来类型为:有符号char,按照原符号位提升,高位补1
即 11111111 11111111 11111111 10000000
打印时 ,为无符号整型,按照内存中数据直接打印,所以结果为:4294967168
4. 练习4
int main()
{
int i = -20;
unsigned int j = 10;
printf("%d\n", i + j);
}
分析
-20
原码:10000000 00000000 00000000 00010100
反码:11111111 11111111 11111111 11101011
补码:11111111 11111111 11111111 1110110010
补码:00000000 00000000 00000000 00001010-20+10
-20(补码) :11111111 11111111 11111111 11101100
10(补码) :00000000 00000000 00000000 00001010
结果(补码):11111111 11111111 11111111 11110110
结果(反码):10000000 00000000 00000000 00001001
结果(原码):10000000 00000000 00000000 00001010因此,结果为:-10
5. 练习5
int main()
{
unsigned int i;
for (i = 9; i >= 0; i--)
{
printf("%u\n", i);
}
}
结果
“unsigned int”值始终介于“0”到“4294967295”范围之间。 循环将无限执行。
分析
由于 i 为无符号整型,所以i>=0
假设i 从 0 减 1 后到 -1
-1内存中存储形式:
原码:10000000 00000000 00000000 00000001
反码:11111111 11111111 11111111 11111110
补码:11111111 11111111 11111111 11111111
但由于为 无符号整型 所以每一位都为有效位,
将直接打印补码表示的二进制数,即4294967295
6. 练习6
int main()
{
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d", strlen(a));
return 0;
}
结果:255
分析
strlen统计为字符串长度,结束标志为**’\0’,其ASCII码值为 0,即只要找到0就停止**
数组a中存储的数据依次为:-1 -2 -3…-128
那么-128下来是-129吗?
假设为-129
原码:10000000 00000000 00000000 10000001
反码:11111111 11111111 11111111 01111110
补码:11111111 11111111 11111111 01111111
由于数组a是char类型数组,只能存储 01111111
其符号位为 0 所以为正数
其结果为127
因此数组a中存储数据依次为:-1 -2 -3 …-127 -128 127 126 125 …2 1 0数字0之前刚好为255(127+128)个元素,所以结果为255
7. 练习7
unsigned char i = 0;
int main()
{
for (i = 0; i <= 255; i++)
{
printf("hello world\n");
}
return 0;
}
结果
hello world 死循环
分析
由于 i 为无符号char型,其范围为0-255,恒<=255,所以死循环
255 原码:00000000 00000000 00000000 11111111
256 原码:00000000 00000000 00000001 00000000
由于char只能存储1个字节,所以存储为:00000000,即 0
因此:i变换:0 1 2 3…254 255 0 1 2 3
0之前刚好为255(127+128)个元素,所以结果为255
7. 练习7
unsigned char i = 0;
int main()
{
for (i = 0; i <= 255; i++)
{
printf("hello world\n");
}
return 0;
}
结果
hello world 死循环
分析
由于 i 为无符号char型,其范围为0-255,恒<=255,所以死循环
255 原码:00000000 00000000 00000000 11111111
256 原码:00000000 00000000 00000001 00000000
由于char只能存储1个字节,所以存储为:00000000,即 0
因此:i变换:0 1 2 3…254 255 0 1 2 3