C语言数据在内存中的存储

C语言数据在内存中的存储

个人主页:大白的编程日记
个人专栏:C语言学习之路


文章目录

  • C语言数据在内存中的存储
    • 前言
    • 一.整数在内存中的存储
      • 1.1整数的表示形式
      • 1.2整数在内存中的存放
    • 二.大小端字节序和字节序判断
      • 2.1大小端的概念
      • 2.2为什么有大小端
    • 三.练习
      • 3.1整型提升
      • 3.2算术转换
      • 3.3练习一(百度笔试题)
      • 3.4练习二
      • 3.5练习三
      • 3.6练习四
      • 3.7练习五
      • 3.8练习六
    • 四.浮点数在内存中的存储
      • 4.1练习
      • 4.2浮点数的存储
      • 4.3浮点数存的过程
      • 4.4浮点数取的过程
    • 后言

前言

哈喽,各位小伙伴大家好!我们都知道计算机的数据都是存储在内存中的。那它是如何存储,以什么形式存储,存储方法又是什么呢?今天小编就带着大家一起去学习数据在内存中的存储。向着大厂冲锋!


一.整数在内存中的存储

1.1整数的表示形式

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

三种表示方法均有符号位和数值位两部分,
符号位都是用0表示“正”,用1表示“负”,
而数值位最高位的⼀位是被当做符号位,剩余的都是数值位。

  • 正数:
    正整数的原、反、补码都相同。

  • 负数:
    负整数的三种表示方法各不相同。
    原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。
    反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
    补码:反码+1就得到补码。

    注意,补码转原码有两种方式。
    一:先-1后取反。
    二:先取反后+1。


1.2整数在内存中的存放

对于整形来说:数据存放内存中其实存放的是补码。
在计算机系统中,数值⼀律⽤补码来表示和存储。
为什么呢?

  • 使用补码,可以将符号位和数值域统⼀处理。
  • 加法和减法也可以统⼀处理(CPU只有加法器),可以将减法转化为加法运算。这是用原码计算是错误的,使用补码才能正确运算。
  • 补码与原码相互转换,其运算过程是相同的,都可以按取反+1转化,不需要额外的硬件电路。

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

当我们了解了整数在内存中存储后,我们调试看⼀个细节:

#include <stdio.h>
int main()
{int a = 0x11223344;return 0;
}

调试的时候,我们可以看到在a中的 0x11223344 这个数字是按照字节为单位,倒着存储的。
这是为什么呢?
这就涉及到大小段字节序的问题了。


2.1大小端的概念

其实超过⼀个字节的数据在内存中存储的时候,就有存储顺序的问题。
我们来想一个问题

#include <stdio.h>
int main()
{int a = 0x11223344;return 0;
}

如果我们要把a存在内存里我们该怎么存。

比如这四种存放顺序。
其实只要保证存放时的数据和我们拿出来的数据是一样的,
任何顺序都可以。
但是为了为了方便理解,我们就选用第一和第二种存放方式。
这两种就会大端字节序存放和小端字节序存放。、

  • 大端字节序存储
    将一个数据低位字节的内容存放到高地址,把高位字节的内容存放到低地址处。

  • 小端字节序存储
    将一个数据高位字节的内容存放到高地址,把低位字节的内容存放到低地址处。

注意无论是大端还是小段存储,我存放的是什么,从内存拿出来时就是什么。不会因为倒着存放,拿出来的数据就是倒着的。


2.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.1整型提升

C语言中整型算术运算总是至少以默认整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。

  • 整型提升的意义
    表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度⼀般就是int的字节长度,同时也是CPU的通用寄存器的长度。
    因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
    通用CPU(general-purposeCPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
//实例1 
char a,b,c;
...
a = b + c;

b和c的值被提升为普通整型,然后再执行加法运算。
加法运算完成之后,结果将被截断,然后再存储于a中。

那如何整型提升呢?

  • 有符号整数提升是按照变量的数据类型的符号位来提升的。
  • 无符号整数提升,高位补0。
//负数的整形提升 
char c1 = -1;
变量c1的⼆进制位(补码)中只有8个⽐特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,⾼位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//正数的整形提升 
char c2 = 1;
变量c2的⼆进制位(补码)中只有8个⽐特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,⾼位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
//⽆符号整形提升,⾼位补0 

3.2算术转换

如果某个操作符的各个操作数属于不同的类型,那么除非其中⼀个操作数的转换为另⼀个操作数的类型,否则操作就无法进行。下⾯的层次体系称为寻常算术转换。

long double
double
float
unsigned long int
long int
unsigned int
int

如果某个操作数的类型在上面这个列表中排名靠后,那么首先要转换为另外⼀个操作数的类型后执行运算。


3.3练习一(百度笔试题)

请简述大端字节序和小端字节序的概念,设计⼀个小程序来判断当前机器的字节序。(10分)-百度笔试题

以1为例。
我们只需要拿到低地址处的字节内容即可。
那怎样才能拿到呢?

#include <stdio.h>
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;
}

