揭开内存的秘密,让一切真相大白

国家级庸才 2021-01-22 11:05:47
C语言是面向底层的语言,虽然语法和概念都很少,但却蕴含着丰富的底层知识,如果你在使用C语言时觉得语法你都会,却总是遇到这样那样的问题,并且为此一直迷惑不解,那么你欠缺的就是藏在语言下面的底层知识。要知道所有底层语言都是围绕内存工作的,因此搞懂内存是一个好的开始,本贴虽然不能为你解答所有的问题,但可以为你指引一个正确的方向。点击
https://blog.csdn.net/jaj2003/article/details/112993815
可以看到完成的内容,因为太长所以只能放入博客中。进入之前先掂量下自己的水平,因为看完只会有两种结果,对于有编程功底追寻语言背后真相的人,这篇帖子也许能让你获得重生,如果是小白来看热闹的,只会更快的将你劝退。本人花了很多时间整理,并且尽量写的通俗易懂,望珍惜。
...全文
525 6 打赏 收藏 转发到动态 举报
写回复
用AI写文章
6 条回复
切换为时间正序
请发表友善的回复…
发表回复
源代码大师 2021-05-06
  • 打赏
  • 举报
回复
希望对你有帮助:https://blog.csdn.net/it_xiangqiang/category_10581430.html 希望对你有帮助:https://blog.csdn.net/it_xiangqiang/category_10768339.html
  • 打赏
  • 举报
回复
国家级庸才 2021-01-31
  • 打赏
  • 举报
回复
文章第一句就是从设计概念上讲,这就排除了例外,寄存器是可以储存数据,但从设计目的来看是为了加快数据储存而不是用来保存数据,要说例外有的计算机连内存都没有,只有寄存器,cpu直接向硬件发送指令。不过你回复的内容可以作为本文内容的补充,幸苦了!想不到我的帖子引出了论坛中比我更老的程序员。

内存对齐算法其实很复杂,与cpu步长、平台、编译器都有关系,具体细节我没有写出来,既然你提出来了,我就来补充一下。每个平台上的特定编译器都有自己的内存对齐方案,以Win32平台下的微软C编译器为例,策略如下:
1)结构体变量的首地址是其最长基本类型成员的整数倍。
编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能是该基本数据类型的整倍的位置,作为结构体的首地址。将这个最宽的基本数据类型的大小作为对齐模数。
2)结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节。
为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。
3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节。

备注:
a、结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。
b、如果结构体内存在长度大于处理器位数的元素,那么就以处理器的倍数为对齐单位;否则,如果结构体内的元素的长度都小于处理器的倍数的时候,便以结构体里面最长的数据元素为对齐单位。
4) 结构体内类型相同的连续元素将在连续的空间内,和数组一样。

我们还可以为编译器设置编译参数来影响对齐策略,程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变对齐系数,其中的n就是上面讲的默认对齐系数。取值规则为:
1、结构的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
2、结构体的整体对齐规则:在数据成员完成各自对齐之后,结构体本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

概括来说内存对齐策略是综合数据类型、cpu步长还有编译器参数三方面得出的结果,其实了解内存对齐对写C代码没有很大的帮助,只对写编译器的人有帮助,《C++程序设计精要教程》是一本好书,但也没讲的那么细,详情可以查阅相关编译原理的书籍。最后文章若还有疏漏之处,欢迎补充。
maguangzhi 2021-01-26
  • 打赏
  • 举报
回复
引用 楼主 jaj2003的回复:
C语言是面向底层的语言,虽然语法和概念都很少,但却蕴含着丰富的底层知识,如果你在使用C语言时觉得语法你都会,却总是遇到这样那样的问题,并且为此一直迷惑不解,那么你欠缺的就是藏在语言下面的底层知识。要知道所有底层语言都是围绕内存工作的,因此搞懂内存是一个好的开始,本贴虽然不能为你解答所有的问题,但可以为你指引一个正确的方向。点击
https://blog.csdn.net/jaj2003/article/details/112993815
可以看到完成的内容,因为太长所以只能放入博客中。进入之前先掂量下自己的水平,因为看完只会有两种结果,对于有编程功底追寻语言背后真相的人,这篇帖子也许能让你获得重生,如果是小白来看热闹的,只会更快的将你劝退。本人花了很多时间整理,并且尽量写的通俗易懂,望珍惜。
作者辛苦了,写的很不错,道出了一般情形。但凡事皆有例外:(1)关于Cpu不能存取数据,或换句话说,内存不是Cpu的一部分,例外便是我国自主设计生产的DJS 130计算机,它采用部分内存作为寄存器,而寄存器是Cpu的一部分,所以Cpu也是可以存取数据的。(2)关于栈的操作,写的是X8086体系结构,事实有的计算机根本没有栈,根本设有push和pop指令。如IBM SYS 390系列机,它的栈是通过内存模拟的,想多大有多大,且运行中能自动检测是否溢出,且可以自动增长。(3) 即使是X8086体系也有不同之处,栈的大小在程序运行时可以设置,当然也可以在编译通过编译参数设置。例如borland编译器曾提供一个外部变量,用于在运行时设置栈的大小,格式为extern int _stklen=栈大小,这也是一种特殊的变量定义而非变量说明,其引入C++的理由正是由于有上述类似需求存在,变量定义要素参见《C++程序设计精要教程》。(4)函数参数并非全部自右向左入栈,这只是大部分编译器的做法,事实上C国际标准自己都说,入栈顺序由编译器自行决定,所以在写程序时不提倡写f(x++,x++)之类的函数调用,由于参数入栈顺序不同导致结果不同,造成程序可移植性问题;这也导致C++编译规定,函数参数默认值的表达式不得包含同一参数表的参数,具体原因参见《C++程序设计精要教程》。(5)关于内存对齐按步长对齐不妥,确切地说是按数据成员的类型对齐,关于更复杂内存布局如包含虚函数和虚基类的情况参见《C++程序设计精要教程》。
  • 打赏
  • 举报
回复
辛苦了,有空看看当复习也不错。
lin5161678 2021-01-23
  • 打赏
  • 举报
回复
文字没看 实例代码扫了两眼 发现不少错误
#include <stdio.h>
int main()
{
	char *str = (char*) 0X000007FFF2E5FE2C4; //使用数值表示一个明确的地址
	str="abc";
	return 0;
}
对于支持64bit整型常量的编译环境来说 这段代码不存在会报错的地方 指针指向不合法的内存就不合法吧 你不比较不解引用 没什么关系 然后就是代码里面所有输入unsigned int类型的代码都写错
scanf("%ud",&len)
应该用 %u 而不是%ud 没有%ud 这个控制符 你这里是运气好d后面没有输入项所以没看出问题 你写一个 %ud%ud 就知道错了

69,371

社区成员

发帖
与我相关
我的任务
社区描述
C语言相关问题讨论
社区管理员
  • C语言
  • 花神庙码农
  • 架构师李肯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