深入了解C语言的内核--数据在内存中的存储

news/2024/12/21 23:09:02/

前言:新手开始学C语言,首先学习的是语法,在懂语法的基础上,在去思考解决问题的方法。大家应该也听说过c语言是最接近底层的编程语言吧,所以我认为最重要的是要理解C语言的内核--1.栈帧空间的销毁和创建  2.数据在内存中的存储方式。 只有深刻理解这些知识,才能让你不再停留在浅层,让你对C语言的理解更上一层楼。

首先,我们要知道存储在计算机中的数据都是以二进制的方式进行存储的。

 大家可能会问十进制,八进制,十六进制呢?其实大家可以把它理解为一种表达形式就行。比如以二进制的方式表达会很长,我们就以十六进制表达,这样更容易理解。

但是,千万不要认为计算机是以十六进制表达的。它只是一种表达方式。本质上是以二进制的方式存储的。


 整数在内存中的存储

整数在内存中占4个字节,也就是32个比特位。(一个字节=八个比特位),一个比特位就是一个二进制位。不清楚的现在了解一下。

整数的2进制表⽰⽅法有三种,即 原码、反码和补码。

 有符号的整数,三种表⽰⽅法均有符号位和数值位两部分,符号位都是⽤0表⽰“正”,⽤1表 ⽰“负”,最⾼位的⼀位是被当做符号位,剩余的都是数值位

  • 正整数的原、反、补码都相同。
  • 负整数的三种表⽰⽅法各不相同。

原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。

反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。

补码:反码+1就得到补码。

int n = 5;

在内存中的存储方式: 

原码:0 0000000 00000000 00000000 00000101

反码:0 0000000 00000000 00000000 00000101

补码:0 0000000 00000000 00000000 00000101

正数的原码,反码,补码都相同

int n = -5;

原码:1 0000000 00000000 00000000 00000101

反码:1 11111111 111111111 111111111 111111010

补码:1 11111111 111111111 111111111 111111011

对于整形来说:数据存放内存中其实存放的是补码。

在计算机系统中,数值⼀律⽤补码来表⽰和存储。 原因在于,使⽤补码,可以将符号位和数值域统⼀处理; 同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是 相同的,不需要额外的硬件电路。 (大家了解一下就行)

总的来说:

原码是让人看的

补码是给计算机看的


大小端字节序和字节序判断

当我们了解整数在内存中的存储后,我们看一下细节

int main()
{int n = 0x11223344;return 0;
}

 

 首先0x是十六进制的表示形式,一位十六进制位用4位二进制位表示,所以存储没有问题。其次,就像123,3是个位,2是十位,1是百位,这边的11223344也一样,44是最低字节位。内存中是从低地址到高地址,我们可以看到44存在了最低地址处。

 我们可以看到在a中的 0x11223344 这个数字是按照字节为单位,倒着存储的。这是为 什么呢?

这时候就要了解大小端: 

其实超过⼀个字节的数据在内存中存储的时候,就有存储顺序的问题,按照不同的存储顺序,我们分 为⼤端字节序存储⼩端字节序存储,下⾯是具体的概念:

⼤端(存储)模式:

是指数据的低位字节内容保存在内存的⾼地址处,⽽数据的⾼位字节内容,保存在内存的低地址处。

⼩端(存储)模式:

是指数据的低位字节内容保存在内存的低地址处,⽽数据的⾼位字节内容,保存在内存的⾼地址处


当了解大小端之后,我们可以 写一个代码来判断当前机器的字节序:

#include <stdio.h>
int main()
{int n = 1;if (1 == *(char*)&n){printf("小端\n");}else{printf("大端\n");}return 0;
}

 

我们可以知道int n = 1在内存的表达形式0x 00 00 00 01(这边用十六进制给大家展示) ,当&n会把地址取出来,当强制类型转换成char*时解引用只会取到一个字节

如果是小端存储:0x 01 00 00 00,数据的低位字节内容存储在内存的低地址处

如果是大端存储:0x 00 00 00 01,数据的低位字节内容存储在内存的高地址处。

所以这样就能判断机器的大小端。


 练习2

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

大家来思考一下,会打印出什么?

 

 大家答对了吗?

首先我们要知道类型的作用:

1.申请内存空间时的大小

