C语言进阶之数据在内存中的存储

1.数据类型详细介绍

前面我们已经学习了基本的内置类型以及它们所占存储空间的大小:

char                     //字符数据类型       1个字节

short                   //短整型                   2个字节

int                        //整型                      4个字节

long                    //长整型                   4 / 8个字节

long long            //更长的整型            8个字节

float                    //单精度浮点型        4个字节   

double               //双精度浮点型         8个字节

 注意:C语言只规定了sizeof ( long ) >= sizeof ( int )

类型的意义:

  1. 使用这个类型开辟内存空间的大小(大小决定了使用范围)。
  2. 如何看待内存空间的视角。

类型的基本归类

整型家族:

char  

     signed  char

     unsigned  char

short

     signed  short  [ int ]

     unsigned  short  [ int ]

int

     signed  int

     unsigned  int

long

     signed  long  [ int ]

     unsigned  long  [ int ]

long long

     signed  long  long  [ int ]

     unsigned  long  long  [ int ]

注意:字符在内存中本质上存的是ASCLL码值,因此char归到整型家族。

浮点数家族:

float

double

构造类型(自定义类型):

>  数组类型

>  结构体类型   struct

>  枚举类型       enum

>  联合类型       union

 指针类型:

int * p;

char * p;

float * p;

void * p; 

结构体指针

空类型 :

void 表示空类型( 无类型 );

通常应用于函数的返回类型、函数的参数、指针类型。

2.整型在内存中的存储

一个变量的创建是要在内存中开辟空间的。空间的大小是根据不同的类型而决定的。

int main()
{
	int num = 0;//创建一个整型变量,叫num
	//num向内存申请4个字节来存放数据
	return 0;
}

2.1原码、反码、补码

计算机中的整数(整数的存储类型是整型,1个整型是4个字节,32个比特位。)有三种2进制表示方法,即原码、反码、补码。

三种表示方法均有符号位和数值位两部分。第一位是符号位,1代表负数,0代表正数。剩下的就是数值位。

1.正整数的原码、反码、补码是相同的

       原码:根据正整数直接写出二进制序列

2.负整数的原码、反码、补码需要计算

       原码:根据正整数直接写出二进制序列

       反码:符号位不变,其他位按位取反

       补码:反码+1

 对于整型来说:数据在内存中是以二进制补码的形式进行存储的。

原因如下:

在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值位统一处理。

同时,加法和减法也可以统一处理(CPU只有加法器)。此外,补码和原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

2.2大小端字节序介绍及判断

字节序:以字节为单位,讨论存储顺序的

大端(存储)模式:指把一个数据的低位字节的内容保存在内存的高地址中,而数据的高位字节的内容保存在内存的低地址中;

小端(存储)模式:指把一个数据的高位字节的内容保存在内存的高地址中,而数据的低位字节的内容保存在内存的低地址中。

为什么会有大小端之分?

因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应一个字节,一个字节为8个比特位。但是在C语言中,除了8比特位的char,还有16比特位的int,32比特位的short等。对于这些位数大于8位的处理器,比如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题,也就导致了大端存储模式和小端存储模式。

举例说明大端存储模式和小端存储模式:

// 0x11223344   0x表示十六进制数字

//二进制转换为十六进制:每四位二进制序列组成一个十六进制数字
//一个字节八个比特位,八位二进制数字
//即11占一个字节,22占一个字节……

//类比十进制数字的个位十位百位
//可得到十六进制的低位就是44,高位是11

//大端字节序存储:
//11 22 33 44

//小端字节序存储:
//44 33 22 11

例题:写一个程序来判断当前机器的字节序

int main()
{
	int a = 0x11223344;
	char* p = (char* )&a;//强制类型转换
	if (*p == 44)
		printf("小端");
	else
		printf("大端");
	return 0; 
}

在进行练习之前,我们先来复习一下整型提升

