文章目录

  • 1. 内存对齐
  • 2. 递归中的内存对齐
  • 3. C++引用的本质
  • 4. 致谢

1. 内存对齐

通过以下语句,获取变量的占用内存打下:

cout << "size of int " << sizeof(int) << endl;
cout << "size of int& " << sizeof(int&) << endl;
cout << "size of char " << sizeof(char) << endl;

int 和 int& 都占 4 字节, char 占 1 字节

编写一个类:其包含1个 int,2个 char,排列顺序不同。

class memory1
{int id;char a;char b;
};
class memory2
{char a;int id;char b;
};
class memory3
{char a;char b;int id;
};

可以看见,他们的占用空间大小是不一样的。

size of memory1 8
size of memory2 12
size of memory3 8
  • 计算机从内存读取数据是按块读取的,一般是4或者8的倍数一块,一起读取
  • CPU和内存IO的硬件限制导致没办法将一个数据类型分在两个块中读取
  • 内存对齐,可以加快程序的运行速度,一般编译器会在后台进行内存对齐优化,但是也不能做到十分完美
  • 内存对齐的参数可以更改,#pragma pack(n),n = 1,2,4,8,16

所以上面的memory类的内存对齐是按照4字节进行的,计算机按照顺序分配内存,4字节剩余空间能放下某个类型的,就放进去,放不进去的,新往下找一块4字节的空间放 int

加入#pragma pack(1),可见就是紧密排列了。

size of memory1 6
size of memory2 6
size of memory3 6
#pragma pack(2)
size of memory1 6
size of memory2 8
size of memory3 6

2. 递归中的内存对齐

我在做LeetCode题的时候遇到一个递归爆栈问题:

在这里做一些测试,不保证结果具有通用性,也请大家指正。

void dfs(int i) {int k = 0;cout << "&k = " << &k << endl;cout << "i = " << i << endl;i = i+1;dfs(i);
}
int main() {int i = 0;dfs(i);
}

变更dfs(i)参数个数
递归次数:32385,第一个k的地址 0x61fdcc,k地址间隔 6410(参数个数为1-4个)


增加参数个数到(5-6个):
递归次数:25908,第一个k的地址 0x61fdbc(比上面移动了16),k地址间隔 8010
增加参数个数到(7-8个):
递归次数:21589,第一个k的地址 0x61fdac(比上面移动了32),k地址间隔 9610
增加参数个数到(9-10个):
递归次数:18505,第一个k的地址 0x61fd8c(比上面移动了64),k地址间隔 11210
增加参数个数到(11个):
递归次数:16191,第一个k的地址 0x61fd7c(比上面移动了80),k地址间隔 12810

增加参数个数到(10个,且全部改成&引用):
递归次数:18505,第一个k的地址 0x61fd8c(比上面移动了64),k地址间隔 11210
以上均为win1064位操作系统 环境


目前通过结果,可以看见,

  • 参数的增多,递归层数会减小
  • 递归中的栈也遵守内存对齐原则
  • int 型的 & 对递归深度没有造成影响(win10,64位)
class memory1
{int id;char a[1280];char b;
};
int main() {int i = 0;int j = 0;int a = 0, b = 0, c = 0,d=0,e=0,f=0,g=0,h=0,l=0;memory1 m1;dfs(m1,i, j, a, b,c,d,e,f,g,h);
}
size of memory1 1288

上面程序,

递归次数:1436,第一个k的地址 0x61f34c(比上面移动了2688),k地址间隔 144010

将 m1 改成&

cout << "size of memory1& " << sizeof(&m1) << endl;
size of memory1& 8

递归次数:16181,第一个k的地址 0x61f86c(比上面移动了1376),k地址间隔 12810

  • 对于大型 结构数据,采用&能大幅节省空间,递归不至于过早爆栈结束。

但是,上面 LeetCode 爆栈就只是把 int&改成int程序就不爆栈了,跟认知的规律是不符合的,还请大佬看看什么原因?

3. C++引用的本质

参考:c++中“引用”的底层实现原理详解
在读完上文后,就清楚了,引用会产生一个8字节的变量存储被引用的变量的地址,所以上面win10的测试结果,有点不可信,可能这就是C++在硬件、操作系统、编译器不同的情况下结果有差异的情况,采用 linux 进行测试

在linux中测试结果:

传入2个int: 递归次数174522
传入2个int&:递归次数130885
传入2个double: 递归次数130912
传入2个double&:递归次数104668

我想这个数值,已经能够侧面说明上面链接文章中提到的引用本质了,C++引用的本质是指针,但是它跟指针又不一样,C++对指针进行了封装产生了引用,你在使用引用的时候,传给你的是它里面指针所指向的内容。

所以对这种内置的变量类型,函数调用的时候,直接使用copy传入就可以了,还比较省内存(int 4字节,使用 int & 会占用 8字节)

至此,可以解释上面 LeetCode 那道题,传入 int & 爆栈了,而改为 int ,题目就AC通过了。

4. 致谢

感谢焦/huaix提出去掉&可以解决问题,开启了我对这个问题的思考
感谢Thin-k.调试,确认是stack-overflow的问题
感谢CSDN群里的朋友热心讨论和研究,还有论坛朋友akari10032的解答
感谢hitskyer阿福的答疑
感谢所有在网络上分享知识的每一个博主!

