【C 数据存储详解】(1)——深度剖析整形数据在内存中的存储

简介: 【C 数据存储详解】(1)——深度剖析整形数据在内存中的存储

今天我们一起来学习一下C语言中的整型数据是如何在内存中存储的!!!


一.数据类型介绍

1.类型的意义

我们已经学习过了一些基本的内置类型:


char //字符数据类型

short //短整型

int //整形

long //长整型

long long //更长的整形

float //单精度浮点数

double //双精度浮点数


以及他们所占存储空间的大小(单位是字节):

#include <stdio.h>
int main()
{
    printf("%d\n", sizeof(char));
    printf("%d\n", sizeof(short));
    printf("%d\n", sizeof(int));
    printf("%d\n", sizeof(long));
    printf("%d\n", sizeof(long long));
    printf("%d\n", sizeof(float));
    printf("%d\n", sizeof(double));
    printf("%d\n", sizeof(long double));
    return 0; }

d0933bd8e0734138bfea1050853ed15a.png

C语言规定了这么多不同的数据类型,那么它们有什么意义呢?


1. 使用这个类型开辟内存空间的大小(大小决定了使用范围)。

2. 如何看待内存空间的视角。

2.类型的基本归类

下面我们对C语言中的数据类型做一个基本的归类:

(1).整型家族


1.char

unsigned char

signed char


2.short

unsigned short [int]

signed short [int]


3.int

unsigned int

signed int


4.long

unsigned long [int]

signed long [int]


这里大家可能会有疑惑,char不是字符类型吗?为啥归到整型里面了。


这是因为每个字符都有对应的ASCII码值,这些字符在内存中存储的时候,实际就是存放的ASCII码值,而ASCII码值都是整数,所以将char也归类到整型里面了


在这里还要给大家提醒一点,就是:


对于char来说,C语言本身并没有明确规定我们定义一个char类型的变量,它到unsigned char 还是 signed char 。 这个取决于编译器,不同的编译器情况可能不同,但是在绝大多数编译器上都是signed char 。

但是,对于short,int,long ,C语言规定了

1.short 就是signed short

2.int 就是 signed int

3.long 就是 signed long


(2).浮点数家族


float 单精度浮点型

double 双精度浮点型


(3).构造类型


数组类型

结构体类型 struct

枚举类型 enum

联合类型 union


(4).指针类型


int pi;

char pc;

float pf;

void pv;

0df6da820ea54658a3f0aa523d65778a.png

(5).空类型


void 表示空类型(无类型)

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


二.详解整型在内存中的存储

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


那接下来我们谈谈数据在所开辟内存中到底是如何存储的?

比如:

int a = 20;
int b = -10;

我们知道为 a 分配四个字节的空间。

那如何存储?

来了解下面的概念:


1. 原码、反码、补码

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

三种表示方法均有符号位和数值位两部分:

符号位都是用0表示“正”,用1表示“负”。

而数值位:

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

负整数的三种表示方法各不相同。


下面来介绍一下什么时是原码、反码、补码:

原码:


直接将数值按照正负数的形式翻译成二进制就可以得到原码。


补码:


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


反码:


反码+1就得到补码。


举个例子:

f00415cb0880456c80c05937fd6d8377.png

再看一个负数:

c9ba3f76ad544acdb98724c2493dac35.png

整数的2进制表示方法有原码、反码和补码,那内存中存的到底是啥哪?


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


为什么呢?


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

同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的

,不需要额外的硬件电路。


下面来解释一下:


1. 可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理


因为CPU只有加法器,所以对于1-1这样的表达式CPU要处理成1+(-1)来进行计算的。

而如果直接将两个操作数的原码进行相加,是可能会出错的:

举个例子:

1a8753b321574cf68b1df88e43bf7c74.png

1+(-1),我们用原码相加,得到错误的结果

用补码计算:

f2106145e58e4debb3e6c092bf93b022.png

最终用补码相加得到的结果才是正确的!!!


2.补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。


我们通过原码得到补码的方法是:

原码的符号位不变,其它位按位取反得到反码,反码加1,得到补码;

其实补码转到原码也可以用同样的方法。


然后我们创建两个变量,看一下,内存给我们展示出来的是什么样子的:

db0b1f1bd122469da0769102e3bb6ce2.png

我们可以看到对于a和b分别存储的是补码。但是我们发现顺序有点不对劲,好像是相反的。

这是又为什么?


2.大小端介绍

上面我们发现,对于a和b分别存储的是补码。但是我们发现顺序有点不对劲,好像是相反的,为什么?


学完下面的内容,我们就清楚了。

什么大端小端:


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


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


解释一下:

5badc71ca232421c9523aa2dae23800f.png

现在再看这张图我们就明白为什么顺序有点不对劲了。

4926b95dded643e5bfe618365d5d39f2.png

因为在vs2022上,采用的是小端存储模式。


那为什么会有大小端呢?


为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元

都对应着一个字节,一个字节为8 bit。但是在C语言中除了8 bit的char之外,还有16 bit的short型,32 bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。


例如:一个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为高字节, 0x22 为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中, 0x22 放在高地址中,即 0x0011 中。小端模式,刚好相反。我们常用的 X86 结构是小端模式,而 KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。


3.百度2015年系统工程师笔试题讲解

那么我们接下来做一道练习题,这道题是百度2015年系统工程师笔试题:


请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。


概念我们上面已经说过了,那怎么设计程序呢?我们来思考一下:


我们可以用整数1来帮助判断,取出1的第一个字节的内容,1的补码是:00000000000000000000000000000001,16进制是:00 00 00 01;

如果第一个字节的值是0(高位在低地址),则为大端;

如果第一个字节的值是1(低位在低地址),则为小端。

(注:我们取出的第一个字节是处在低地址的那一个字节)

afefa61b4ef842408d7feda9c454f1a0.png

上代码:

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

我们已经知道了vs上是小端,我们来一下看结果对不对:

72436107aa964b1f86dd1da538bfcca1.png

以上就是对整型在内存中如何存储的详细介绍,欢迎大家指正,我们一起进步!!!

00f04406d8ba4137945e709d108e6f55.png


目录
相关文章
|
1月前
|
存储 编译器 数据处理
C 语言结构体与位域:高效数据组织与内存优化
C语言中的结构体与位域是实现高效数据组织和内存优化的重要工具。结构体允许将不同类型的数据组合成一个整体,而位域则进一步允许对结构体成员的位进行精细控制,以节省内存空间。两者结合使用,可在嵌入式系统等资源受限环境中发挥巨大作用。
62 11
|
2月前
|
监控 算法 应用服务中间件
“四两拨千斤” —— 1.2MB 数据如何吃掉 10GB 内存
一个特殊请求引发服务器内存用量暴涨进而导致进程 OOM 的惨案。
|
2月前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
116 1
|
1月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
280 1
|
21天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
1月前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
1月前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
22 3
|
1月前
|
存储 缓存 监控
Elasticsearch集群JVM调优堆外内存
Elasticsearch集群JVM调优堆外内存
49 1
|
1月前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。
|
2月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
86 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS