36.C程序内存映射和存储类关键字


36.1.相关概念解析
(1)存储类即存储类型,就是描述C语言变量在何种地方存储;内存有多种管理方法譬如栈、堆、数据段、bss段、.text段等,某个变量的存储类属性即描述该变量存储在何种内存段中;譬如局部变量分配在栈上,则它的存储类就是栈;显式初始化为非0的全局变量分配在数据段;显式初始化为0和没有显示初始化(默认为0)的全局变量分配在bss段。
(2)作用域即描述该变量起作用的代码范围;基本来说,C语言变量的作用域规则是代码块作用域,即该变量起作用的范围是当前的代码块;代码块就是1对大括号{}括起来的范围,则某个变量的作用域是该变量定义所在的{}范围内从该变量定义开始往后的部分。
(3)声明周期即描述该变量什么时候诞生(运行时分配内存空间给这个变量)及什么时候死亡(运行时收回该内存空间)的;研究变量的生命周期可以帮助我们理解程序运行的某些现象和理解C语言的某些规则。
(4)链接属性即程序在链接过程中把符号和相对应的段链接在一起时的属性;程序从源代码到最终可执行程序经历的过程是编译+链接;编译阶段即将源代码搞成.o目标文件,目标文件里有很多符号+代码段+数据段+bss段等分段;符号即代码中的变量名或函数名等;程序运行时变量名+函数名能够和相应的内存地址对应起来是靠符号来做链接的;当.o的目标文件链接生成最终可执行程序时其实是把符号和相对应的段给链接起来;C语言中的符号有3种链接属性外连接属性+内链接属性+无连接属性。
(5)存储类+作用域+声明周期+链接属性是从4个不同角度来分析C语言的某些运行规则,综合这4种分析角度能够让程序员完全掌握C语言程序的运行规则和方法。


36.2.linux下C程序的内存映像
(1)代码段+只读数据段;代码段=对应着程序中的代码和函数(别名文本段.text);只读数据段=在程序运行期间只能读不能写的数据(const修饰的常量有可能是存在只读数据段的,因为const常量的实现方法在不同平台是不同的)。
(2)数据段+bss段;数据段=显式初始化为非0的全局变量+显式初始化为非0的static局部变量;bss段=显式初始化为0或者未显式初始化的全局变量+显式初始化为0或未显式初始化的static局部变量。
(3)堆内存;堆内存=C语言不会自动向堆中存放东西,堆的操作是程序员自己手工操作的,程序员会根据需求自己判断是否需要使用堆内存,用的时候自己申请并使用,使用完后自己释放。
(4)文件映射区;文件映射区=即进程打开了文件后,将该文件的内容从硬盘读到进程的文件映射区,以后就直接在内存中操作该文件,读写完了后在保存时再将内存中的文件写到硬盘中去。
(5)栈内存;栈内存=局部变量分配在栈上+函数调用传参过程也会用到栈。
(6)内核映射区;内核映射区=即把操作系统内核程序映射到该区域;对于linux中的每个进程来说它都以为整个系统中只有它自己和内核而已,它认为内存地址0xC0000000以下都是它自己的活动空间,0xC0000000以上是OS内核的活动空间;每个进程都活在自己独立的进程空间中,0-3G的空间每个进程是不同的(通过虚拟地址技术),但3G~4G的空间内核是唯一的(见图1)。


36.3.OS下和裸机下C程序加载执行的差异
(1)C语言程序运行时环境有特殊要求,即个人写的C语言程序没法直接在内存中运行,需要外部协助,该段协助代码叫加载运行代码(或者叫构建C运行时环境代码);该段代码在操作系统下是别人写好的并且会自动添加到我们写的程序上,这段代码的主要作用是给全局变量赋值+清bss段。
(2)在裸机程序中没人帮我们来做加载运行时代码动作,需要程序员自己做(start.S中的重定位和清bss段即在做这个事);在操作系统中运行程序时程序员自己不用操心,操作系统会自动完成重定位和清bss,则我们看到的现象是C语言中未初始化的全局变量默认为0等。
(3)数据段的全局变量或静态局部变量都是有非0的初值的,这些初值在main函数运行之前就已经被初始化了,初始化时间是重定位期间完成的初始化。


