重学C#之前传——由C/C++开发经验总结的程序内存分配知识

    最近一位写单片机嵌入式程序的同事因为工作需要,要学习C#开发应用层程序。然而她学习的角度很独特,不同于一般语言学习者的学习流程:优先学习语法,了解how之后先投入开发,在随着经验的积累去深究why。而是每学习一项语法,都去关心它在内存上的分配,直接考虑最底层,甚至对于C#转为CLR后包装的逻辑都不甚关心。而因为纯粹的C#使用者一般不甚了解此类知识,所以C/C++和C#都有开发经验的我被要求共同探讨学习。在帮她解答一些问题中也蜀都赋积累了很多知识,故为文以记之。后之览者,亦将有感于斯文。

    首先,写一些和内存相关的预备知识。

    对于一个程序而言,内存空间主要由五个部分组成:代码段(.text)、数据段(.data)、BSS段(.bss)、堆和栈。其中代码段、数据段和BSS段时编译时编译器分配的,这些和程序的一些资源都被打包在生成的可执行二进制文件中(我第一次验证这个问题是在将windows的*.exe文件拷贝到linux系统,忘记使用wine执行时发现的,此时程序被归档管理器打开,清楚的看到.data、.text和其他资源文件等组成,并没有.bss段,后面分析),这部分地址控件都是编译器在链接时分配的。堆区和栈区的内存控件则是在程序运行时由操作系统分配。

    Bss segment(Block Started by Symbol):通常用来存放程序中的未初始化的全局变量和静态变量的内存区域。BSS段属于静态内存分配。BSS节不包含任何数据,只是简单的维护开始和结束的地址,即总大小,以便内存区能在运行时分配并被有效地清零。bsssegment在生成的二进制文件中并不存在,也就是不占用磁盘空间,只在运行时占用内存空间,不难得出全局变量和静态变量未初始化时,可执行文件大小要比初始化后的文件小。

    这里要说明的是,变量的初始化分为隐式初始化和显式初始化,隐式初始化时会给变量赋值为0,因为都是0没有存储很多0的必要,bss段存放未初始化的全局变量并不存储每一个0,这样也节省了内存空间。

    Data segment:存放已经初始化的全局变量和静态变量。

    Text segment:代码段,顾名思义存放执行代码的空间。

    Heap:堆区,malloc或者new分配的内存空间。(linux开发中遇到过一个问题,signal软中断的线程使用堆上的变量程序会挂掉。因为malloc会给内存上锁,而signal在上下文切换时有最高优先级,如过malloc刚给内存上锁,而切换到signal软中断的线程中使用malloc的话,等的锁永远不会释放)除了没有free或者delete而造成内存泄露,频繁的分配释放还可能产生大量的内存碎片,降低内存使用效率。

    Stack:局部变量分配在栈上,作用域为一对“{}”,由操作系统控制分配释放。分配给每个程序的栈的大小是固定的,windows默认一般为1-2m,linux为8m,可以根据需要修改。栈上的变量操作效率高,空间有限。


    操作系统内存布局

    32位经典内存布局



    32位系统经典内存布局如上图,起始的1GB地址为内核空间,接下来向下增长的为栈空间和向上增长的mmap。堆区从代码段数据段bss段后向上增长。这个布局据说有几个问题,首先容易遭到溢出攻击;其次堆地址空间只有1Gb不到,如果mmap匹配的内存使用比较少会有内存浪费,所以就有了下面的内存布局。



    这幅图中首先是加入了randomoffset的随机偏移,导致内存溢出攻击不那么容易了,其次是堆向上增长,而mmap想下增长,减少了mmap使用少时导致的内存浪费。但是栈的空间就是固定大小了,如前文所述,在windows中使用/STACK宏,linux中使用ulimit可以查看和修改栈区的大小。

    64位内存空间布局,平时开发不使用,暂不讨论。

    无脑抄的一段:(在比较ptmalloc、tcmalloc、jemalloc时看到的)

    对于大内存malloc直接mmap。而对于小数据,则通过向操作系统申请扩大堆顶,这时候操作系统会把需要的内存分页映射过来,然后再由这些malloc管理这些堆内存块,减少系统调用。而在free内存的时候,不同的malloc有不同的策略,不一定会把内存真正的还给系统,所以很多时候,如果访问了free掉的内存,并不会立即runtimeerror,只有访问的地址没有对应的内存分页,才会崩溃。


    最后说一点,在最近的linux开发项目中,本来想自己写内存池,然而,无论开发效率还有使用效果都是不可控的,所幸目前使用默认的malloc并没有造成性能瓶颈,由于线程并不多,使用的数据结构也不算大。然而在高并发中分配较大的数据结构时,为了减少分配时的锁的争用,自己维护内存池还是相当有实践意义的。
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值