【C Primer Plus第六版 学习笔记】第十二章 储存类别、链接和内存管理

有基础,进阶用,个人查漏补缺

  1. 代码中使用的数据都储存在内存中。从硬件来看,被储存的每个值都占用一定的物理内存,C语言把这样一块内存成为对象。从软件来看,程序需要一种方法访问对象,这可以通过声明变量完成:int a = 3;

  2. 标识符:标识符(identifier)就是变量、函数、类型等的名字。它们由大小写字母、数字和下划线组成,但不能以数字开头(前两句来自C与指针)。上述声明创建了一个名为a的标识符。标识符可以用来指定特定对象的内容。标识符即是软件(即C程序)指定硬件内存中的对象的方式。

  3. 作用域:当变量在程序的某个部分被声明时,它只有在程序的一定区域才能被访问。这个区域由标识符的作用域(scope)决定(前两句来自C与指针)。作用域是描述程序中可以访问标识符的区域。块是一对用花括号括起来的代码区域,块作用域变量的可见范围是从定义处到包含该定义的块的末尾。

    以下来自C与指针:

    1. 编译器可以确认4种不同类型的作用域——文件作用域、函数作用域、代码块作用域和原型作用域。标识符声明的位置决定它的作用域。

    2. 代码块作用域(block scope):位于一对花括号之间的所有语句称为一个代码块。任何在代码块的开始位置声明的标识符都具有代码块作用域,表示它们可以被这个代码块中的所有语句访问。

    3. 文件作用域(file scope):任何在所有代码块之外声明的标识符都具有文件作用域,它表示这些标识符从它们的声明之处直到它所在的源文件结尾处都是可以访问的。

      在文件中定义的函数名也具有文件作用域,因为函数名本身并不属于任何代码块。

      在头文件中编写并通过#include指令包含到其他文件中的声明就好像它们是直接写在那些文件中一样。它们的作用域并不局限于头文件的文件尾。

    4. 原型作用域(prototype scope):只适用于在函数原型中声明的参数名。在原型中(与函数的定义不同),参数的名字并非必需。但是,如果出现参数名,你可以随你所愿给它们取任何名字,它们不必与函数定义中的形参名匹配,也不必与函数实际调用时所传递的实参匹配。原型作用域防止这些参数名与程序其他部分的名字冲突。

    5. 函数作用域(function scope):只适用于语句标签,语句标签用于goto语句。基本上,函数作用域可以简化为一条规则——一个函数中的所有语句标签必须唯一。

      int a;//具有文件作用域
      int b (int c);//b具有文件作用域,c具有原型作用域
      int d (int e);//d具有文件作用域;e作为形参,在函数体内部也具有代码块作用域
      {
      	int f;//具有代码块作用域
      	int g (int h);//g具有代码块作用域,h具有原型作用域
      	···
      	{
      		int f, g, i;//具有代码块作用域
      	}
      
      	{
      		int i;//具有代码块作用域
      	}
      }
      
  4. 可以用存储期描述对象,所谓存储期是指对象在内存中保留了多长时间。标识符用于访问对象,可以用作用域和链接描述标识符,表明了程序的哪些部分可以使用它。

  5. 链接属性:当组成一个程序的各个源文件分别被编译之后,所有的目标文件以及那些从一个或多个函数库中引用的函数链接在一起,形成可执行程序。(该大点均来自C与指针)

    1. 标识符的作用域与它的链接属性有关,但这两个属性并不相同。

    2. C变量有3种链接,内部链接internal、外部链接external、无链接none。

    3. 内部链接:internal属性出现在static修饰之后。属于internal链接属性的标识符在同一个源文件内的所有声明中都指同一个实体,但位于不同源文件的多个声明则分属不同的实体。

    4. 外部链接:不声明于代码块内的变量,缺省情况下具有external链接属性。属于external链接属性的标识符不论声明多少次、位于几个源文件都表示同一个实体。关键字是extern

    5. 无链接属性:缺省情况下,除了external属性以外,其余均为none。没有链接属性的标识符(none)总是被当作单独的个体,也就是说该标识符的多个声明被当作独立不同的实体。具有块作用域、函数作用域、函数原型作用域的变量都是无链接变量,即函数形式参数和代码块内声明的变量在缺省情况下具有none链接属性 。

      //b、c、f为external,其余标识符的链接属性则为none
      /*若另一个源文件也包含了标识符b的类似声明并调用函数c,它们实际上访问的是这个源文件所定义的实体
      因为f是个函数名,故f为外部链接。
      在这个源文件中调用函数f,它实际上将链接到其他源文件所定义的函数,
      甚至这个函数的定义可能出现在某个函数库
      */
      typedef char *a;
      int b;//external
      int c (int d)//c为external
      {
      	int e;
      	int f (int g);//f为external
      	···
      }
      
    6. 具有文件作用域的变量可以是外部链接或内部链接,前者变量可以在多文件程序中使用,后者变量只能在一个翻译单元中使用。

      1. 关键字extern和static用于在声明中修改标识符的链接属性。如果某个声明在正常情况下具有external链接属性,在它前面加上static关键字可以使它的链接属性变为internal,这可以防止它被其他源文件调用

      2. static只对缺省链接属性为external的声明才有改变链接属性的效果

      3. 当extern关键字用于源文件中一个标识符的第1次声明时,它指定该标识符具有external链接属性。但是,如果它用于该标识符的第2次或以后的声明时,它并
        不会更改由第1次声明所指定的链接属性

        static int i;
        int func()
        {
        	int j;
        	extern int k;
        	static int i;//此处声明并不修改由第一行的声明所指定的变量i的链接属性
        }
        
      4. 如何知道文件作用域的变量是外部链接,还是内部链接?

        //查看外部定义中是否使用了存储类别说明符static
        int a = 5;         //文件作用域,外部链接
        static int b = 3;  //文件作用域,内部链接,属于文件私有
        int main()
        {
        	···
        }
        ···
        
  6. 存储期:C对象有4种存储期,静态存储期、线程存储期、自动存储期、动态分布存储期

    1. 静态存储期:具有静态存储期的对象,在程序执行期间会一直存在。无论是内部链接还是外部链接(对于文件作用域变量,关键字static表明了其内部链接属性,而非储存期),文件作用域变量具有静态存储期。
    2. 线程存储期:用于并发程序设计,程序执行可以被分为多个线程。具有线程存储期的对象,从被声明到线程结束一直存在。
    3. 自动存储期:块作用域的变量通常具有自动存储期。当程序进入定义这些变量的块时,为这些变量分配内存;当退出这个块时,释放刚才为变量分配的内存。
  7. 储存类别:(本大点由C与指针以及本书相互补充而成)变量的存储类型(storage class)是指存储变量值的内存类型。变量的存储类型决定变量何时创建、何时销毁以及它的值将保持多久。有三个地方可以用于存储变量:普通内存、运行时堆栈、硬件寄存器。

    有5种存储类别,分别是自动、寄存器、静态块作用域、静态外部链接、静态内部链接

    存储类别 存储期 作用域 链接 声明方式
    自动 自动 块内
    寄存器 自动 块内,使用关键字register
    静态外部链接 静态 文件 外部 所有函数外
    静态内部链接 静态 文件 内部 所有函数外,使用关键字static
    静态无链接 静态 块内,使用关键字static
    1. 自动变量:在代码块内部声明的变量的缺省存储类型是自动的(automatic),也就是说它
      存储于堆栈中,称为自动(auto)变量。属于自动存储类别的变量具有自动存储期、块作用域且无链接。

      1. 自动存储期:意味着程序在进入该变量声明所在的块时变量存在,退出时该块变量消失。默认情况下,声明在块或函数头中的任何变量都属于自动存储类别,为了更明显地表达意图,可以显式使用关键字auto(存储类别说明符,极少使用,因为代码块中的变量在缺省情况下就是自动变量),如auto int plox; 但是不要在C++中使用,其用法完全不同。

      2. 块作用域和无链接:意味着只有在变量定义所在的块中才能通过变量名访问该变量。

      3. 对于在代码块内部声明的变量,如果给它加上关键字static,可以使它的存储类型从自动变为静态。具有静态存储类型的变量在整个程序执行过程中一直存在,而不仅仅在声明它的代码块的执行时存在。注意,修改变量的存储类型并不表示修改该变量的作用域,它仍然只能在该代码块内部按名字访问。

      4. 自动变量不会初始化,除非显式初始化它。

        int repid;//repid变量的值是之前分配给repid的空间中的任意值
        int tents = 5;//被显示初始化它
        
    2. 寄存器变量:属于自动存储类别的变量具有自动存储期、块作用域且无链接。

      1. 储存在最快的可用内存(CPU的寄存器)中,访问和处理的速度比普通变量更快。
      2. 由于寄存器变量储存在寄存器而非内存中,所以无法获取寄存器变量的地址。
      3. 存储类别说明符register可声明寄存器变量,但是可声明为register的数据类型有限。
      4. register int quick; 声明变量为register类别与直接命令相比更像是一种请求,编译器必须根据寄存器或最快可用内存的数量衡量你的请求,或者直接忽略你的请求,此时寄存器变量就变成普通的变量。
    3. 静态变量:凡是在任何代码块之外声明的变总是存储于静态内存中,也就是不属于堆栈的内存,这类变量称为静态(static)变量。

      静态变量在程序运行之前创建,在程序的整个执行期间始终存在。它始终保持原先的值,除非给它赋一个不同的值或者程序结束

      1. 块作用域的静态变量

        1. 静态的意思是该变量在内存中原地不动

        2. 具有文件作用域的变量自动具有静态存储期

        3. 在块中(提供块作用域和无链接)以存储类别说明符static(提供静态存储期)声明这种变量

          #include
          void trystst(void);
          int main(void)
          {
          	int count;
          	for(count=1; count<=3; count++)
          	{
          		printf("Here comes %d:\n", count);
          		trystat();
          	}
          	return 0;
          }
          
          void trystat(void)
          {
          	int fade = 1;         //每次调用该函数的时候都会执行这条
          	static int stay = 1;  //该命令并不是该函数的一部分,
          												//因为静态变量和外部变量在程序被载入内存时已经被执行完毕
          	printf("fade = %d and stay = %d\n", fade++, stay++);
          }
          /*输出:
          Here comes 1:
          fade = 1 and stay = 1
          Here comes 2:
          fade = 1 and stay = 2
          Here comes 3:
          fade = 1 and stay = 3
          
          静态变量stay保存了它被递增1后的值,但是fade变量每次都是1.
          这表明了初始化的不同:每次调用trystat()都会初始化fade,但是stay只在编译时初始化一次
          如果未显式初始静态变量,它们会被初始化为0
          
        4. 不能在函数的形参中使用static

      2. 外部链接的静态变量(外部变量具有静态存储期)

        1. 把变量的定义性声明放在所有函数的外面便创建了外部变量。为了指出该函数使用了外部变量,可以在函数中使用关键字extern再次声明。

        2. 如果一个源代码文件使用的外部变量定义在另一个源代码文件中,则必须用extern在该文件中声明该变量。

          int Errupt;              //外部定义的变量
          double Up[100];          //外部定义的数组
          extern char Coal;        //如果Coal被定义在另一个数组,则必须这样声明
          void next(void)int main(void)
          {
          	extern int Errupt;     //可选的声明,不会改变链接属性
          	extern double Up[];    //可选的声明,不用指明数组大小,第1次声明已经提供了数组大小信息
          	···
          }
          void next(void)
          {
          	···
          }
          /*main()中的两条声明完全可以省略,因为外部变量具有文件作用域。
          
          
        3. 初始化外部变量:如果没有操作,外部变量会被自动初始化为0;只能使用 常量表达式 初始化文件作用域变量;外部变量只能初始化一次,且必须在定义该变量时进行

          int x = 10;
          int x2 = 2 * x;//不行,x是变量
          
      3. 内部链接的静态变量

        int a = 1;//外部链接
        static int b = 1;//内部链接
        int main(void)
        {
        	···
        }
        
  8. 多文件

    1. 只有当程序由多个翻译单元组成时,才体现区别内部链接和外部链接的重要性
    2. 多个文件之间要共享外部变量时,需要在一个文件里进行定义式声明并初始化,其他文件使用extern进行引用式声明,才能使用这个变量
  9. 存储类别说明符(本大点由C与指针以及本书相互补充而成)

    1. auto说明符表明变量是自动存储期,只能用于块作用域的变量声明中。由于在块中声明的变量本身就具有自动存储期,所以使用auto主要是为了明确表达要使用与外部变量同名的局部变量的意图。
    2. register 说明符也只用于块作用域的变量,它把变量归为寄存器存储类别,请求最快速度访问该变量。同时,还保护了该变量的地址不被获取。
    3. 用 static说明符创建的对象具有静态存储期,载入程序时创建对象,当程序结束时对象消失。
      1. 如果static 用于文件作用域声明,作用域受限于该文件,用于修
        改标识符的链接属性,从external改为internal,但标识符的存储类型和作用域不
        受影响。
      2. 如果 static用于块作用域声明,作用域则受限于该块,用于修改变量的存储类型,
        从自动变量修改为静态变量,但变量的链接属性和作用域不受影响。
      3. 因此,只要程序在运行对象就存在并保留其值,但是只有在执行块内的代码时,才能通过标识符访问。块作用域的静态变量无链接。文件作用域的静态变量具有内部链接。
    4. extern 说明符表明声明的变量定义在别处。如果包含 extern 的声明具有文件作用域,则引用的变量必须具有外部链接。如果包含 extern 的声明具有块作用域,则引用的变量可能具有外部链接或内部链接,这接取决于该变量的定义式声明。
  10. 存储类别和函数

    函数也有存储类别,可以是外部函数(默认)或静态函数。C99 新增了第 3 种类别——内联函数,将在第16章中介绍。外部函数可以被其他文件的函数访问,但是静态函数只能用于其定义所在的文件。假设一个文件中包含了以下函数原型:

    double gamma(double); /* 该函数默认为外部函数 */
    static double beta(int, int);
    extern double delta(double, int);
    

    在同一个程序中,其他文件中的函数可以调用 gamma()和 delta(),但是不能调用 beta(),因为以static存储类别说明符创建的函数属于特定模块私有。这样做避免了名称冲突的问题,由于beta()受限于它所在的文件,所以在其他文件中可以使用与之同名的函数。

    通常的做法是:用extern 关键字声明定义在其他文件中的函数。这样做是为了表明当前文件中使用的函数被定义在别处。除非使用 static关键字,否则一般函数声明都默认为 extern。

  11. 存储类别的选择

    绝大多数会选择自动存储类别,随意使用外部存储类别的变量导致的后果远远超过它所带来的便利。唯一的例外是const数据。

  12. 分配内存:malloc()和free()

    1. malloc():用其创建一个数组,为其动态分配空间,可以灵活设置数组大小。返回一个指针 ,指向已分配大小的内存。如果请求失败,则返回 NULL。

      double * pt;
      int n;
      scanf("%d", %n);
      pt = (double *) malloc(n * sizeof(double));
      
    2. calloc():与malloc()一样。

      1. malloccalloc 之间的不同点是,malloc 不会设置内存为零,而 calloc 会设置分配的内存为零。calloc() 函数将分配的内存全部初始化为零。如果不需要初始化,可以使用 malloc() 函数代替。另外,使用 calloc() 函数时需要注意,如果分配的内存块过大,可能会导致内存不足的问题。
      2. 该函数返回一个指针,指向已分配的内存。如果请求失败,则返回 NULL。
      long * pt2 = (long *) calloc(100, sizeof(long));
      
    3. free():与malloc()/calloc()配套使用,防止内存泄漏(内存未被及时释放,导致内存不足)。不返回任何值。

  13. const类型限定符

    1. 在指针和形参声明中使用const

      const float * pf;//pf指向一个float类型的const值,pf指向的值不能改变,pf本身可以变
      float * const pt;//pt本身不能改变,必须指向同一个地址,但是指向的值即地址存储的值可以变
      const float * const ptr;//ptr既不能指向别处,所指的值也不能变
      float const * pfc;//和const float * pfc一样(星号位置不同)
      
      void func(const int a[], int n);//保证数据不会被改
      
    2. 对全局数据使用const:避免数据被代码其他部分修改

你可能感兴趣的:(C语言,c语言,学习,笔记)