存储类、作用域、生命周期、链接属性

1.linux下C语言程序的内存映像代码段(.text)、数据段(.data)、bss段、栈、堆的概念

代码段(.text)
(1)对应着程序中的代码(函数),代码段在linux中又叫文本段(.text)
(2)部分平台下的const修饰的变量。

数据段(.data)
1、显式初始化为非0的全局变量;

2、显式初始化为非0的static局部变量

bss段
1、显式初始化为0或者未显式初始化的全局变量;

2、显式初始化为0或未显式初始化的static局部变量。

局部变量分配在栈上;函数调用传参过程也会用到栈
用时需要手工申请。
文件映射区 文件映射区就是进程打开了文件后,将这个文件的内容从硬盘读到进程的文件映射区,以后就直接在内存中操作这个文件,读写完了后在保存时再将内存中的文件写到硬盘中去。
内核映射区
(1)内核映射区就是将操作系统内核程序映射到这个区域了。
(2)对于linux中的每一个进程来说,它都以为整个系统中只有它自己和内核而已。它认为内存地址0xC0000000以下都是它自己的活动空间,0xC0000000以上是OS内核的活动空间。
(3)每一个进程都活在自己独立的进程空间中,0-3G的空间每一个进程是不同的(因为用了虚拟地址技术),但是内核是唯一的。

2.存储类相关的关键字
(1)讲述存储类关键字auto、static、register,其中重点是static。

  auto关键字在C语言中只有一个作用,那就是修饰局部变量。默认就是auto

  static两种用法:

  第一种修饰局部变量为静态局部变量。

  1、静态局部变量在存储类方面和全局变量一样。(数据段或bss段)
  2、静态局部变量在生命周期方面和全局变量一样。(整个程序)
  3、静态局部变量和全局变量的区别是:作用域、连接属性。静态局部变量作用域是代码块作用域(和普通局部变量是一样的)、链接属性是无连接;全局变量作用域是文件作用域(和函数是一样的)、链接属性方面是外连接。

  第二种是修饰全局变量,函数名。表示只在当前文件下有用,用于避免重名。(修饰后的链接从外连接变成内链接)

  register:用register修饰的变量编译器优先分配到寄存器中。

(2)讲述存储类关键字extern、volatile、restrict、typedef,其中重点是extern和volatile。

  extern:在声明中用,表示这个变量在其他文件中定义。

  volatile:比较敏感,可能被外界环境改变的量。用这个修饰后可以避免编译器过度优化。

  typedef:

3.作用域

  1、局部变量的代码块作用域

  (1)代码块基本可以理解为一对大括号{}括起来的部分。
  (2)代码块不等于函数,因为if while for都有{}。所以代码块<=函数
  (3)局部变量的作用域是代码块作用域,也就是说一个局部变量可以被访问和使用的范围仅限于定义这个局部变量的代码块中定义式之后的部分。

  2、函数名和全局变量的文件作用域
  (1)文件作用域的意思就是全局的访问权限,也就是说整个.c文件中都可以访问这些东西。这就是平时所说的局部和全局,全局就是文件作用域。
  (2)详细准确的说:函数和全局变量的作用域是定义所在的整个.c文件之内定义式之后的部分。

  3、同名变量的掩蔽规则
  (1)问题:编程时,不可避免会出现同名变量。变量同名后不一定会出错。
  (2)首先,如果两个同名变量作用域不同且没有交叠,这种情况下同名没有任何影响。
  (3)其次,如果两个同名变量作用域有交叠,C语言规定在作用域交叠范围内,作用域小的一个变量会掩蔽掉作用域大的那个(县官不如现管)。

