返回「计算机、信息技术与工程」

C语言内存模型

更多
Markdown 结构化数据
本文目录 23 个章节

C语言内存模型

创建时间:2020/10/6 18:54

  • C语言内存模型
    • C语言内存布局概述
      • 内存区域总结
      • C语言编辑运行机制与内存
      • 延申知识
        • PE文件格式
        • 可执行文件、目标文件、DLL
        • Windows NT
    • 三种内存分配方式对比
      •     * 从静态存储区分配
        
        • 在栈上创建
        • 从堆上分配(动态内存分配)
    • 关于堆区和栈区
      • 堆和栈的区别
      • 关于结构体或类的内存分配
        • 结构体的成员
    • 内存对齐(memory alignment)
    • 函数调用的过程——函数栈
    • 经典的堆栈的讲解
    • 相关语法
      • new和malloc的区别
      • 关于free
      • 例子
    • Ref

C语言内存布局概述

内存区域总结

Alt text

栈区(stack):由编译器自动分配与释放,存放为运行时函数分配的局部变量、函数参数、返回数据、返回地址等。其操作类似于数据结构中的栈。 堆区(heap):一般由程序员自动分配,如果程序员没有释放,程序结束时可能有OS回收。其分配类似于链表。 全局/静态存储区 (static .bss段和.data段):存放全局变量、静态数据、常量。程序结束后由系统释放。全局区分为已初始化全局区(data)和未初始化全局区(bss)。 常量区(.rodata段):存放常量字符串,程序结束后有系统释放。 代码区(.text段):存放函数体(类成员函数和全局区)的二进制代码。

C语言编辑运行机制与内存

在Windows平台下,高级语言写出的程序经过编译链接,最终会变成PE文件。当PE文件被装载运行后,就成了所谓的进程。 PE文件代码段中包含的二进制级别的机器代码会被装入内存的 代码区(.text) ,处理器将到内存的这个区域一条一条地取出指令和操作数,并送入运算逻辑单元进行运算;如果代码中请求开辟 动态内存 ,则会在内存的 堆区 分配一块大小合适的区域返回给代码区的代码使用;当函数调用发生时, 函数的调用关系等信息 会动态地保存在内存的 栈区 ,以供处理器在执行完被调用函数的代码时,返回母函数。

延申知识

PE文件格式

PE文件 (Portable Executable,可移植性可执行文件)是一种用于 可执行文件目标文件动态链接库 的文件格式,主要使用在32位和64位的Windows操作系统上。

“可移植的”是指该文件格式的通用性,可用于许多种不同的操作系统和体系结构中。

PE文件格式封装了Windows操作系统加载可执行程序代码时所必需的一些信息。这些信息包括动态链接库、API导入和导出表、资源管理数据和线程局部存储数据。在Windows NT操作系统中,PE文件格式主要用于EXE文件、DLL文件、.sys(驱动程序)和其他文件类型。

可执行文件、目标文件、DLL

可执行文件 指一种内容可 被电脑解释为程序 的电脑文件。通常可执行文件内,含有 以二进制编码的微处理器指令 ,也因此可执行文件有时称为 二进制档


目标文件 (object file)即存放目标代码的计算机文件,它常被称作 二进制文件(binaries)目标代码 (英语:object code)指计算机科学中编译器或汇编器处理源代码后所生成的代码,它一般由机器代码或接近于机器语言的代码组成。

目标文件是从 源代码文件 产生 程序文件 这一过程的 中间产物链接器 正是通过把目标文件链接在一起来生成可执行文件或库文件。

目标文件包含着机器代码(可直接被计算机中央处理器执行)以及代码在运行时使用的数据,如重定位信息,如用于链接或调试的程序符号(变量和函数的名字),此外还包括其他调试信息。