2.类型决定了看待内存中数据的视角

然后: %d--打印有符号的整型,对于char类型的会发生整型提升。

%u --打印无符号的整数。 

最后还要知道:char的取值范围-128-127 ,unsigned char的范围为0-255

char a = -1;
    //原码: 1 0000000 00000000 00000000 00000001
    //反码:1 1111111 11111111 11111111 11111110
    //补码: 1 1111111 11111111 11111111 11111111
    //发生截断 11111111
    //因为char在这个里面是有符合的:补码:11111111 11111111 11111111 11111111
    //反码:1 0000000 00000000 00000000 00000000
    //补码:1 0000000 00000000 00000000 00000001  整型提升 

    signed char b = -1;   //跟上面的char a是一样的
    unsigned char c = -1;
    //原码:1 0000000 00000000 00000000 00000001
    //反码:1 1111111 11111111 11111111 11111110
    //补码:1 1111111 11111111 11111111 11111111
    //发生截断 11111111
    //0 0000000 00000000 00000000 11111111  整型提升
    printf("a=%d,b=%d,c=%d", a, b, c);


练习3

#include <stdio.h>
int main()
{char a = -128;printf("%u\n",a); //%u打印无符号整型return 0;
}

 有了上面例子的铺垫,大家应该心里有数了吧?答案是上面呢?

 

 char a = -128;
    //原码:1 0000000 00000000 00000000 10000000
    //反码:1 1111111 11111111 11111111 01111111
    //补码:1 1111111 11111111 11111111 10000000
    //发生截断:10000000
    //%u打印无符号整型,发生整型提升
    //char在vs2022里面是有符号的
    //11111111 11111111 11111111 10000000
    //然后%u认为是无符号的,直接打印。会是一个很大的数字。
    printf("%u\n", a);

 大家可以多深入了解一下,这块并不是很难。


 练习4

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

如果不理解整数在内存中的存储的话,这块大概是做不对的。这边我给大家深入讲解一下,让大家对C语言的知识更加深刻。

首先,每个类型都有自己的取值范围:char的取值范围-128-127 。那么超过这个数字是怎么存储的呢?

 

首先,我们知道字符类型的只占一个字节 ,所以当最高位为0时,它就是正数。直接的值到127。而1为符号位,是负数需要进行转化,所以到-128。

 

所以我们可以得知它的值的范围始终都在127- -128之间,现在大家知道答案是什么了吧? 

没错就是255. 


 浮点数在内存中的存储

大家肯定听说过浮点数不能精确保存的吧,接下来给大家娓娓道来。

首先,我们需要知道浮点数在计算机内部的表达方式。 

根据国际标准IEEE(电⽓和电⼦⼯程协会) 754,任意⼀个⼆进制浮点数V可以表⽰成下⾯的形式:

 V   =  (−1) ∗^S∗  M ∗ 2E

• (−1)^S 表⽰符号位,当S=0,V为正数;当S=1,V为负数

• M 表⽰有效数字,M是⼤于等于1,⼩于2的

• 2 E 表⽰指数位

 举个栗子:

⼗进制的5.0,写成⼆进制是 101.0 ,相当于 1.01×2^2 。

 

 

大家要知道当 E全为0

这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第⼀位的1,⽽是还 原为0.xxxxxx的⼩数。这样做是为了表⽰±0,以及接近于0的很⼩的数字。

E全为1

这时,如果有效数字M全为0,表⽰±⽆穷⼤(正负取决于符号位s); 

 

