第六章----运行时数据结构
一、段
1.含义:对于目标文件而言,它是二进制文件中简单的区域,里面保存了和某种特定类型(如符号表条目)相关的所有信息。
对于UNIX中:段表示一个二进制文件相关的内容块
对于intel x86中:段表示一种设计结果,即地址空间并非一个整体,而是分成一些64k大小的区域
2.一个程序(可执行文件.exe)是由BSS段、数据段、文本段所组成的
(1)
BSS段:指用来存放程序中未初始化的全局变量的一块内存区域,属于静态内存分配。
数据段:保存经过初始化的全局和静态变量以及它们的值,属于静态内存分配。
文本段:通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域属于只读。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
(2)数据段和BSS段的整个区段称为数据段,BSS只保存可执行文件中得到的大小,但不占可执行文件的任何空间。
(3)堆栈段:用于保存局部变量、临时数据、传递到函数中的参数,也就是auto自动变量。另外需要堆(heap) 空间 ,用于动态分配的内存,调用malloc()函数即可。
(4)虚拟地址空间的最低部分未被映射,它位于进程的地址空间内,但未被赋予物理位置,所以任何对它的引用都是非法的。
—它用于捕捉使用空指针和小整型值的指针引用内存情况
(6)查看堆栈的大致位置,数据段、BSS段
#include <stdio.h>
char a[10];
int p = 1 ;
main()
{
int i ;
printf("The 堆栈 is near %p\n",&i);
printf("The 数据段 is near %p\n",&p);
printf("The BSS段 is near %p\n",a);
return 0;
}
3.堆栈段
(1)它包含一种单一的数据结构:堆栈,“后进先出”,值可以压到堆栈中,也可以通过栈取出值。
(2)主要三个用途:
a.堆栈为函数内部声明的局部变量提供存储空间,这些变量是自动变量
b.进行函数调用时,堆栈存储与此有关的一些维护性信息,这些信息被称为堆栈结构,也就是过程活动记录。
c.堆栈也可以被用作暂时存储区,例如使用alloca()函数分配的内存就是在堆栈中,但是如果想要让内存在函数调用结束之后,仍然有效,那就不能用alloc来分配,因为它会被下一个函数调用所覆盖。
(3)在不同的系统中:
UNIX:当进程需要更多空间时,堆栈会自动增长。
------一般使用某种形式的虚拟内存,并且当试图访问当前系统分配给堆栈空间之外时,会产生一个硬件中断,被称为页错误!
MS-DOS:在建立可执行文件时,堆栈的大小必须同时确定,而且它不能在运行时增长
二、过程活动记录
1.含义:一种用于支持过程调用,并记录调用结束以后返回调用点所需要的全部信息的数据结构。
2.目的:跟踪调用链——那些函数调用了哪些函数,当下一个return语句执行后,控制将返回何处等问题。
过程活动记录的规范描述 |
---|
局部变量 |
参数 |
静态链接(用于上层引用,C语言不引用) |
指向先前的结构指针 |
返回地址 |
TIPS:
a.允许嵌套函数的语言中,活动记录一般包含一个指向它的外层函数的活动记录指针,这个指针被称为静态链接。
b.一个指向词法上外层范围的数据项的引用被称为上层引用。
c.C语言不运行嵌套函数!!
三、控制线程
----如何解决在进程中支持不同的控制线程
只需要简单为每个控制线程分配不同的堆栈即可
四、setjmp 和 longjmp
1.它们都是通过操纵过程活动记录实现的
(1)setjmp(jmp_buf j)必须首先被调用,它表示:使用变量j记录现在的位置,函数返回0;
setjmp保存了一份程序的计数器和当前的栈顶指针
(2)longjmp(jmp_buf j, int i)可以接着被调用,它表示:回到j所记录的位置,将导致这个被保护的状态重新恢复,但是返回i!
当使用longjmp的话 ,j的内容被摧毁;并且它只跳回曾经到过的地方。
(3)
#include <stdio.h>
#include <setjmp.h>
jmp_buf buf;
banana()
{
printf("in banana() \n");
longjmp(buf,1);
//以下代码不会显示
printf("you'll never see this , because i longjmp'd");
}
main()
{
if(setjmp(buf))
printf("back in main\n");
else{
printf("first time through\n");
banana();
}
}
tips:要保证局部变量在longjmp过程中保持值唯一的话可靠方法是把它声明为volatile!
2.两者最大的用途是,错误恢复,只要还没有从函数返回,一旦发现一个不可恢复的错误,可以把控制转移到主输入循环,并从那里重新开始。