C/C++数据在内存中的存储方式

简介: C/C++数据在内存中的存储方式

C语言给定了一些基本的数据类型

char   类型
int    类型   
long   类型
float  类型
double 类型

前三者都属于整型家族,后两者属于浮点型家族,为什么char属于整型家族呢?这是因为字符是以对应的ASCII码值存放到内存中,本质上是属于整数的,所以将其归类为整型。

那么我们在创建这些变量且赋予它们具体数值的时候,它们又是如何将这些数值数据存放到内存中的呢?我们都知道任何形式的数据(字符,图片, 音频......)放到电脑中都是以二进制的形式存放的

例如 我们创建一个整型变量,并赋予它一个数值

int  a = 10;
a中的数据放在内存中需要转化为二进制形式
将10转化为二进制形式为 1010 因为int有32个比特位的大小,所以最终的形式如下
00000000000000000000000000001010

这样我们就解决了存放整数的问题,但很快会发现,如果存放一个负整数该怎么存呢?该怎么表示这个数字是负的呢?聪明的科学家想到了这样一个办法,那就是将这个二进制序列的最高位定义为符号位,如果最高位上的数字为0,那表示这个数字是正数,如果最高位上的数字为1,那表示这个数字是负数,例如下面的表示

int  a = 10;
a中的数据放在内存中转化为二进制形式为
00000000000000000000000000001010
int a = -10;
a中的数据放在内存中转化为二进制形式为
10000000000000000000000000001010

好了,现在正整数和负整数在内存中存放的形式都解决了,哈哈,不过问题又来了,正整数和负整数在进行加减运算的时候就出现问题了,如下

int a = 10;
int b = -1;
     a 在内存中的二进制形式为  00000000000000000000000000001010
假设 b 在内存中的二进制形式为  10000000000000000000000000000001
如果将a 与 b 相加 
a 00000000000000000000000000001010
+
b 10000000000000000000000000000001
= 10000000000000000000000000001011

a和b相加,结果却变成了 -11,怎们办,这里不得不佩服科学家的聪明,引入了原码 补码 反码的概念,那么这些具体有什么作用呢?接下来分析一下。

正数的原码 反码 补码相同
int a = 10;
a 的二进制形式为 
00000000000000000000000000001010  //原码
00000000000000000000000000001010  //反码
00000000000000000000000000001010  //补码
负数的反码为 原码的符号位不变,其他位取反。负数的补码为反码加1
int b = -10;
b 的二进制形式为
10000000000000000000000000001010  //原码
11111111111111111111111111110101  //反码
11111111111111111111111111110110  //补码

这里需要知道的是,无论正数还是负数,都是以补码的二进制序列放到内存中的

因此实际上,a放到内存中是补码  00000000000000000000000000001010

b放到内存中是补码  11111111111111111111111111110110,接下来我们通过编译器去验证一下。

验证结果是没有问题滴,说明确实是这样存储的,但至此,可能对其作用仍不了解,那接下来说一下反码 补码的具体的作用,还是以加法为例

int a = 10;
a 的二进制形式为 
00000000000000000000000000001010  //原码
00000000000000000000000000001010  //反码
00000000000000000000000000001010  //补码
int b = -5;
b 的二进制形式为
10000000000000000000000000000101  //原码
11111111111111111111111111111010  //反码
11111111111111111111111111111011  //补码
因为都是以补码存放到内存中,所以在内存中
a 为00000000000000000000000000001010
b 为11111111111111111111111111111011
a 00000000000000000000000000001010
+
b 11111111111111111111111111111011
= 00000000000000000000000000000101
转化为十进制就是5  是不是很奇妙,这就是反码 补码的妙用,非常漂亮的的解决掉正负数进行加减运算的问题,其他的乘除运算都可以转化为加法运算,这样也就同时解决掉其他的运算问题。
这里补充一点,补码取反加1是可以转化为原码的
我们试一试
11111111111111111111111111111011  这是b的补码,现在我们符号位不动,其他位取反
10000000000000000000000000000100  这是补码取反后的结果,接下来我们把它加1
10000000000000000000000000000101  这是加1后的结果,发生什么事了,是不是又变回了原码

接下来,举点栗子,加深对上面知识的理解

char a = -1;                   //注:char 是 signed char还是 unsigned char取决于编译器
signed char b = -1;                                         //不过一般都是signed char
unsigned char c = -1;
printf("%d %d %u", a, b, c);
那么打印的结果分别是什么呢?

一起分析一下吧

以上就是整型数字在内存中的存储方式,但仔细观察下图,会发现一个奇怪的现象



就拿int a 来说 我们分析的存储方式应该是00 00 00 0a  ,而编译器上却显示存储形式为0a 00 00 00

这其实就牵涉到大端,小端存储的问题,为什么会这样呢?因为int 类型接连占用四个字节,该空间的数据的存放位置就得有一个规定,我们同样可以将a中得数据以00 0a 00 00或者是00 00 0a 00的方式存储,但这些都太过麻烦了,00 00 00 0a 和 0a 00 00 00这两种存储方式最符合我们的思维习惯,就沿用至今,分别叫大端存储法和小端存储法。大端还是小端是取决于硬件的,与编译器无关,一般个人pc用的是小端存储法。