4.生命周期 

  栈变量的生命周期
  (1)局部变量(栈变量)存储在栈上,生命周期是临时的。临时的意思就是说:代码执行过程中按照需要去创建、使用、消亡的。
  (2)譬如一个函数内定义的局部变量,在这个函数每一次被调用时都会创建一次,然后使用,最后在函数返回的时候消亡。

  堆变量的生命周期
  (1)首先要明白:堆内存空间是客观存在的,是由操作系统维护的。我们程序只是去申请然后使用然后释放。
  (2)我们只关心我们程序使用堆内存的这一段时间,因此堆变量也有了自己的生命周期,就是:从malloc申请时诞生,然后使用,直到free时消亡。

  数据段、bss段变量的生命周期
  (1)全局变量的生命周期是永久的。永久的意思就是在程序被执行时诞生,在程序终止时消亡。
  (2)全局变量所占用的内存是不能被程序自己释放的,所以程序如果申请了过多的全局变量会导致这个程序一直占用大量内存。
  (3)如果说堆内存是图书馆借的书,那么全局变量就是自己买的书。

  代码段、只读段的生命周期
  (1)其实就是程序执行的代码,其实就是函数,它的生命周期是永久的。不过一般代码的生命周期我们并不关注。
  (2)有时候放在代码段的不只是代码,还有const类型的常量,还有字符串常量。(const类型的常量、字符串常量有时候放在rodata段,有时候放在代码段,取决于平台)

5.链接属性  

  1、C语言程序的组织架构:多个C文件+多个h文件
  (1)庞大、完整的一个C语言程序(譬如linux内核、uboot)由多个c文件和多个h文件组成的。
  (2)程序的生成过程就是:编译+链接。编译是为了将函数/变量等变成.o二进制的机器码格式,链接是为了将各个独立分开的二进制的函数链接起来形成一个整体的二进制可执行程序。
  2、编译以文件为单位、链接以工程为单位
  (1)编译器工作时是将所有源文件依次读进来,单个为单位进行编译的。
  (2)链接的时候实际上是把第一步编译生成个单个的.o文件整体的输入,然后处理链接成一个可执行程序。
  3、三种链接属性:外连接、内链接、无链接
  (1)外连接的意思就是外部链接属性,也就是说这家伙可以在整个程序范围内(言下之意就是可以跨文件)进行链接,譬如普通的函数和全局变量属于外连接。
  (2)内链接的意思就是(c文件内部)内部链接属性,也就是说这家伙可以在当前c文件内部范围内进行链接(言下之意就是不能在当前c文件外面的其他c文件中进行访问、链接)。static修饰的函数/全局变量属于内链接。
  (3)无连接的意思就是这个符号本身不参与链接,它跟链接没关系。所有的局部变量(auto的、static的)都是无连接的
  4、函数和全局变量的同名冲突(static修饰)
  (1)因为函数和全局变量是外部链接属性,就是说每一个函数和全局变量将来在整个程序中所有的c文件都能被访问,因此在一个程序中的所有c文件中不能出现同名的函数/同名的全局变量。
  (2)最简单的解决方案就是起名字不要重复,但是很难做到。主要原因是一个很大的工程中函数和全局变量名字太多了,而且一个大工程不是一个人完成的,是很多人协作完成,所以很难保证不会重名。解决方案呢?
  (3)现代高级语言中完美解决这个问题的方法是命名空间namespace(其实就是给一个变量带上各个级别的前缀)。但是C语言不是这么解决的。
  (4)C语言比较早碰到这个问题,当时还没发明namespace概念,当时C语言就发明了一种不是很完美但是凑活能用的解决方案,就是三种链接属性的方法。
  (5)C语言的链接属性解决重名问题思路是这样的:我们将明显不会在其他c文件中引用(只在当前c文件中引用)的函数/全局变量,使用static修饰使其成为内链接属性,这样在将来连接时即使2个c文件中有重名的函数/全局变量,只要其中一个或2个为内链接属性就没事。
  (6)这种解决方案在一定程度上解决了问题。但是没有从根本上解决问题,留下了很多麻烦。所以这个就导致了C语言写很大型的项目难度很大。

  5、static的第二种用法:修饰全局变量和函数
  (1)普通的(非静态)的函数/全局变量,默认的链接属性是外部的
  (2)static(静态)的函数/全局变量,链接属性是内部链接。