36.4.auto和static和register
(1)auto;其在C语言中只有1个作用即修饰局部变量;auto修饰局部变量则表明该局部变量是自动局部变量;自动局部变量分配在栈上,则该变量如果不初始化那么变量的值就是随机的;平时定义局部变量时就是定义成auto的,只是省略了auto关键字而已,则auto的局部变量是默认定义的普通的局部变量。
(2)static;其在C语言中有2种用法并且这两种用法彼此是完全是独立的,当年本应该多发明1个关键字,但C语言的作者觉得关键字太多不好,于是给static增加了1种用法,导致该关键字竟然有两种截然不同的含义;static的第1种用法是用来修饰局部变量形成静态局部变量,静态局部变量和全局变量的存储类(数据段/bss段)和生命周期(永久)相同,静态局部变量和全局变量的区别是作用域(静态局部变量-代码块作用域、全局变量-文件作用域)和连接属性(静态局部变量-无链接、全局变量-外链接)不同,静态局部变量和普通局部变量的作用域(代码块作用域)和链接属性(无链接)相同,静态局部变量和普通局部变量的区别是存储类(静态局部变量-数据段/bss段、普通局部变量-栈内存)和生命周期(静态局部变量-永久、普通局部变量-临时)不同;static的第2种用法是用来修饰全局变量,形成静态全局变量,静态全局变量和非静态全局变量的区别是链接属性不同。
(3)register;其在C语言中只有1个作用即编译器会尽量将其修饰的变量分配在寄存器中(平时分配的的变量都是在内存中的);分配在寄存器中使用方法相同但读写效率会高很多,register修饰的变量用在那种变量被反复高频率的使用,通过改善该变量的访问效率可以极大的提升程序运行效率的情况下,则register是1种极致提升程序运行效率的手段;uboot中使用了1个register类型的变量gd,该变量是用来存uboot的全局变量,因为该全局变量在整个uboot中到处都被访问,则使用register关键字修饰;平时写代码要被定义成register这种情况很少请慎重使用;register编译器只能承诺尽量将register修饰的变量放在寄存器中,但不保证一定放在寄存器中,主要原因是因为寄存器数量有限,不一定有空闲。


36.5.extern和volatile和restrict
(1)extern;其在C语言中主要用来声明全局变量,声明的目的主要是在a.c中定义全局变量而在b.c中使用该变量;C语言中程序的编译时以单个.c源文件为单位的,因此编译a.c时只考虑a.c中的内容(不会考虑b.c的内容),则导致a.c中使用了b.c中定义的变量时在编译时报错;则应该在a.c中使用g_b之前先声明g_b,声明就是告诉a.c我在别的文件中定义了g_b,并且它的原型和声明相同,将来在链接的时候链接器会在别的.o文件中找到该同名变量,声明某个全局变量要用到extern关键字。
(2)volatile;C语言中volatile用来修饰某个变量,表示该变量可以被编译器之外的东西改变;编译器之内的改变-变量的值的改变是代码的作用;编译器之外的改变-变量的值的改变不是代码造成的或者不是当前代码造成的,编译器在编译当前代码时无法预知,譬如在中断处理程序isr中更改了该变量的值,譬如多线程中在别的线程更改了该变量的值,譬如硬件自动更改了该变量的值;前面所说的3种情况(中断isr中引用的变量、多线程中共用的变量、硬件会更改的变量)都是编译器在编译时无法预知的更改,此时应使用volatile告诉编译器该变量属于易变的情况,编译器在遇到volatile修饰的变量时就不会对该变量的访问进行优化,就不会出现错误;编译器的优化在一般情况下可帮助提升程序效率,但在特殊情况(volatile)下,变量会被编译器想象之外的力量所改变,此时如果编译器没有意识到而去优化则就会造成优化错误,优化错误就会带来执行时错误并且该错误很难被发现;若遇到了应该加volatile的情况而没有加则程序可能会被错误的优化,若在不应该加volatile而加了则程序不会出错只是会降低效率,所以我们对于volatile的态度应要正确区分不同情况而确定是否添加,若不能确定该不该加为了保险起见就加上(见图2)。
(3)restrict;c99中才支持的关键字,则很多延续c89的编译器不支持restrict关键字,gcc支持该关键字;restrict也是和编译器行为特征有关的;restrict只用来修饰指针,修饰普通变量没有意义;参考博客链接http://blog.chinaunix.net/uid-22197900-id-359209.html


这里写图片描述


这里写图片描述


36.code_area
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 项目:C程序内存映射和存储类关键字
 * 功能:演示代码块作用域的细节。
 */

#include <stdio.h>

int var = 10;

int main(int argc, char **argv)
{
#if 1
    printf("In main first: var = %d.\n", var);          // In main first: var = 10.
    int var = 8;

    if (1)
    {
        int var = 5;
        printf("In if: var = %d.\n", var);              // In if: var = 5.
    }
    printf("After if: var = %d.\n", var);               // After if: var = 8.
#endif

#if 0
    int i = 0;

    for (i=0; i<10; i++)
    {
        int ret = 5;
        printf("In for: i = %d.\n", ret);
    }
    printf("Outside for: ret = %d.\n", ret);            //  error: ‘ret’ undeclared (first use in this function)
#endif  

    return 0;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值