接下来我们一起分析一下大端与小端,然后再写个小程序来判断自己的设备采用的是大端还是小端

那我们怎么判断自己的设备是大端还是小端呢? 同样是 int a = 10; 我们想看看它是大端存放还是小端存放,只需查看该空间的第一个字节,因为空间的第一个字节是处于低地址的,如果该空间的第一个字节没有存放 0a 则说明是大端存放,如果存放了0a,则说明是小端存放。

代码实现如下

int main()
{
  int a = 10;
  char *p = (char*)&a;
  if (10 == *p)
  { 
       printf("小端\n");
  }
  else
  {
       printf("大端\n");
  }
  return 0;
}


目录
相关文章
|
3天前
|
消息中间件 存储 缓存
kafka 的数据是放在磁盘上还是内存上,为什么速度会快?
Kafka的数据存储机制通过将数据同时写入磁盘和内存,确保高吞吐量与持久性。其日志文件按主题和分区组织,使用预写日志(WAL)保证数据持久性,并借助操作系统的页缓存加速读取。Kafka采用顺序I/O、零拷贝技术和批量处理优化性能,支持分区分段以实现并行处理。示例代码展示了如何使用KafkaProducer发送消息。
|
21天前
|
存储 算法 C++
【C++数据结构——图】图的邻接矩阵和邻接表的存储(头歌实践教学平台习题)【合集】
本任务要求编写程序实现图的邻接矩阵和邻接表的存储。需掌握带权有向图、图的邻接矩阵及邻接表的概念。邻接矩阵用于表示顶点间的连接关系,邻接表则通过链表结构存储图信息。测试输入为图的顶点数、边数及邻接矩阵,预期输出为Prim算法求解结果。通关代码提供了完整的C++实现,包括输入、构建和打印邻接矩阵与邻接表的功能。
44 10
|
21天前
|
存储 算法 搜索推荐
【C++面向对象——群体类和群体数据的组织】实现含排序功能的数组类(头歌实践教学平台习题)【合集】
1. **相关排序和查找算法的原理**:介绍直接插入排序、直接选择排序、冒泡排序和顺序查找的基本原理及其实现代码。 2. **C++ 类与成员函数的定义**:讲解如何定义`Array`类,包括类的声明和实现,以及成员函数的定义与调用。 3. **数组作为类的成员变量的处理**:探讨内存管理和正确访问数组元素的方法,确保在类中正确使用动态分配的数组。 4. **函数参数传递与返回值处理**:解释排序和查找函数的参数传递方式及返回值处理,确保函数功能正确实现。 通过掌握这些知识,可以顺利地将排序和查找算法封装到`Array`类中,并进行测试验证。编程要求是在右侧编辑器补充代码以实现三种排序算法
36 5
|
1月前
|
存储 缓存 编译器
【硬核】C++11并发:内存模型和原子类型
本文从C++11并发编程中的关键概念——内存模型与原子类型入手,结合详尽的代码示例,抽丝剥茧地介绍了如何实现无锁化并发的性能优化。
|
15天前
|
存储 程序员 编译器
什么是内存泄漏?C++中如何检测和解决?
大家好,我是V哥。内存泄露是编程中的常见问题,可能导致程序崩溃。特别是在金三银四跳槽季,面试官常问此问题。本文将探讨内存泄露的定义、危害、检测方法及解决策略,帮助你掌握这一关键知识点。通过学习如何正确管理内存、使用智能指针和RAII原则,避免内存泄露,提升代码健壮性。同时,了解常见的内存泄露场景,如忘记释放内存、异常处理不当等,确保在面试中不被秒杀。最后,预祝大家新的一年工作顺利,涨薪多多!关注威哥爱编程,一起成为更好的程序员。
|
2月前
|
存储 编译器 数据处理
C 语言结构体与位域:高效数据组织与内存优化
C语言中的结构体与位域是实现高效数据组织和内存优化的重要工具。结构体允许将不同类型的数据组合成一个整体,而位域则进一步允许对结构体成员的位进行精细控制,以节省内存空间。两者结合使用,可在嵌入式系统等资源受限环境中发挥巨大作用。
96 11
|
2月前
|
存储 缓存 C语言
【c++】动态内存管理
本文介绍了C++中动态内存管理的新方式——`new`和`delete`操作符,详细探讨了它们的使用方法及与C语言中`malloc`/`free`的区别。文章首先回顾了C语言中的动态内存管理,接着通过代码实例展示了`new`和`delete`的基本用法,包括对内置类型和自定义类型的动态内存分配与释放。此外,文章还深入解析了`operator new`和`operator delete`的底层实现,以及定位new表达式的应用,最后总结了`malloc`/`free`与`new`/`delete`的主要差异。
71 3
|
2月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
220 4
|
3月前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
197 1
|
2月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
543 1