最后的总结

(1)普通(自动)局部变量分配在栈上,作用域为代码块作用域,生命周期是临时,连接属性为无连接。定义时如果未显式初始化则其值随机,变量地址由运行时在栈上分配得到,多次执行时地址不一定相同,函数不能返回该类变量的地址(指针)作为返回值。
(2)静态局部变量分配在数据段/bss段(显式初始化为非0则在数据段,显式初始化为0或未显示初始化则在bss段),作用域为代码块作用域(人为规定的),生命周期为永久(天然的),链接属性为无连接(天然的)。定义时如果未显式初始化则其值为0(天然的),变量地址由运行时环境在加载程序时确定,整个程序运行过程中唯一不变;静态局部变量其实就是作用域为代码块作用域(同时链接属性为无连接)的全局变量。静态局部变量可以改为用全局变量实现(程序中尽量避免用全局变量,因为会破坏结构性)。
(3)静态全局变量/静态函数和普通全局变量/普通函数的唯一差别是:static使全局变量/函数的链接属性由外部链接(整个程序所有文件范围)转为内部链接(当前c文件内)。这是为了解决全局变量/函数的重名问题(C语言没有命名空间namespace的概念,因此在程序中文件变多之后全局变量/函数的重名问题非常严重,将不必要被其他文件引用的全局变量/函数声明为static可以很大程度上改善重名问题,但是仍未彻底解决)。
(4)写程序尽量避免使用全局变量,尤其是非static类型的全局变量。能确定不会被其他文件引用的全局变量一定要static修饰。
(5)注意区分全局变量的定义和声明。一般规律如下:如果定义的同时有初始化则一定会被认为是定义;如果只是定义而没有初始化则有可能被编译器认为是定义,也可能被认为是声明,要具体分析;如果使用extern则肯定会被认为是声明(实际上使用extern也可以有定义,实际上加extern就是明确声明这个变量为外部链接属性)。
(6)全局变量应该定义在c文件中并且在头文件中声明,而不要定义在头文件中(因为如果定义在头文件中,则该头文件被多个c文件包含时该全局变量会重复定义)。
(7)在b.c中引用a.c中定义的全局变量/函数有2种方法:一是在a.h中声明该函数/全局变量,然后在b.c中#include <a.h>;二是在b.c中使用extern显式声明要引用的函数/全局变量。其中第一种方法比较正式。
(8)存储类决定生命周期,作用域决定链接属性
(9)宏和inline函数的链接属性为无连接。

时间: 2024-10-17 00:50:04

存储类、作用域、生命周期、链接属性的相关文章

储存类生命周期链接属性作用域总结

储存类:变量在定义时的储存类型对其在程序中的生命周期.连接属性及作用域有很大的关联链接属性: 无连接.内连接.外链接关键字:1.auto    用于修饰局部变量,在定义局部变量时,将其定义在栈上.普通局部变量存放在栈上,因为栈内存的特性决定了普通局部变量定时如果未初始化其的值是随机的,因为栈内存是脏的,变量在释放后并未对其初始化,且栈内存被广泛运用,也就造就了普通变量的特性.普通局部变量的生命周期是临时,可从其存放于栈上看出其临时的特性.普通局部变量的作用域为代码块中"{}",C89与

存储类、生命周期、作用域、链接域

Linux下c内存映像 大方向分为 应用空间 + 内核空间,他俩内存空间布局差不多.这里重点回顾应用空间布局,应用空间氛围代码段 + 数据段(静态数据段+动态数据段) 代码段 为啥是只读的 代码段在编译时就定好了,在程序的运行过程中,不能在代码段去开辟空间,以及释放空间. 包含哪几部分 ELF头.段头部表.init节 参考:剖析可执行文件ELF组成 .text 指令节,也叫代码节,所有函数中的指令都放在了.text节中.能够与指令直接弄在一起的常量,也随指令一起放在了.text中. .rodat

