C知识点总结静态、动态存储.docx

上传人:小飞机 文档编号:4883760 上传时间:2023-05-21 格式:DOCX 页数:18 大小:524.82KB
返回 下载 相关 举报
C知识点总结静态、动态存储.docx_第1页
第1页 / 共18页
C知识点总结静态、动态存储.docx_第2页
第2页 / 共18页
C知识点总结静态、动态存储.docx_第3页
第3页 / 共18页
C知识点总结静态、动态存储.docx_第4页
第4页 / 共18页
C知识点总结静态、动态存储.docx_第5页
第5页 / 共18页
亲,该文档总共18页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述

《C知识点总结静态、动态存储.docx》由会员分享,可在线阅读,更多相关《C知识点总结静态、动态存储.docx(18页珍藏版)》请在三一办公上搜索。

1、几个问题:局部变量和静态变量的存储*1,33间;下面的初始化有什么同姬蝙译嚣提示invalid initializers或其他信息.Char *p = Enalloc (10 t答:这个声明是静态或非局部变量吗?函数调用只能出现在自动变量即局部非静态变量的初 始式中.为什么左边的会打印出乱码,而右边的 可以正常输出char * getmemory()char str=”hello world”;return str;int main()char *ptr=getmemory();printf(sn”,ptr);return 0;main栈帧ptrprintf 栈帧printf会占用以前getm

2、emory的栈空间, 改写这块内存。导致这里的字符串被改 写。printf打印时出现乱码。void fun(char *p) int a10;printf(sn”,p);char * getmemory()char str=”hello world”; return str;int main()char *ptr=getmemory();fun(ptr);return 0;fun函数占用了 getmemory的栈空间, 但是fun的局部变量数组a因为没有初 始化,所以只为a分配了内存,却没有 修改a占用的内存;该内存的内容还是 以前的内容。效果就是,a把字符串 “hello world” 保护

3、起来。使得 printf即局部变量可以用类型相符的任意表达式来初始化,而全局变量和静态变量只能用常量表达 式(Constant Expression)初始化。因为对它们的初始化是在编译阶段完成的。为什么不应返回指向局部若多次调用函数,为什么b=3递归函数的变量n,有几个?变量的指针。都不再执行了?void f(int n)char * itoa()fun()if (n 其首地址为0,其余指令中的地址都相对于首地址来编址。 不能用逻辑地址在内存中读取信息。例:用objdump反汇编a.out,查看,编译时,给指令和数据分配的地址。见例题。物理地址(绝对地址,实地址):物理内存中存储单元的地址。物

4、理地址可直接寻址。地址映射:将用户程序中的逻辑地址转换为运行时由机器直接寻址的物理地址。当程序运行,即需要装入内存时 操作系统要为该程序分配一个合适的内存空间,由于 程序的逻辑地址与分配到内存物理地址不一致,所以当cpu访问内存时,先要进行地址转换。页表:记录逻辑地址和物理地址的对应关系。内存管理单元MMU:该硬件读取页表,进行地址映射和内存保护。如果处理器启用了 MMU,CPU执行单元发出的内存地址将被MMU截获,从CPU到 MMU的地址称为虚拟地址(Virtual Address,以下简称VA),而MMU将这个地址翻译成另 一个物理地址(Physical Address,以下简称PA)发到

5、CPU芯片的外部地址引脚上,也就是 将VA映射成PA。如下图所示。程序的虚拟地址范围与系统有关。这个范围的大小由CPU的地址总线位数决定,例如 一个32位地址总线的CPU,它可寻址的地址范围是00xFFFFFFFF (4G),而对于一个64位的 CPU,它的地址范围为00xFFFFFFFFFFFFFFFF (64T)。这个范围就是我们的程序能够使用的地 址范围,我们把这个地址范围称为虚拟地址空间,该空间中的某一个地址我们称之为虚拟地 址。对于一台内存为256M的32bit x86主机来说,它的虚拟地址空间范围是00xFFFFFFFF (4G),而物理地址空间范围是 0x0000000000x0

6、FFFFFFF(256M)。现代的操作系统使用一种分页(paging )的内存管理机制。虚拟地址空间划分成称为页 (page)的单位,而相应的物理地址空间也被进行划分,单位是页桢(frame)。页和页桢的大 小必须相同。例如,若页的大小为4K,则页桢大小也为4K这点是必须保证的,而且内 存和外围存储器(磁盘等)之间的传输总是以页为单位的。linux使用了段页式的内存管理,吸收了段式和页式的优点。(见ppt的解释)对于4G的虚拟地址和256M的物理存储器,他们分别包含了 1M个页和64K个页桢。 页表就是记录页与物理内存的页帧的对应关系的。注意:并不是每个页都对应一个页帧。若 一个页不对应任何页

7、帧,则该页不可访问,试图去访问就会出现常见的段错误。例如,linux 系统保证对0号页及附近的一些虚拟页不对应任何物理页帧,即虚地址0不对应任何物理 地址,因此对虚地址0的访问会出现段错误。空指针NULL每个进程都有独立的虚拟地址空间。以32bit x86主机来说,每个进程都有4G的虚拟地 址空间。这个功能是通过页表(将不同进程相同的虚拟地址映射到不同的物理地址上)来实 现的。例如,进程A在地址0X8049000写一个字符m,进程B同样在地址0X8049000写字 符n,如果A进程读地址0X8049000,读到的是进程A写入的m,而进程B读取地址 0X8049000时,则读到的是B写入的n。两

8、者不会冲突。这里,进程A的虚拟地址0X8049000 和进程B的虚拟地址0X8049000对应的物理地址不同,因此他们之间没有干扰。例如:例题演示,两个进程,变量的地址相同,在两个进程里修改这个变量,他们彼 此不干扰。地址映射机制的产生,使得只有对应有物理页帧的虚拟页才是可访问的;实现不同进 程的虚拟页对应同一个物理页帧,达到内存共享的目的。进程弛址空间是独立的ba sh pi d=2 9 977MMU将VA映射到PA是以页(Page)为单位的,32位处理器的页尺寸通常是4KB。例 如,MMU可以通过一个映射项将VA的一页0xb70010000xb7001fff映射到PA的一页 0x20000

9、x2fff,如果CPU执行单元要访问虚拟地址0xb7001008,则实际访问到的物理地址 是0x2008。物理内存中的页称为物理页面或者页帧(Page Frame)。虚拟内存的哪个页面映 射到物理内存的哪个页帧是通过页表(Page Table)来描述的,页表保存在物理内存中,MMU 会查找页表来确定一个VA应该映射到什么PA。操作系统和MMU是这样配合的: 操作系统在初始化或分配、释放内存时会执行一些指令在物理内存中填写页表,然后 用指令设置MMU,告诉MMU页表在物理内存中的什么位置。 设置好之后,CPU每次执行访问内存的指令都会自动引发MMU做查表和地址转换操 作,地址转换操作由硬件自动完

10、成,不需要用指令控制MMU去做。我们在程序中使用的变量和函数都有各自的地址,程序被编译后,这些地址就成了指令 中的地址,指令中的地址被CPU解释执行,就成了 CPU执行单元发出的内存地址,所以在 启用MMU的情况下,程序中使用的地址都是虚拟地址,都会引发MMU做查表和地址转换 操作。那为什么要设计这么复杂的内存管理机制呢?多了一层VA到PA的转换到底换来了 什么好处?All problems in computer science can be solved by another level of indirection.计算机科学的所有问题都可以通过增加一个抽象层来解决。在32位系统上的进程

11、执行于32位地址空间。操作系统负责具体细节。使得每个进程都 以为自己拥有整个地址空间的独家访问权。这个幻觉是通过“虚拟内存”实现的。所有的进 程共享同一个物理内存。当内存用完时就用磁盘保存数据。在进程运行时,数据在磁盘和内 存之间来回移动。内存管理硬件MMU负责把虚拟地址翻译为物理地址,并让一个进程运行 于系统的真正内存中。应用程序员只看到虚拟内存,并不知道自己的进程在磁盘和内存之间 来回切换。除非他们诸如“ps 之类的系统命令。命令ps aux有两列分别显示虚拟内存和物理内存的使用大小。MMU除了做地址转换之外,还提供内存保护机制。各种体系结构都有特权模式(Privileged Mode,也

12、称内核态)和用户模式(User Mode, 也称用户态)之分。intel有四种工作模式(0-3),linux只使用0和3两种模式,分别对应 特权模式,和用户模式。操作系统可以在页表中设置每个内存页面的访问权限,有些页面不允许访问,有些页面 只有在CPU处于特权模式时才允许访问,有些页面在用户模式和特权模式都可以访问,访 问权限又分为可读、可写和可执行三种。例如:用命令pmap显示一个进程内存映像。这样设定好之后,当CPU要访问一个VA时,MMU会检查CPU当前处于用户模式还是 特权模式,访问内存的目的是读数据、写数据还是取指令,如果和操作系统设定的页面权限 相符,就允许访问,把它转换成PA,否

13、则不允许访问,产生一个异常(Exception)0异常的 处理过程和中断类似,不同的是中断由外部设备产生而异常由CPU内部产生,中断产生的 原因和CPU当前执行的指令无关,而异常的产生就是由于CPU当前执行的指令出了问题, 例如访问内存的指令被MMU检查出权限错误,除法指令的除数为0等都会产生异常。处理器模式32位x86平台下,进程的4G虚拟地址空间又分为用户空间和系统空间。用户空间:00Xbfffffff的地址空间,用于存放用户的程序和数据。系统空间:0xc00000000xffffffff的地址空间,用于存放内核和内核数据。(每个进程的 系统空间的内容基本相同,通过地址映射来实现物理内存共

14、享的。)用户程序加载到用户空间,在用户模式下执行,不能访问内核中的数据,也不能跳转到 内核代码中执行。这样可以保护内核,如果一个进程访问了非法地址,顶多这一个进程崩溃, 而不会影响到内核和整个系统的稳定性CPU在产生中断或异常时不仅会跳转到中断或异常 服务程序,还会自动切换模式,从用户模式切换到特权模式,因此从中断或异常服务程序可 以跳转到内核代码中执行。事实上,整个内核就是由各种中断和异常处理程序组成的。总结 一下:在正常情况下处理器在用户模式执行用户程序,在中断或异常情况下处理器切换到特 权模式执行内核程序,处理完中断或异常之后再返回用户模式继续执行用户程序。段错误我们已经遇到过很多次了,

15、它是这样产生的:1. 用户程序要访问的一个VA,经MMU检查无权访问。2. MMU产生一个异常,CPU从用户模式切换到特权模式,跳转到内核代码中执行异常服 务程序。3. 内核把这个异常解释为段错误,向该进程发送一个信号(signal),把引发异常的进 程终止掉。虚拟内存管理的作用,可以从以下四个方面来理解。第一,虚拟内存管理可以控制物理内存的访问权限。物理内存本身是不限制访问的,任 何地址都可以读写,而操作系统要求不同的页面具有不同的访问权限,这是利用CPU模式 和MMU的内存保护机制实现的。例如,Text Segment代码段被只读保护起来,防止被错误 的指令意外改写,内核地址空间也被保护起

16、来,防止在用户模式下执行错误的指令意外改写 内核数据。这样,执行错误指令或恶意代码的破坏能力受到了限制,顶多使当前进程因段错 误终止,而不会影响整个系统的稳定性。第二,虚拟内存管理最主要的作用是让每个进程有独立的地址空间。所谓独立的地址空 间是指,不同进程中的同一个VA被MMU映射到不同的PA,并且在某一个进程中访问任何 地址都不可能访问到另外一个进程的数据,这样使得任何一个进程由于执行错误指令或恶意 代码导致的非法内存访问都不会意外改写其它进程的数据,不会影响其它进程的运行,从而 保证整个系统的稳定性。另一方面,每个进程都认为自己独占整个虚拟地址空间,这样链接 器和加载器的实现会比较容易,不

17、必考虑各进程的地址范围是否冲突。动态库.so shared object 共享目标使用共享库可以大大节省内存。比如libc,系统中几乎所有的进程都映射libc到自己的 进程地址空间,而libc的只读部分在物理内存中只需要存在一份,就可以被所有进程共享, 这就是“共享库”这个名称的由来了。现在我们也可以理解为什么共享库必须是位置无关代码了。比如libc,不同的进程虽然 共享libc所在的物理页面,但这些物理页面被映射到各进程的虚拟地址空间时却位于不同的 地址,所以要求libc的代码不管加载到什么地址都能正确执行。第三,VA到PA的映射会给分配和释放内存带来方便,物理地址不连续的几块内存可以 映射

18、成虚拟地址连续的一块内存。即虚拟地址连续,物理地址不一定连续。比如要用malloc 分配一块很大的内存空间,虽然有足够多的空闲物理内存,却没有足够大的连续空闲内存, 这时就可以分配多个不连续的物理页面而映射到连续的虚拟地址范围。如下图所示。不连续的PA可以映射为连续的VA第四,一个系统如果同时运行着很多进程,为各进程分配的内存之和可能会大于实际 可用的物理内存,虚拟内存管理使得这种情况下各进程仍然能够正常运行。因为各进程分 配的只不过是虚拟内存的页面,这些页面的数据可以映射到物理页面,也可以临时保存到磁 盘上而不占用物理页面,在磁盘上临时保存虚拟内存页面的可能是一个磁盘分区,也可能是 一个磁盘

19、文件,称为交换设备(Swap Device)。当物理内存不够用时,将一些不常用的物理 页面中的数据临时保存到交换设备,然后这个物理页面就认为是空闲的了,可以重新分配给 进程使用,这个过程称为换出(Page out)。如果进程要用到被换出的页面,就从交换设备再 加载回物理内存,这称为换入(Pagein)。换出和换入操作统称为换页(Paging)。下面是一个linux的4G空间的典型布局。其中阴影部分代表未被使用。(深入理解计 算机系统第九章)对每个进程J都不相同与进程相关的数据结构(例如,页表、和mm结构,内核柱内核虚拟存储器对每个进程J 都一样物理存储器内核代码和数据用户栈共享库的存储器映射区

20、域运行时堆(通过malloc分配的)未初始化的数据(上瞬) 已初始化晚据(:归3) :食序文件(. Bxt )005048000(32)0x40000000(64)图如26 个Linux进程的虚拟存储器下面对各个段介绍。linux系统对不同的段赋予不同的权限。.text文本段:包含程序的指令,这个段是可读和可执行的。在32位系统中,文本段固 定存放在虚拟地址0X8048000开始的地方。.data已初始化数据段:存放已经初始化的全局变量和静态变量。.bss未初始化数据段:存放未初始化的全局变量和静态变量。.data段和.bss段统称为数据段,数据段在文本段的下一个页开始的地方。数据段的下 个页

21、开始是堆,堆向上生长,即向高地址方向延伸。栈(又称为堆栈段):用于函数调用,存放局部变量,传递参数,返回地址等。在接近 系统空间的地方,栈向下生长,栈的大小是有限制的,一般12M大小。例如:写代码估计linux下堆的最大值。并用ps命令查看进程的内存使用情况,命令free查看物理内存的使用情况。下面是C源文件和可执行程序的对应关系。C语句的各部分会出现在哪些段中a.out丈件.局部霓址井不进 aoiit,它们 T 任运行时驾建.文本段 static long melon ?= 2001 ;| ip = mall jc(xi7rof(i);pear5= i;iJ peach = 1.0 * in

22、ango;BSS段所需的大小 vartia i数据段卜初始化际的全局和推志变星a.out神奇数字a.nilL的其他内容皿御() L _、 一 S H9一=inr i s 3 *ip;char pear40|;I ,;static double peach;:a.out神奇数字ta.OllL的其他内容BSS段顶i需的大小aia数据段h 初始化后的全局和禅志变鼠文本段* 可执行文件的指争这神类型的C源丈件遂入这个幔通过编洋器期详可执行文件和内存中的进程各段的对应关系。(C专家编程第六章)例子:用命令size查看一个a.out文件的各段大小。修改你的源文件,增加一个未初始 化的int a1O0擞组,

23、编译后用size看看。给数组a初始化一些非零值,编译后再用size 查看各段的大小变化。若把该数组放到main函数里呢?由上可知,全局变量和静态变量的值,地址和占用空间的大小,在编译时就已经确定; 根据是否初始化,分别存储在.data段和.bss段。栈的典型分布(深入理解计算机系统8.4.5节)name=value图&21当一个新的程序开始时,用户栈的典型组织结构(实际上,一个进程的栈段的栈底不在0xbfffffff,出于安全的考虑,在进程创建时, 系统在0xbfxxxxxx的虚拟地址范围内取一个地址作为栈的栈底。)bf000000 bfffffffchar *argvargvargv arg

24、v 。 argvl图419参数列表的组织结构ecvp 图8-2。环境变量列表的组织结构栈的特点:一块连续的虚拟地址空间,实现了 “后进先出”的结构。类似于叠在一起的 盘子,只能从顶部取出。栈段的用途:1)栈为函数内部声明的局部变量提供存储空间。2)进行一次函数调用时,系统在栈顶分配一段空间(通常大小为16字节的倍数),存储与 该函数调用相关的一些维护性信息。这些信息称为栈结构或栈帧(即是上面例子里的盘 子)。这些信息包括函数调用地址(即当被调用的函数结束后,调用函数接下来需要执 行的语句的地址,这个入栈操作由call指令完成)、任何不适合装入寄存器的参数(比 如参数需要的存储空间大或需要对参数

25、取地址。因为寄存器的访问速度快,并不是参 数都要放到栈中)以及一些寄存器值的保存(例如,上一个栈帧空间的起始地址,即 寄存器ebp的值),为了重用寄存器;以及1和3里提到的用途。3)栈可被用作暂时存储区。有时候程序需要一些临时存储,比如计算一个很长的算术表达 式时,可以把中间结果暂时存储到栈中,用的时候再取出。由上可知,栈主要用于函数调用,因此又称函数栈。对函数调用的分析,需要知道系 统如何利用栈,来跟踪函数之间的调用关系;当函数执行return语句后,控制将返回到哪 里;当产生函数调用时(例如调用函数fun),系统给被调用的函数(fun)分配一个栈帧, 在这里可以记录上面提到三方面的信息。地

26、址增大栈指时%esp - - - -*返回地址被保存的牝g破舜存的苛存器、 本地变量和 临时斐鱼栈底+4+4n较早的威祯用者的技当前也+4慎指针校顶图3-1 栈帧结构校用来传递参数、存储返回悟息、保存寄存器.以及本地存储9几个重要寄存器:eip:指令寄存器,记录将要执行的下一条指令的地址。也称为pc寄存器ebp:记录当前栈帧的起始地址esp:记录当前栈的栈顶地址eax:在函数返回时,可存放返回值(前提是,返回值能放到4个字节里。否则,返回 值需要放在栈里。)对于下面的代码,当函数caller调用swap_add时,caller的函数栈空间如右图所示。这里演示了一个典型的栈帧的分布。保存寄存器局

27、部变量空间区未使用空间(出于对齐的考虑)参数构造区(被调用函数的形参空间)返回地址例题:验证保存的返回地址就是调用函数的下一条指令的地址分析寄存器eax和返回值有关。该分布图解释了执行下面这条语句为什么会出现段错误。void fun()(int a10;a10=1;进入一个函数体前,保存以前用过的ebp,和某些通用寄存器例如ecx。紧挨着的是局 部变量的存储空间。若该函数内进行了函数调用,在栈顶,把所有不适合放到寄存器的实参值按照从右向 左顺序,依次入栈(这其实就是个形参初始化的操作),这些存储空间就是被调用函数形参 的存储空间。为何不能从函数中返回一个指向该函数局部变量的指针?函数返回后,给

28、它分配的栈帧空间的访问合法性就没有了;系统会把这部分空间再分 配,作为后来调用的函数的栈帧空间。main ()printf();scanf();fun();main的栈帧 fun的栈帧第一页的例子int a;a的作用域在该函数内,只要a的内存合法,即a所在的函数还没执行完。就可以把a 的地址赋给其他函数使用。注意,局部变量的地址作为返回值和作为实参传递给其他函数不同。若想返回一个指向在函数内部定义的变量的指针,可以把那个变量声明为static。这样 该静态局部变量就的存储位置在数据段中而不是栈中了。(在高级编程中有一些函数会使用 此法来返回指向静态局部变量的值。)例如:写代码估计linux下栈

29、的最大值。分析递归程序每次函数调用都重新给局部变量分配空间,局部变量的初始化操作在运行时进行。而静 态变量的初始化操作在编译时。函数间接或直接地调用本身。例子演示:打印顺序。int at=5;void fun()int b;printf(局部变量b的地址:dn,&b);-at;if(at0)fun();void a(int i)(if(i=0)printf(i has reached zeron);elseprintf( i=%d n”, i);a(-i);return;void main()a(1);/或 fun();分析方法:按照函数栈的分布写,或表格列出每一次的调用。例如:写代码估计li

30、nux下栈的最大值。一道大华笔试题递归函数的分析。ini main( ini urge; ( hai * argvl ) -ini a = 3:printf(func( 1 );return 0:I请问其运行结束时的打印信息是malloc free的基本原理malloc和free是C标准库函数。在linux下,malloc根据申请的内存大小不同,通过调用mmap或者brk系统调用向操作系 统申请内存。free通过munmap或者brk系统调用,向操作系统返还内存。free如何根据用户提供的地址释放空间的:1、malloc分配空间时,会额外分配4个字节的内存,用于记录分配空间的大小以及标记是 否

31、被使用。在linux32系统,malloc分配空间大小为8的倍数,尾部可以有填充,最小分配 16个字节;malloc返回的地址为8的倍数。2、free释放的空间前面提到,malloc通过系统调用向系统申请内存。因为系统调用会有从用户模式到特权模式 的代价。因此,malloc申请后的内存,用free释放后,并不一定立即返还给系统。通常释 放的空闲内存,先由malloc和free维护的空闲链表来记录。以备该进程将来的内存请求(即,将来再通过malloc申请空间,malloc会先到空闲链表里查询是否已经有足够大的空 闲内存,若有,把该内存地址返回,标记该内存已使用;若没有足够大的空闲内存,再向操 作系统申请。)

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 生活休闲 > 在线阅读


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号