int main()
{
	//负数的整型提升
	char c1 = -1;
	//变量c1的二进制(补码)中只有8个比特位
	//11111111
	//因为char是有符号的char
	//因此整型提升的时候,高位补充符号位,即为1
	//提升后的结果就是
	//11111111111111111111111111111111
 
	//正数的整型提升
	char c2 = 1;
	//变量c2的二进制(补码)中只有8个比特位
	//00000001
	//因为char是有符号的char
	//因此整型提升的时候,高位补充符号位,即为0
	//提升后的结果就是
	//00000000000000000000000000000001
 
	//无符号整型提升,高位补0
 
	return 0;
}

 练习题一:

#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 a = -1;
	//原码:10000000000000000000000000000001
	//反码:11111111111111111111111111111110
	//补码:11111111111111111111111111111111
	//截断:11111111(补码)

	signed char b = -1;
	//原码:10000000000000000000000000000001
	//反码:11111111111111111111111111111110
	//补码:11111111111111111111111111111111
	//截断:11111111(补码)

	unsigned char c = -1;
	//原码:10000000000000000000000000000001
	//反码:11111111111111111111111111111110
	//补码:11111111111111111111111111111111
	//截断:11111111(补码)

	printf("a = %d,b = %d,c = %d", a, b, c);
	//%d——以十进制的形式打印有符号的整数
	//要发生整型提升
	//a : 11111111111111111111111111111111(补码)需要转换为原码打印 -> -1
	//b : 11111111111111111111111111111111(补码)需要转换为原码打印 -> -1
	//c : 00000000000000000000000011111111(补码)需要转换为原码打印 ->255

练习题二:

#include<stdio.h>
int main()
{
	char a = -128;
	printf("%u\n", a);
	return 0;
}

请问程序输出的结果是什么?

运行结果:

解释如下:

    char a = -128;
	//10000000000000000000000010000000(原码)
	//11111111111111111111111101111111(反码)
	//11111111111111111111111110000000(补码)
	//截断:10000000
	 
	printf("%u\n", a);
	//%u以十进制的形式打印无符号的整数
	//整型提升:
	//11111111111111111111111110000000(补码)
	//char 类型最高位是符号位,所以整型提升的时候补的是1
	//易错点:无符号位的数字相当于正整数,补码就是它的原码

练习题三: 

#include<stdio.h>
int main()
{
	char a = 128;
	printf("%u\n", a);
	return 0;
}

运行结果:

 解释如下:

    char a = 128;
	//000000000000000000000000010000000(原码、反码、补码均相同)
	//截断:10000000

	printf("%u\n", a);
	//%u以十进制的形式打印无符号的整数
	//整型提升:
	//11111111111111111111111110000000(补码)
	//char 类型最高位是符号位,所以整型提升补的是1
	//易错点:无符号位的数字相当于正整数,补码就是它的原码

练习题四:

#include<stdio.h>
int main()
{
	int i = -20;
	unsigned int j = 10;
	printf("%d\n", i + j);
	return 0;
}

 运行结果:

解释如下:

    int i = -20;
	//10000000000000000000000000010100
	//11111111111111111111111111101011
	//11111111111111111111111111101100

	unsigned int j = 10;
	//00000000000000000000000000001010
	printf("%d\n", i + j);
	//11111111111111111111111111101100
	//00000000000000000000000000001010
	//11111111111111111111111111110110(补码)
	//11111111111111111111111111110101(反码)
	//10000000000000000000000000001010(原码) -> -10

练习题五: 

#include<stdio.h>

int main()
{
	unsigned int i;
	for (i = 9; i >= 0; i--)
	{
		printf("%u\n", i);
	}
    return 0;
}

运行结果:陷入死循环

解释如下:

    unsigned int i;
	for (i = 9; i >= 0; i--)
	{
		printf("%u\n", i);
	}
	//%u——以十进制的形式打印无符号的整数dang
	//i = 9—— i = 0 
	//会打印9、8、7、6、5、4、3、2、1、0
	//当i = -1 时
	//原码:10000000000000000000000000000001
	//反码:11111111111111111111111111111110
	//补码:11111111111111111111111111111111
	//%u 是无符号的,因此它会将-1的补码打印出来一个特别大的数字,然后递减
	//因此会陷入死循环

练习题六: 

#include<stdio.h>
#include<string.h>