我们只需要取出变量i的地址,因为&取出的总是低地址的那个字节地址。
但是因为是int类型,而我们只需要一个字节,所以我们强制类型转化为char*。
再解引用即可访问低地址的那个字节,再判断是1还是0即可。

  • 验证:
    在这里插入图片描述

3.4练习二

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

上面的代码会输出啥?
首先我们需要知道signed char和unsigned char的区别。

  • signed char

  • unsigned char

    我们现在再回到题目。

  • 补码存储

    因为内存中存储的是二进制的补码,所以我们先把-1的补码写出来。

  • 截断

    因为-1是整数,放在char类型变量需要发生截断,只保留后八位比特位。

  • 整型提升
    注意char是有符号的char还是无符号的char是取决于编译器的!
    在vs中char等价于signed char。

    char a = -1;signed char b = -1;unsigned char c = -1;printf("a=%d,b=%d,c=%d", a, b, c);

因为是%d打印,%d是打印有符号整数。所以这里需要发生整型提升。
整型提升有符号数按照符号位填充,无符号数用0填充。

所以结果就是-1 -1 255。

  • 验证

    所以数据在存储时不会关心是否是有符号数还是无符号数。
    输出时才会考虑是否是有符号数还是无符号数。

3.5练习三

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

    这里我们写出-128的补码。

  • 截断

  • 整型提升
    %u认为内存存放的是无符号数。
    a是char类型,需要发生整形提升。

  • 验证:


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


所以这两个代码的输出一样。

  • 验证:

3.6练习四

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

我们再来看这道题。
首先我们需要知道strlen求得是字符串的长度,
统计\0之前字符的个数,\0的ASCLL码值是0。
所以这道题的意思就是统计在0之前的字符数。
那我们怎么找呢?
我们前面说过signed char的取值范围是-128到217。
那如果超出范围会怎样呢?

大家来看图解。
如果超出范围内存中存的数据就会继续绕圈循环。
顺时针看是+1的循环,逆时针看是-1的循环。

for(i=0; i<1000; i++){a[i] = -1-i;}

题目的for循环是-1,那我们就逆时针看。
数字变化过程就应该是这样的:
在这里插入图片描述
所以答案应该是255。

  • 验证:在这里插入图片描述

3.7练习五

#include <stdio.h>
unsigned char i = 0;
int main()
{for(i = 0;i<=255;i++){printf("hello world\n");}return 0;
}

我们来看这个代码。

这是无符号的char的循环图。
所以unsigned char的取值范围为0到255。
所以i<=255的条件恒成立,代码死循环。

#include <stdio.h>
int main()
{unsigned int i;for(i = 9; i >= 0; i--){printf("%u\n",i);}return 0;
}

同理unsigned int存在内存的数据永远>=0.
所以i >= 0判断条件恒成立。代码也是死循环。


3.8练习六

#include <stdio.h>
int main()
{int a[4] = { 1, 2, 3, 4 };int *ptr1 = (int *)(&a + 1);int *ptr2 = (int *)((int)a + 1);printf("%x,%x", ptr1[-1], *ptr2);return 0;
}

小端字节序,X86环境下代码输出的结果是啥?

  • ptr1[-1]
    在这里插入图片描述
    这里大家注意指针的类型和强制类型转化即可。
  • *ptr2

    大家看一下图解。
  • 验证:

    这里再跟大家说一下为啥是x86的环境。
    因为x64的环境这段代码执行不了为啥呢?
    因为x64指针大小是8个字节,int是四个字节。
    强转的过程会发生截断,那就会有数据的丢失。
    后面再强转成指针时就会有野指针的风险,所以无法执行。
  • 验证:

四.浮点数在内存中的存储

常见的浮点数:3.14159、1E10等,浮点数家族包括: float、double、long double 类型。
浮点数表示的范围: float.h 中定义

4.1练习

#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("num的值为:%d\n",n);printf("*pFloat的值为:%f\n",*pFloat);return 0;
}

大家来看看这段代码,大家觉得会输出啥?
可能很多小伙伴都会觉得时9 9.0 9 9.0 。
但其实不是。

通过观察我们可以发现,只有当我们以整型存储并且以整形读取输出时,或者浮点数存储并且以浮点数读取输出时结果才和存储的数据一样。
这就说明整型和都浮点型的存储和读取方式不一样。
那浮点型的存储和读取方式是怎么样的呢?


4.2浮点数的存储

我们先来思考一个问题: 浮点数如何用二进制表示?

