C语言中数据的存储

C语言中数据的存储

一、类型的基本归类

1、整型

char

 	unsigned char0-255signed char-128-127short

	 unsigned short [int]0-65535signed short [int]-32768-32767int

 	unsigned int0-65535signed int-32768-32767long

 	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.高位扩展

  1. 符号扩展:对于有符号数,扩展存储位数的方法。在新的高位字节使用当前最高有效位即符号位的值进行填充。

  2. 零扩展:对于无符号数,扩展存储位数的方法。在新的高位直接填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 11101100

10
补码: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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值