DLL (动态链接库, Dynamic-link library) 是微软公司在Windows操作系统中实现 共享函数库 概念的一种实现方式。这些库函数的扩展名是.DLL、.OCX(包含ActiveX控制的库)或者.DRV(旧式的系统驱动程序)。

Windows NT

Windows NT(Windows New Technology) 是美国微软公司1993年推出的纯32位操作系统 核心 。 Windows NT 3.1 ~ Windows 2000 ~ Windows XP ~ Windows Vista ~ Windows 10

Windows 2000 architecture

三种内存分配方式对比

从静态存储区分配

内存在程序编译的时候已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。

在栈上创建

在执行函数时,函数内局部变量的存储单元可以在栈上创建,函数执行结束时,这些内存单元会自动被释放。 栈内存分配运算内置于处理器的指令集,效率高,但是分配的内存容量有限。

从堆上分配(动态内存分配)

程序在运行的时候使用malloc或者new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。 动态内存的生命周期有程序员决定,使用非常灵活,但如果在堆上分配了空间,既有责任回收它,否则运行的程序会出现内存泄漏,频繁的分配和释放不同大小的堆空间将会产生内存碎片。

关于堆区和栈区

栈区(stack)用于存放局部变量、返回地址、函数的实参等,由编译器自动分配释放。栈的功能主要是函数调用、局部变量申请、函数参数传递所使用的空间,是为函数调用的实现提供一些保存、恢复操作。一般情况下栈空间都特别小。 在windows下,栈地址是向低地址扩展的数据结构,是一块连续的内存区域。栈顶的地址和栈的最大容量是系统预先规定好的,在windows下,栈的大小是2MB,而申请堆空间的大小一般小于2GB 栈存放的数据占空间比较小,而且栈内存不由程序员来管理,存放在栈里面的局部变量随着函数调用结束就自动释放了。

堆区(heap)是一个程序运行动态分配的内存区域。一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。 程序员通过new等方式创建的对象,一般会在堆区中分配内存。

我们平常说的GC,指的是对堆内存的回收。对内存的优化一般也指的是堆内存。

堆和栈的区别

管理方式 不同: 是由 编译器自动 申请和释放空间, 是需要 程序员手动 申请和释放; 空间 大小不同: 的空间是有限的,在32位平台下,VC6下默认为 1M 最大可以到 4G ; 能否产生 碎片 和数据结构中的栈原理相同,在弹出一个元素之前,上一个已经弹出了, 不会 产生碎片,如果不停地调用 malloc、free堆 造成内存 碎片很多生长方向 不同: 生长方向是 向上 的,也就是向着内存地址增加的方向, 刚好相反,向着内存 减小的方向 生长。 分配方式 不同: 都是 动态分配 的,没有静态分配的堆。 静态 分配和 动态 分配。 静态 分配是 编译器完成 的,比如局部变量的分配。 动态分配malloc 函数进行分配 ,但是栈的动态分配和堆是不同的,它的 动态分配是由编译器进行释放 ,无需我们手工实现。 分配效率 不同: 的效率比堆 很多。栈是机器系统提供的数据结构,计算机在底层提供栈的支持,分配专门的寄存器来存放栈的地址,压栈出栈都有相应的指令,因此比较快。 是由库函数提供的,机制很复杂,库函数会按照一定的算法进行搜索内存,因此比较慢。

栈区(stack)用于存放局部变量、返回地址、函数的实参等,由编译器自动分配释放。栈的功能主要是函数调用、局部变量申请、函数参数传递所使用的空间,是为函数调用的实现提供一些保存、恢复操作。一般情况下栈空间都特别小。 在windows下,栈地址是向低地址扩展的数据结构,是一块连续的内存区域。栈顶的地址和栈的最大容量是系统预先规定好的,在windows下,栈的大小是2MB,而申请堆空间的大小一般小于2GB 栈存放的数据占空间比较小,而且栈内存不由程序员来管理,存放在栈里面的局部变量随着函数调用结束就自动释放了。

