c语言进阶部分详解(数据在内存中的存储)

简介: c语言进阶部分详解(数据在内存中的存储)

大家好,今天要进行梳理的内容是数据在内存中的存储相关内容。


在C语言中,数据在内存中的存储是一个非常重要的概念。了解数据在内存中的存储方式可以帮助我们更好地理解程序的执行过程,优化内存使用,提高程序的性能。


一.数据类型介绍

我们经常见到和使用的数据类型如下

image.png

需要注意的是:学习过Java的同学们知道有String(字符串类型),但是c语言没有,我们使用字符数组来代替(char arr [ ]).

而对于上述类型所占字节大小,各位可使用sizeof关键字来进行查看

1.类型的基本归类

整型:


其中char为整型:在C语言中,char类型可以用来表示字符,每个字符都对应一个整数值。例如,字符'A'对应的整数值是65,字符'a'对应的整数值是97。这是因为C语言使用了ASCII编码,将字符映射为整数值


浮点型:


构造类型:

  • 数组类型
  • 结构体类型 struct
  • 枚举类型 enum
  • 联合类型 union

指针类型:

  • int *pi;
  • char *pc;
  • float* pf;
  • void* pv;

空类型:

空类型(void)是一种特殊的类型,表示“无类型”或“没有值”。void类型通常用于以下几个方面:

  1. 函数返回类型:当函数不返回任何值时,可以将其返回类型声明为void。例如,一个不返回值的函数可以定义为void func()
  2. 函数参数类型:当函数不接受任何参数时,可以将其参数列表声明为void。例如,一个不接受参数的函数可以定义为void func(void)
  3. 指针类型:void指针是一种通用指针类型,可以指向任何类型的数据。void指针可以通过强制类型转换(类型转换操作符)转换为其他类型的指针。例如,void* ptr可以指向任何类型的数据。
  4. 函数指针类型:void函数指针可以指向任何类型的函数。例如,void (*funcPtr)()是一个指向不返回值的函数的指针。

需要注意的是,void类型的变量不能直接声明和初始化,因为它没有具体的值。它只能用于函数返回类型、函数参数类型或指针类型的声明


二.整形在内存中的存储

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

而且整型数据在内存中的存储方式涉及到原码、反码和补码的概念

 

1 原码、反码、补码

计算机中的整数有三种2进制表示方法,即原码、反码和补码。

三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位

正数的原、反、补码都相同
负整数的三种表示方法各不相同、

  • 原码:原码是整数的二进制表示形式,最高位表示符号位,0表示正数,1表示负数。例如,+5的原码为00000101,-5的原码为10000101
  • 反码:反码是对原码按位取反(符号位除外)得到的结果。正数的反码与原码相同,负数的反码是将原码中的1变为0,0变为1。例如,+5的反码为00000101,-5的反码为11111010
  • 补码:补码是对反码加1得到的结果。正数的补码与原码相同,负数的补码是将反码中的1加1。例如,+5的补码为00000101,-5的补码为11111011

对于整型来说,数据存放内存中其实存放的是补码

因为:计算机使用补码表示有符号整数可以解决0的表示问题以及减法运算的简化,原码和反码没有办法解决多个0的问题(10000000和00000000均可表示0)


2. 大小端介绍

大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中

小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存高地址中。


这里我们可以看到,位于数据低位的44在地址里也是低位,所以VS019是小端存储

3.利用代码判断大小端

我们当然也可以写一段代码来判断大小端

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;
}


  1. 在函数check_sys()中,定义了一个整型变量i并初始化为1。
  2. 然后使用类型转换,将i的地址强制转换为char类型的指针,并通过解引用操作符*访问该地址所指向的字节值(即内存中最低处的值)。
  3. 返回该字节值,即返回i的最低有效字节。
  4. main()函数中,调用check_sys()函数,并将返回值赋给变量ret
  5. 使用条件语句判断ret的值,如果等于1,则输出"小端",表示当前系统是小端序;如果等于0输出"大端",表示当前系统是大端序

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

 

1.浮点数存储规则

根据国际标准IEEE,任意一个二进制浮点数V可以表示成下面的形式:

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


eg:

十进制的5.0,写成二进制是 101.0 ,相当于 1.01×2^2

按照上面V的格式,可以得出S=0,M=1.01,E=2

十进制的-5.0,写成二进制是 -101.0 ,相当于 -1.01×2^2 。那么,S=1,M=1.01,E=2