int main()
{
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	printf("%d", strlen(a));
	return 0;
}

运行结果:

解释如下: 

    char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	printf("%d", strlen(a));//strlen 求字符串长度,统计的是\0之前的字符的个数
    //\0 的 ASCLL 是 0 
	//char 类型可存的数字大小是 -128~127

练习题七:

#include<stdio.h>

unsigned char i = 0;
int main()
{
	for (i = 0; i < 255; i++)
	{
		printf("Hello world\n");
	}
	return 0;
}

运行结果:陷入死循环

解释如下:

unsigned char i = 0;//全局变量
//char 类型的范围是 0~255

int main()
{
	for (i = 0; i <= 255; i++)//永远小于255
	{
		printf("Hello world\n");
	}
	return 0;
}

3.浮点型在内存中的存储

常见的浮点数

3.1415926

1E10                        1.0 * 10^10

浮点数家族包括:float 、double 、long double 类型

浮点数表示的范围:在头文件中 <float.h> 有定义

为什么叫浮点数呢?

 浮点数其实就是数学中的小数

3.1415926 == 31.415926 * 10^(-1)

在科学计数法的作用下,小数点是可以浮动的,因此称为浮点数

3.1一个例子 

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

int main()
{
	int n = 9;
	float* pFloat = (float*)&n;
	printf("n的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);

	*pFloat = 9.0;
	printf("n的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);

	return 0;
}

大家猜猜输出结果是什么?我猜大多数人猜想的结果如下:

9

9.000000 

9

9.000000

让我们来运行一下,来验证我们的猜想吧

 这似乎和我们的猜想并不相同,num 和  *pFloat 在内存中明明是同一个数字,为什么浮点数和证书的解读结果会不一样呢?这是为什么呢?接下来,让我们来学习浮点数在内存中的存储规则,来解决我们的疑惑吧。

2.2浮点数在内存中的存储规则

根据国际标准 IEEE (电器和电子工程协会)754 规定,任意一个二进制浮点数 V 可可以表示成下面的形式:

  • (-1)^S * M * 2^E
  • (-1)^S 表示符号位,当 S = 0 时,V 为正数;当 S = 1 时,V 为负数
  • M 表示有效数字,大于等于1,小于2
  • 2^E 表示指数位

 举例如下:

9.0

//二进制序列是 1001.0 

//相当于 1.001 * 2^3

按照 (-1)^S * M * 2^E 的格式

(-1)^0 * 1.001 * 2^3

S = 0

M = 1.001

E = 3

IEEE 754  规定: 

对于 32 位的浮点数 ( float ) ,最高的 1 位是符号位 S ,接着的 11 位是指数 E,剩下的 52 位为有效数字 M,图示如下:

 对于 64 位的浮点数 ( double ) ,最高的 1 位是符号位 S ,接着的 8 位是指数 E,剩下的 52 位为有效数字 M,图示如下:

 

 IEEE 754 对有效数字 M 和指数 E,还有一些特别规定。

有效数字 M :

在计算机内部保存 M 时,由于M 总是大于等于 1 、小于 2,因此数字的第一位总是 1 ,所以可以被舍去,只保存小数点后面的数字。

举例如下:

9.0

//二进制序列是 1001.0 

//相当于 1.001 * 2^3

按照 (-1)^S * M * 2^E 的格式

(-1)^0 * 1.001 * 2^3

S = 0

M = 1.001

E = 3

因此,在计算机内部 M 保存的就是 001

等到读取的时候,再把第一位的 1 加上去。这样做的目的:是节省 1 位有效数字,使精度可以更加准确。

指数 E : 

E 是 一个无符号的整数 ( unsigned int ),意味着:当 E 是 8 位时,它的取值范围是 0 ~ 255 ;当 E 是11 位时,它的取值范围是 0~2047。但是科学计数法中,指数是可以出现负数的。因此,IEEE 754 规定,存入内存的时候,E 的真实值必须再加上一个中间数。对于 8 位的 E ,这个中间数是127;对于 11 位的 E,这个中间数是1023。

举例如下:

9.0

//二进制序列是 1001.0 

//相当于 1.001 * 2^3

按照 (-1)^S * M * 2^E 的格式

(-1)^0 * 1.001 * 2^3

S = 0

M = 1.001

E = 3

//在 32 位的浮点数 ( float ) 中

在保存 E 时,要给 3 + 127 = 130;

130 的二进制序列是 10000010

即在内存中存储的就是 10000010

//在 64 位的浮点数 ( float ) 中

在保存 E 时,要给 3 + 1023 = 1026;

1026 的二进制序列是 10000000010

即在内存中存储的就是 10000000010

证明如下:

在 32 位的浮点数 ( float ) 中

  

在 64 位的浮点数 ( float ) 中

 然而,指数 E 从内存中取出还可以分成三种情况:

1.E 不全为 0 或不全为 1

浮点数采用指数 E 的计算值减去 127(或1023),得到真实值

2. E 全为 0

浮点数的指数 E 等于 1 - 127(或者 1023)即为真实值。

有效数字 M 不再加上第一位的 1 ,而是还原为 0.xxxxx 的小数,这样做是为了表示  ±0,以及接近于 0 的很小的数字。

3.E 全为 1

如果有效数字 M 全为0,表示 ± 无穷大(正负取决于符号位 S)

浮点数在内存中的存储讲解到此为止,接下来我们在返回看刚开始的那个例子:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

int main()
{
	int n = 9;
	float* pFloat = (float*)&n;//强制类型转换为float
	printf("n的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);//*解引用操作符

	*pFloat = 9.0;
	printf("n的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);

	return 0;
}

运行结果: 

解释如下:

	int n = 9;
	//00000000000000000000000000001001

	float* pFloat = (float*)&n;//强制类型转换为float
	
	printf("n的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);//*解引用操作符
	//*pFloat认为9的二进制序列就是浮点数
	//0 00000000 00000000000000000001001
	//S     E              M
	//根据(-1)^S * M * 2^E
	//符号位 S 为 0
	//指数 E 为 -126( E 在内存中全为 0)
	//有效数字 M :0.00000000000000000001001
	//相乘过后数字极小
	//%f只能打印小数点后 6 位
	//因此打印结果就是0.000000


	*pFloat = 9.0;
	//1001.0
	//相当于 1.001 * 2^3
	//(-1)^0 * 1.001 * 2^3
	//S = 0
	//M = 1.001
	//E = 3
	//由前面可知,最后存进内存的二进制序列是
	//01000001000100000000000000000000
	//即2^20+2^24+2^30=1091567616
	printf("n的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);

数据在内存中的存储讲解到此结束,大家有什么疑惑,欢迎在评论区继续和我探讨。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
嵌入式C语言进阶之道,可以涉及以下几个方面: 1. 熟悉硬件平台:嵌入式开发需要对目标硬件平台有深入了解,包括处理器架构、寄存器、外设等。学习并理解硬件的特性和工作原理,能够更好地进行底层编程和优化。 2. 掌握低级编程技巧:嵌入式开发常常需要进行底层编程,因此需要熟悉C语言的底层特性,如指针操作、位操作、内存管理等。掌握这些技巧可以提高代码的效率和可靠性。 3. 理解断和定时器:在嵌入式系统断和定时器是常用的机制,用于实时响应和时间触发的任务。学习如何使用断和定时器,并了解其原理和应用场景,可以实现更加高效和可靠的嵌入式系统。 4. 学习RTOS:实际的嵌入式系统往往需要处理多个任务,并具有实时性要求。学习实时操作系统(RTOS)的使用和原理,可以帮助开发者更好地管理任务、调度和资源。 5. 进行性能优化:嵌入式系统通常资源有限,需要进行性能优化以提高系统的效率和响应速度。了解编译器优化、代码压缩、数据结构选择等技术,可以帮助优化嵌入式系统的性能。 总之,嵌入式C语言进阶之道包括对硬件平台的深入了解、掌握底层编程技巧、理解断和定时器、学习RTOS以及进行性能优化。通过不断学习和实践,可以提升嵌入式开发的能力和水平。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

囚徒玩电脑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值