参考链接:
带你深入理解内存对齐最底层原理
C/C++内存对齐详解

C++ 内存对齐 及 引用是否真的节省内存的一点思考相关推荐

  1. Eigen向量化内存对齐/Eigen的SSE兼容,内存分配/EIGEN_MAKE_ALIGNED_OPERATOR_NEW

    1.总结 对于基本数据类型和自定义类型,我们需要用预编译指令来保证栈内存的对齐,用重写operator new的方式保证堆内存对齐.对于嵌套的自定义类型,申请栈内存时会自动保证其内部数据类型的对齐,而 ...

  2. 内存对齐是什么?为什么要内存对齐?

    原文: http://blog.csdn.net/liupeng900605/article/details/7530010 首先由一个程序引入话题:  1 //环境:vc6 + windows sp ...

  3. C语言:--位域和内存对齐

    位域 位域是指信息在保存时,并不需要占用一个完整的字节,而只需要占几个或一个二进制位.为了节省空间,C语言提供了一种数据结构,叫"位域"或"位段". " ...

  4. 基于STM32使用嵌套结构体摸索内存对齐

    导读 一.结构体的声明以及初始化 二.结构体成员的内存对齐规则 三.联合体以及嵌套结构体的用法 位段 小端模式 三.位段数据跨字节单位导致的内存对齐现象 用来描述对象特征的各类信息,通常会被整合成记录 ...

  5. 4 OC 中的内存分配以及内存对齐

    目录 一  OC  中的内存分配 一  OC  中的内存分配 student 结构体明明是20?为什么是24个字节,因为结构体会按照本身成员变量最大的内存进行对齐,最大成员变量是8个字节,因此就是8的 ...

  6. c语言20字节的内存的数据怎么读取_C++编程-内存对齐

    内存对齐可以大大提升内存访问速度,是一种用空间换时间的方法. 1.内存对齐的计算机原理 内存地址对齐,是一种在计算机内存中排列数据(表现为变量的地址).访问数据(表现为CPU读取数据)的一种方式,包含 ...

  7. iOS之深入解析内存对齐的底层原理

    CPU 存取原理 一."存"示例 CPU 并不是以字节为单位存取数据的.CPU 把内存当成是一块一块的,块的大小可以是2,4,8,16字节大小,因此 CPU 在读取内存时是一块一块 ...

  8. 计算机大端模式和小端模式 内存对齐问题(sizeof)

    目录(?)[+] 一大端模式和小端模式的起源 二什么是大端和小端 三数组在大端小端情况下的存储 四为什么会有大小端模式之分呢 五如何判断机器的字节序 内存对齐问题 再讲讲pragma pack 内存对 ...

  9. 怎么调试内存溢出的c++代码_【C/C++】内存对齐 到底怎么回事?

    1 明确几个概念 代码分区:在使用C/C++编程时,我们定义的变量存在于内存中,而内存在C语言的角度上可以分为五大区.局部变量在栈区,静态/全局变量在全局区,动态申请的变量存在于堆区,const修饰的 ...

最新文章

  1. mysql中utf8_bin、utf8_general_ci、utf8_general_cs编码区别
  2. Star Way To Heaven (prim最小生成树) // [ NOIP提高组 2014]飞扬的小鸟(DP)
  3. 【bzoj1727】[Usaco2006 Open]The Milk Queue 挤奶队列 贪心
  4. MFC开发IM-第二十七篇、如何引入acl,解决Json解析问题
  5. 编码问题,java,当不知道自己的字符串编码是什么的时候,可以用如下程序进行尝试并自动转码utf-8,源码直接可用
  6. jQuery之animate自定义动画
  7. 带有书签的Android WebView
  8. 信号与系统与数字信号处理丹梅老师公众号笔记
  9. 为WINPE加入IMDISK 内存硬盘,虚拟磁盘,加速磁盘访问
  10. MSP430F5529-基于ATK-IMU901角度传感器在UART的串口通信于数据显示
  11. Python破解zip文件解压密码
  12. spring cloud 微服务调用链
  13. Springboot整合Hibernate入门
  14. 数据结构:图:图形和图形模型(Graphs and Graph Models)
  15. CQHTTP 以HTTP形式制作你的QQ机器人
  16. 简单易学:本身就很小且简单
  17. 阿里云ECS云服务器1M宽带能干嘛?
  18. HTML5的内容模型及页面结构图
  19. 北航软件能力培养启发和下一步工作
  20. 【树莓派 有趣实践】寻找小项目

热门文章

  1. dellt服务器r修复,RE: 求助 Dell T 310服务器蓝屏
  2. 第七季1:MP4文件格式解析
  3. c++远征之多态篇——异常处理
  4. find、grep,which、whereis
  5. s5pv210——LCD的原理和实战
  6. 项目开发日志:Build AssetBundle——SpriteAtlas(已解惑)
  7. vue中,点击button按钮后,页面上的input框再次自动获取焦点
  8. stopping hbasecat: /tmp/hbase-elastic-master.pid: 没有那个文件或目录
  9. hdu 2049 考新郎
  10. 12 哈希表相关类——Live555源码阅读(一)基本组件类