java类的生命周期,从装载,链接,初始化到卸载,关键是何时卸载??

卸载       关于类的卸载,笔者在单例模式讨论篇:单例模式与垃圾回收一文中有过描述,在类使用完之后,如果有下面的情况,类就会被卸载: 该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例.加载该类的ClassLoader已经被回收.该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法.        如果以上三个条件全部满足,jvm就会在方法区垃圾回收的时候对类进行卸载,类的卸载过程其实就是在方法区中清空类信息,java类的整个生

java类的生命周期

类的生命周期:加载.连接(验证.准备.解析).初始化.使用.卸载主动引用(有且只有)初始化: 1.new.getstatic.putstatic.invokestatic如果类没初始化,则初始化new关键字实例化对象.读取或设置一个类的静态字段(被final修饰.*已在编译期把结果放入常量池的静态字段除外).调用一个类的静态方法  2.使用java.lang.reflect包的方法对类进行发射调用的时候,如果类没有进行过初始化,则初始化 3.当初始化一个类的时候,父类没初始化,则初始化 4.当虚

【Java】【JVM】类的生命周期【整理】

参考如下两篇并整理. https://www.cnblogs.com/dongguacai/p/5860241.html https://www.cnblogs.com/ITtangtang/p/3978102.html 类的生命周期是从被加载到虚拟机内存中开始,到卸载出内存结束.过程共有七个阶段. 1.加载---2.验证---3.准备---3.解析---5.初始化---6.使用---7.卸载 加载---(链接)----验证(校验.检查)----准备----解析-----初始化----使用---

乐字节Java反射之三:方法、数组、类加载器和类的生命周期

本文承接上一篇:乐字节Java发射之二:实例化对象.接口与父类.修饰符和属性 继续讲述Java反射之三:方法.数组.类加载器 一.方法 获取所有方法(包括父类或接口),使用Method即可. public static void test() throws Exception { Class<?> clz = Class.forName("com.shsxt.ref.simple.User "); //获取属性 System.out.println("======

类的生命周期(下)类的初始化【转】

上接深入java虚拟机——深入java虚拟机(二)——类加载器详解(上),在上一篇文章中,我们讲解了类的生命周期的加载和连接,这一篇我们接着上面往下看. 类的初始化:在类的生命周期执行完加载和连接之后就开始了类的初始化.在类的初始化阶段,java虚拟机执行类的初始化语句,为类的静态变量赋值,在程序中,类的初始化有两种途径:(1)在变量的声明处赋值.(2)在静态代码块处赋值,比如下面的代码,a就是第一种初始化,b就是第二种初始化 [html] view plaincopyprint? public

深入java虚拟机(二)——类的生命周期(上)类的加载和连接

类加载器,顾名思义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中.一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件).类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例.每个这样的实例用来表示一个 Java 类.通过此实例的 newInstance()方法就可以创建出该类的一个对象.实际的情况可能

iOS对UIViewController生命周期和属性方法的解析

目录[-] iOS对UIViewController生命周期和属性方法的解析 一.引言 二.UIViewController的生命周期 三.从storyBoard加载UIViewController实例的传值陷阱 四.UIViewController与StroyBoard的相关相互方法 1.ViewController直接在StoryBoard中进行跳转的传值 2.使用代码跳转Storyboard中的controller 五.UIViewController之间的一些从属关系 1.parentV

深入java虚拟机(三)——类的生命周期(下)类的初始化

上接深入java虚拟机——深入java虚拟机(二)——类加载器详解(上),在上一篇文章中,我们讲解了类的生命周期的加载和连接,这一篇我们接着上面往下看. 类的初始化:在类的生命周期执行完加载和连接之后就开始了类的初始化.在类的初始化阶段,java虚拟机执行类的初始化语句,为类的静态变量赋值,在程序中,类的初始化有两种途径:(1)在变量的声明处赋值.(2)在静态代码块处赋值,比如下面的代码,a就是第一种初始化,b就是第二种初始化 [html] view plaincopyprint? public