上面的代码中, num 和 *pFloat 在内存中明明是同⼀个数,为什么浮点数和整数的解读结果会差别这么大?
要理解这个结果,⼀定要搞懂浮点数在计算机内部的表示方法。
根据国际标准IEEE(电气和电子工程协会)754,任意⼀个二进制浮点数V可以表示成下面的形式:
在这里插入图片描述
举例来说:
十进制的5.0,写成二进制是 101.0 ,相当于 1.01×2^2 。
那么,按照上面的格式,可以得出S=0,M=1.01,E=2。
十进制的-5.0,写成⼆进制是 -101.0 ,相当于 -1.01×2^2 。那么,S=1,M=1.01,E=2。
IEEE754规定:

  • 对于32位的浮点数(float),最高的1位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字M

  • 对于64位的浮点数(double),最高的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M。

    所以浮点数的存储过程其实就是存储S E M三个数据的过程。


4.3浮点数存的过程

EEE754对有效数字M和指数E,还有⼀些特别规定。

  • 前面说过, 1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中 xxxxxx 表示小数部分。
    IEEE754规定,在计算机内部保存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是可以出现负数的,所以IEEE754规定,存入内存时E的真实值必须再加上一个中间数,
    对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。

这里我们来验证一下吧。

int main()
{float f = 5.5f;return 0;
}


因为是小端存放,所以内存是倒着存放。

  • 特殊浮点数无法精确保存
    大家注意,并不是所有浮点数都能精确表示的。
    可能会出现这种情况:

  • 验证:


4.4浮点数取的过程

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

  • E不全为0或不全为1
    这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第⼀位的1。
    比如:0.5的⼆进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位,则为1.0*2^(-1),其阶码为-1+127(中间值)=126,表示为01111110,二尾数1.0去掉整数部分为0,补齐0到23位
    00000000000000000000000,则其二进制表示形式为:
1 0 01111110 00000000000000000000000
  • E全为0
    这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第⼀位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
1 0 00000000 00100000000000000000000
  • E全为1
    这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);
1 0 11111111 00010000000000000000000

这是我们再来看回开始的代码

所以代码输出就是:


后言

这就是数据再内存中的存储啦!这些知识看似不起眼,其实都是在修炼我们编程学习的内功。大家下去一定要认真学习。感谢大家的垂阅。今天就分享到这里,咱们下期见!拜拜~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/323022.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

利用智能私信软件,快速拓展潜在客户群体

在数字化营销的浪潮中&#xff0c;企业如何快速而有效地触及并吸引潜在客户&#xff0c;已成为一个不可忽视的挑战。随着人工智能技术的不断进步&#xff0c;智能私信软件作为一种新型工具&#xff0c;正逐渐改变着企业的市场拓展方式。本文将探讨如何通过这类软件&#xff0c;…

加州大学欧文分校英语中级语法专项课程04:Intermediate Grammar Project学习笔记(完结)

Intermediate Grammar Project Course Certificate Specialization Certificate Specialization Intro Course Intro 本文是学习 Coursera: Intermediate Grammar Project 这门课的学习笔记。 文章目录 Intermediate Grammar ProjectWeek 01: IntroductionCapstone Introducti…

机器学习——2.损失函数loss

基本概念 损失函数也叫代价函数。损失函数就是计算预测结果和实际结果差距的函数&#xff0c;机器学习的过程就是试图将损失函数的值降到最小。 图左&#xff1a;&#xff5c;t_p - t_c&#xff5c; 图右&#xff1a;&#xff08;t_p - t_c&#xff09;**2 代码实…

docker安装Debian:11 freeswitch1.10.5

文章目录 一、生成一个镜像二、切换一个镜像源为阿里源三、安装一些相关依赖和freeswitch3.1第一步&#xff1a;安装freeswitch-mod和下载所需的依赖项3.2 设置密钥3.3 安装freeswitch所需的依赖项3.4 报错3.4.1 报错13.4.2 报错23.4.3 报错3 四、运行4.1 通话三十秒自动挂断 一…

linux学习:线程池

目录 原理 初始线程池 运行中的线程池 相关结构体 api 线程池初始化 投送任务 增加活跃线程 删除活跃线程 销毁线程池 例子 thread_pool.h thread_pool.c test.c 测试程序 原理 一个进程中的线程就好比是一家公司里的员工&#xff0c;员工的数目应该根据公司的…

【qt】容器的用法

容器目录 一.QVertor1.应用场景2.增加数据3.删除数据4.修改数据5.查询数据6.是否包含7.数据个数8.交换数据9.移动数据10.嵌套使用 二.QList1.应用场景2.QStringList 三.QLinkedList1.应用场景2.特殊点3.用迭代器来变量 四.QStack1.应用场景2.基本用法 五.QQueue1.应用场景2.基本…

LeetCode例题讲解:快乐数