对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M


对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M


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

1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中xxxxxx表示小数部分。

IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字


以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字

而对于E的情况就更加复杂了

  • E为一个无符号整数(unsigned int)
  • E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047
  • 但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数
  • 对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001

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

  1. 全为0

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

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

0的很小的数字

    2.全为1

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

   3.不全为1或0

这时,浮点数就采用下面的规则表示,即指数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,则其二进

制表示形式为:  0 01111110 00000000000000000000000

 


2.练习

1. int main(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的补码是00000000000000000000000000001001

从float指针的角度来看:0 00000000 00000000000000000001001  其中S=0,E=1-127,M=00000000000000000001001,符合上面E全是0的情况,结果非常非常小,所以是0.000000

第三个结果

9.0即是1001.0(二进制后几位)-> 1.001*2^3-> S=0   E=3+127   M=1.001


M的有效位是001,后面要补全20个0


最终二进制的呈现:0 10000010 001 0000 0000 0000 0000 000(刚好就是1091567616)


目录
相关文章
|
2月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
|
27天前
|
C语言
C语言程序设计核心详解 第二章:数据与数据类型 4种常量详解 常见表达式详解
本文详细介绍了C语言中的数据与数据类型,包括常量、变量、表达式和函数等内容。常量分为整型、实型、字符型和字符串常量,其中整型常量有十进制、八进制和十六进制三种形式;实型常量包括小数和指数形式;字符型常量涵盖常规字符、转义字符及八进制、十六进制形式;字符串常量由双引号括起。变量遵循先定义后使用的规则,并需遵守命名规范。函数分为标准函数和自定义函数,如`sqrt()`和`abs()`。表达式涉及算术、赋值、自增自减和逗号运算符等,需注意运算符的优先级和结合性。文章还介绍了强制类型转换及隐式转换的概念。
|
5天前
|
缓存 NoSQL 算法
14)Redis 在内存用完时会怎么办?如何处理已过期的数据?
14)Redis 在内存用完时会怎么办?如何处理已过期的数据?
13 0
|
7天前
|
存储 弹性计算 网络协议
阿里云ECS内存型实例规格族特点、适用场景、指标数据参考
阿里云ECS提供了多样化的内存型实例规格族,专为需要高性能内存资源的应用场景设计。从最新的r8a系列到经过优化的re6p系列,旨在提供稳定、高效且安全的计算环境。这些实例不仅具备强大的计算性能与内存配比,还通过支持ESSD云盘和高效网络协议,显著提升了存储I/O能力和网络带宽,适用于大数据分析、高性能数据库、内存密集型应用等多种场景,为用户带来卓越的计算体验。本文将详细解析阿里云ECS中的多个内存型实例规格族,包括它们的核心特点、适用场景、实例规格及具体指标数据,为用户在云计算资源选型时提供参考。
|
1月前
|
缓存 Linux C语言
C语言 多进程编程(六)共享内存
本文介绍了Linux系统下的多进程通信机制——共享内存的使用方法。首先详细讲解了如何通过`shmget()`函数创建共享内存,并提供了示例代码。接着介绍了如何利用`shmctl()`函数删除共享内存。随后,文章解释了共享内存映射的概念及其实现方法,包括使用`shmat()`函数进行映射以及使用`shmdt()`函数解除映射,并给出了相应的示例代码。最后,展示了如何在共享内存中读写数据的具体操作流程。
|
2月前
|
存储 监控 Docker
如何限制docker使用的cpu,内存,存储
如何限制docker使用的cpu,内存,存储
|
2月前
|
C语言
【C语言】在限制定条件下数据移动
【C语言】在限制定条件下数据移动
29 1
|
2月前
|
存储 NoSQL 程序员
C语言中的内存布局
C语言中的内存布局
38 0
|
2月前
|
C语言
【C语言篇】字符和字符串以及内存函数详细介绍与模拟实现(下篇)
perror函数打印完参数部分的字符串后,再打印⼀个冒号和⼀个空格,再打印错误信息。
|
2月前
|
存储 安全 编译器
【C语言篇】字符和字符串以及内存函数的详细介绍与模拟实现(上篇)
当然可以用scanf和printf输入输出,这里在之前【C语言篇】scanf和printf万字超详细介绍(基本加拓展用法)已经讲过了,这里就不再赘述,主要介绍只针对字符的函数.

热门文章

最新文章

下一篇
无影云桌面