栈区大小的限制,不同的资料上有不同的说法,有个文章说申请大小的限制是1或者2M,可以自己改,但是最大不超过8M

堆区(heap)是一个程序运行动态分配的内存区域。一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。 程序员通过new等方式创建的对象,一般会在堆区中分配内存。

我们平常说的GC,指的是对堆内存的回收。对内存的优化一般也指的是堆内存。

嗯,这段描述基本适用于多数语言了。不过c# 函数里new的struct对象(值类型)一般情况都是在栈上的,和c++不一样。然后堆的空间在64位下就没有2G或者4G的限制了。

关于结构体或类的内存分配

string str1 = "test";
string str2 = "test";

dog1在栈中 dog2指向堆中的结构体

  • 函数内部声明的局部变量的内存由编译器在栈中自动分配与释放,函数结束时栈会回收,局部变量也随之销毁。如果局部变量为类对象,其析构函数会被自动调用。

  • 若使用new/malloc分配内存,则该内存在堆上,需要由程序员释放。 如以下语句:

string str1 = "test";
string str2 = "test";

这行语句的含义是,使对象obj具有“自动存储(automatic storage)”的性质。所谓“自动存储”,意思是这个对象的存储位置取决于其声明所在的上下文。如果这个语句出现在函数内部,那么它就在栈上创建对象。如果这个语句不是在函数内部,而是作为一个类的成员变量,则取决于这个类的对象是如何分配的。

结构体的成员

string str1 = "test";
string str2 = "test";

使用方法如下:

  • unit_t1申请了 空间,不需要自己去考虑使用后释放空间的事情。

  • unit_t2中申请了 空间,需要自己释放。

string str1 = "test";
string str2 = "test";

内存对齐(memory alignment)

操作的内存地址(更准确的说,操作内存的起始地址)能够被操作的内存大小整除,满足这个要求的内存访问叫做访问对齐的内存(aligned memory access),否则就是访问未对齐的内存(unaligned memory access)。 https://www.cnblogs.com/zhao-zongsheng/p/9099603.html

函数调用的过程——函数栈

https://blog.csdn.net/m0_37717595/article/details/80368411

经典的堆栈的讲解

https://blog.csdn.net/yingms/article/details/53188974

相关语法

new和malloc的区别

  1. new 是 运算符 malloc是 库函数

  2. new和delete底层还是由malloc和free实现 的,区别在于new和delete在生成复杂数据类型时会执行两步: 分配内存调用构造函数 / 调用析构函数销毁内存

  3. malloc 申请的内存 没有类型 ,为(void*) 需要根据使用需求进行强制转换, new 不需要,返回 对应类型的指针

  4. new并不是每次都会调用构造函数,对于简单类型eg.int是不需要的。

关于free

  • 当不需要再使用申请的内存时,记得 释放 ,并 且指针=NULL ;释放后应该把指向这块内存的指针指向NULL, 防止程序后面不小心使用了它

  • free时候,指针不能发生偏移,原因是指针指向的是申请空间的首地址,而且申请的空间的大小在free源码结构体中有记录

例子

string str1 = "test";
string str2 = "test";

Ref

https://www.cnblogs.com/houjun/p/4909413.html https://www.cnblogs.com/qiuly/p/8576149.html http://www.360doc.com/content/12/0102/11/1299815_176623363.shtml https://www.zhihu.com/question/19729973 http://www.360doc.com/content/12/0102/11/1299815_176623363.shtml https://blog.csdn.net/yingms/article/details/53188974 https://www.cnblogs.com/cchHers/p/10010275.html https://zhuanlan.zhihu.com/p/78478567 https://www.zhihu.com/question/59238963 https://blog.csdn.net/cherrydreamsover/article/details/81627855 https://blog.csdn.net/buptzy/article/details/80149622 https://www.cnblogs.com/will-boot/p/3302936.html https://blog.csdn.net/feliciafay/article/details/9830005