int main()
{int n = 9; //占4个字节float* pFloat = (float*) &n;//int*printf("n的值为:%d\n", n); //9printf("*pFloat的值为:%f\n", *pFloat); //0.000000*pFloat = 9.0;printf("n的值为:%d\n", n);//1,091,567,616printf("*pFloat的值为:%f\n", *pFloat);//9.0return 0;
}

 根据上面的知识,大家认为它会打印出什么?

 int n = 9; //占4个字节
    //00000000 00000000 00000000 00001001(原,反,补码相同)
    float* pFloat = (float*) &n;//int*
    //0 00000000 0000000 00000000 00001001
    //由二进制的存储,我们可以得知float的表示形式
    //S = 0   
    //E = 1-127 = -126 ->因为这8个比特位都是0,特殊的情况,然后M表示出来前面不用补1
    //M= 0.0000000 00000000 00001001
    //(-1)^0 * 0.0000000 00000000 00001001 * 2^ -126 //一个趋近于0的数字乘以无限小的数字,结果也趋近于0
    printf("n的值为:%d\n", n); //9
    printf("*pFloat的值为:%f\n", *pFloat); //0.000000

    *pFloat = 9.0;
    //1001.0(二进制)
    //(-1)^0 * 1.001 * 2^2
    //S = 0
    //M = 1.001
    //E = 2  -> 127 + 2 = 129 用这个存在计算机中
    // 0 10000001 00100000 000000000000000
    // 然后%d打印是打印有符号的整数,那么0表示符号位,代表它是一个正数,所以它是一个很大的数字

    printf("n的值为:%d\n", n);//1,091,567,616
    printf("*pFloat的值为:%f\n", *pFloat);//9.0

 希望对大家有所帮助。

 


http://www.ppmy.cn/news/1527053.html

相关文章

golang中连接达梦数据库使用域名来代替IP时会出现解析问题

中间件使用gorm driverName : "dm" dataSourceName : fmt.Sprintf("dm://%s:%s%s:%s/SYSDBA?charsetutf8&parseTimetrue", config.Database.Username, config.Database.Password, config.Database.Address, config.Database.Port)config.Database.Ad…

注意!Facebook已移除细分定位排除受众的功能

上月&#xff0c;Meta发布更新将移除细分定位排除受众的功能&#xff0c;1月31前现有的使用细分定位排除条件的广告仍可继续投放&#xff0c;但新建广告无法使用细分定位排除功能&#xff0c;1月31后所有使用细分定位排除条件的广告都将无法投放&#xff0c;这就意味着广告主们…

django 通过地址访问本地文件

django 通过地址访问本地文件 在Django中&#xff0c;如果你想通过URL访问本地文件&#xff0c;你可以使用Django的serve视图。首先&#xff0c;你需要配置你的urls.py来匹配文件存储的路径&#xff0c;并且确保文件存储在你的本地文件系统中。 以下是一个简单的例子&#xff…

春秋云境之CVE-2022-30887

一.靶场环境 1.下载靶场环境 根据题目提示&#xff0c;此靶场存在文件上传漏洞。 2.启动靶场环境 我们可以看到是一个登录页面&#xff0c;我们尝试进行登录 二.登录页面 1.尝试进行登录 我们发现用户名必须是邮箱&#xff0c;那么弱口令肯定不行&#xff0c;我们可以看到…

python学习第八节:爬虫的初级理解

python学习第八节&#xff1a;爬虫的初级理解 爬虫说明&#xff1a;爬虫准备工作&#xff1a;分析网站url分析网页内容 爬虫获取数据&#xff1a;1.使用urllib库发起一个get请求2.使用urllib库发起一个post请求3.网页超时处理4.简单反爬虫绕过5.获取响应参数6.完整请求代码 解析…

2024 VMpro 虚拟机中如何给Ubuntu Linux操作系统配置联网

现在这是一个联网的状态 可以在商店里面下载东西 也能ping成功 打开虚拟网络编辑器 放管理员权限 进行设置的更改 选择DNS设置 按提示修改即可 注意的是首选的DNS服务器必须是114.114.114.114 原因 这边刚刚去查了一下 114.114.114.114 是国内的IP地址 8.8.8.8 是国外的I…

高级编程--第七章 XML

1、目标 理解XML该你那及优势 回避那些格式良好的XML文档 了解XML中特殊字符的处理方式 了解解析器概念 了解DOM树节点构造 会使用Dom操作XML数据&#xff08;添加/保存&#xff09; 2、XML简介 XML&#xff08;EXtensible Markup Language&#xff09;,可扩展标记语言&…

@PathVariable,@RequestParam,@RequestBody注解,springboot与前端请求之间的数据类型转换

前端数据与springboot java数据类型转换 springboot&mybatis中数组和字符串数据类型的转换-CSDN博客中曾经提到&#xff0c;在Spring Boot中&#xff0c;通过URL传参、payload中的key-value形式或json形式&#xff0c;将前端数据以字符串格式发送到后端&#xff0c;后端We…