编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变不到 1。如果这个过程 结果为 1&#xff0c…

[uniapp 地图组件] 小坑:translateMarker的回调函数,会调用2次

大概率是因为旋转和移动是两个动画&#xff0c;动画结束后都会分别调用此函数 即使你配置了 【不旋转】它还是会调用两次&#xff0c; 所以此处应该是官方的bug

JavaWeb之Servlet(上)

前言 1. 什么是Servlet (1) Servlet介绍 (2) Servlet运行于支持Java的应用服务器中。 (3) Servlet工作模式&#xff1a; 2. Servlet API 3. 第一个Servlet (1) 创建一个类实现Servlet接口,重写方法。或继承HttpServlet亦可 (2) 在web.xml文档中配置映射关系 标签的执行…

新能源汽车充电站智慧充电电能服务综合解决方案

安科瑞薛瑶瑶18701709087/17343930412 ★解决方案 ✔目的地充电-EMS微电网平台 基于EMS解决方案从设备运维的角度解决本地充电的能量管理及运维问题&#xff0c;与充电管理平台打通数据&#xff0c;为企业微电网提供源、网、荷、储、充一体化解决方案。 ✔运营场站--电能服务…

Burp插件之BurpSuitFake

在最近的测试中发现一个问题 某些高校在在登陆教务平台登陆的时候没有设置验证码 而且重点是 他写的很清楚 密码是身份证后六位 以下是我的一些思路和过程中解决的办法 part1信息搜集 学生的学号是很有规律的 例如我的学号 就是入学年份专业代码班级学号 每个大学学号规则不…

AlphaFold3: Google DeepMind的的新突破

AlphaFold 3的论文今天在Nature期刊发表啦!这可是AI在生物领域最厉害的突破的最新版本。AlphaFold-3的新招就是用扩散模型去"画出"分子的结构。它一开始先从一团模模糊糊的原子云下手,然后慢慢透过去噪把分子变得越来越清楚。 Alphafold3 我们活在一个从Llama和Sora那…

69、oak和华为atlas 200dk A2进行编解码测试

基本思想:将oak深度相机与atlas 200dk A2进行结合,测试其dvpp的编解码能力 cmakelist.txt cmake_minimum_required(VERSION 3.16) project(untitled10) set(CMAKE_CXX_FLAGS "-std=c++11") set(CMAKE_CXX_STANDARD 11) add_definitions(-DENABLE_DVPP_INTERFACE)i…

小红书释放被封手机号 无限注册

前几年抖音也可以释放被封手机号 那时候都不重视 导致现在被封手机号想释放 基本不可能的 或者就是最少几百块 有专业的人帮你通过某些信息差释放 本教程是拆解 小红书被封手机号怎么释放&#xff0c;从今年开始&#xff0c;被封的手机号无法注销了 所以很困扰 那么本教程来…

【ytb数据采集器】按关键词批量爬取视频数据,界面软件更适合文科生!

一、背景介绍 1.1 爬取目标 用Python独立开发的爬虫工具&#xff0c;作用是&#xff1a;通过搜索关键词采集油管的搜索结果&#xff0c;包含14个关键字段&#xff1a;关键词,页码,视频标题,视频id,视频链接,发布时间,视频时长,频道名称,频道id,频道链接,播放数,点赞数,评论数…

nacos下载安装和nacos启动报错

nacos简介: Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称&#xff0c;一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集&#xff0c;帮助您…

关于线程池,它的扩展问题你知道吗?(自己总结)

专门想一下为什么线程池不用Excutors&#xff0c;之前的印象是错的&#xff0c;居然还拿来面试里讲&#xff0c;惭愧&#xff0c;这里暂时整理俩小问题&#xff0c;其他的后续可能会更新。。 线程池是创建的越大越好嘛 #线程池创建的越大越好吗 Tip&#xff1a;2024-04-10 更…

阵痛中的乳业产业,何时才能成为下一个啤酒产业?

说起饮品&#xff0c;近年来中国啤酒业中各大品牌齐齐聚焦高端化的趋势绝对值得一提。然而&#xff0c;与之相反&#xff0c;国内乳业却是仍未进入高端化阶段&#xff0c;甚至陷入了周期底部中。 图源&#xff1a;中国圣牧财报 增收降利 牧企承受巨大的供需缺口压力 从产业链…

字节跳动(社招)四面算法原题

TikTok 进展 又是一期定时汇报 TikTok 进展的推文。 上周&#xff0c;美国总统拜登签署了价值 950 亿美元的一揽子对外援助法案。 该法案涉及强制字节跳动剥离旗下应用 TikTok 美国业务&#xff0c;即 针对 TikTok 非卖即禁的"强抢行为"开始进入九个月&#xff08;27…

五大自动化测试的 Python 框架

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…