zoukankan      html  css  js  c++  java
  • C language_identifier_scope_name space_linkage_life time_storage duration

    http://www.findfunaax.com/notes/file/134

    命名空间表示标识符的上下文。一个标识符可在多个命名空间中定义,它在不同命名空间中的含义是互不相干的。这样,在一个新的命名空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其它命名空间中。在编程语言中,命名空间是一种特殊的作用于,且本身也用一个标识符来表示,这样便将一系列在逻辑上相关的标识符用一个标识符组织起来。许多现代编程语言也都支持命名空间。

    我们谈下C语言的命名空间(我第一次知道)

    C语言标准定义了4中命名空间:
    1. label names
     所有的label位于一个命名空间下;
    2. the tags of sturctures, unions, and enumerations
     全局的结构struct, 联合union和枚举enum,位于一个命名空间下;
    3. the members of structures or unions
     结构struct和联合union的成员位于它们各自struct或union命名空间下,相互独立互不影响,并且可以形成递归的命名空间(如struct中在定义struct)。
    4. all other identifiers
     所有其它的标识符,包括变量名、函数名、类型名(typedef)和枚举常量位于一个命名空间下;这里的标识符指全局标识符,必须通过其它文件可以访问到,局部标识符(static)不会污染这个命名空间。

    1. 标识符的作用域

            作用域是指允许对标识符进行访问的位置范围。按照C99(章节6.2.1),C语言的作用域共有 4 种类型:文件作用域、代码块作用域、函数作用域、函数原型作用域。

    ------------------|---------------------------|-------------------------     
            类型       |         位置               |         说明            
    ------------------|---------------------------|-------------------------     
         文件作用域     |   在所有代码块和参数类表       |      整个文件都可以访问    
           (file)     |   之外                     | 
    ------------------|---------------------------|------------------------      
         代码块作用域    |   在“代码块” 或者“函数的      |   只有所在的代码块内可            
          (block)     |   参数列表“内部              |   以访问          
    ------------------|---------------------------|------------------------      
                      |                           |  具有此作用域的只有一种语          
         函数作用域     |    函数体内                 |  ”语句标签“。简化为一条规         
         (function)   |                           |  则:一个函数中的语句标签           
                      |                           |  不可相同        
    ------------------|---------------------------|------------------------      
                      |                           |  由于函数原型的参数名称可           
                      |                           |  以省略,即使不省略,也不          
                      |                           |  要求和”函数定义“中的形参         
       函数原型作用域    |  声明的函数原型的参数列表      |  列表中名称相同。                 
       (function      |  中(注意与”函数定义“不同)     |  只有一种情况会发生冲突:           
        prototype)    |                           |  参数列表中有重复的变量名          
                      |                           | (此时编译报错:                  
                      |                           |  redefinition of      
                      |                           |  parameter)       ------------------|---------------------------|----------------------

            说明:当出现两个标识符名称相同的情况,而且都属于同一个命名空间,那么在内层代码块,内层的那个标识符会隐藏外层的那个标识符。

    举例说明并分析

    int my_func(int a, int b);   //myfunc是“文件作用域”;a,b是 “函数原型作用域”
    int a; /* a是文件作用域。 注意:虽然上面的函数原型中将参数名称
                                    声明为a, 但是由于作用域不同,是合法的
                                */ static int b; // b是文件作用域
    int d( int n )    /* d是“文件作用域”。因为这是函数定义,而不是函数原型,
                         所以形式参数n 是“代码块作用域”
                     
    */
    { /* 由于形式参数中已经声明n,那么在函数体内的最外层变量的名称就不能 再为n,因为同一个作用域内不允许对同一个变量进行多次声明。如果声 明,编译器会提示重复声明变量。(在某些较老版本的编译器是允许的,
                         但是C99标准是不允许的)在不同的作用域内可以
    */ int f; // f是代码块作用域 int g(int k); /* 函数原型,位于函数体代码块内。声明的函数名称g是
                          “代码块作用域”,参数k是“函数原型作用域”
                       
    */
    my_label: // 定义一个label,是“函数作用域” ... // 下面的代码块可以是while循环、for循环或if语言等等 { int f, g, i; /* 都是代码块作用域,而且只是在内层代码块,
                            在外层代码块不可见。
                            对于f,外层已经存在f,这里会隐藏掉外层的f,
                            即在这个内层代码块中无法访问外层的f

                         
    */
    int n; /* 代码块作用域,由于这里已经不是函数体内的最外层,所以 可以声明与函数的形式参数同名的变量, 同样会隐藏掉外层的变量n */ } ... /* 另外一个 代码块 */ { int i; /* 代码块作用域,虽然上面的一个内层代码块中已经存在i,
                       但是由于这两个代码块不存在嵌套关系,所以也不存在隐藏现象
                   
    */
    } }

        注意事项:

             1.  注意函数原型中的参数是“函数原型作用域”,而函数定义中的参数是“代码块作用域”。例如上面代码中第一行的a,b和函数定义中的 n

             2.  由于函数定义中参数是“代码块作用域”,所以在函数体内的最外层的变量名称不能再为n,但是内层嵌套的代码块变量名称可以为n。虽然这条特性在某些较老版本的编译器中是可以的,但是在ANSI C中是不允许的。

             3.  变量的隐藏只是针对嵌套的作用域,对于不嵌套的作用域就没有这个说法。例如上面例子中的变量 f 是嵌套的,而 i 是不嵌套的,所以内层的 f 会隐藏掉外层的 f ,但是 i 不会相互隐藏。

    2.标识符的命名空间

            命名空间是为了解决 “在相同作用域内如何区分 相同的标识符”
           说明:(1)只有在相同作用域的情况下才能使用到命名空间去区分标识符,在嵌套的作用域不同的作用域区分标识符都用不到命名空间的概念。
                      (2)相同的作用域内,如果命名空间不同,标识符可以使用相同的名称。否则,即如果命名空间不同,编译器会报错,提示重复定义。

        按照C99(章节6.2.3),命名空间可以分为四种:

            2.1  所有的标签(label)都属于同一个命名空间
                 说明:(1)在同一个函数内,你的标签不能相同。

                            (2)在同一个函数内,标签可以和其他变量名称相同。因为它们所属的命名空间不同。

           2.2  struct、enum和union的名称,在C99中称之为tag,所有的tag属于同一个命名空间。
                   也就是说,如果你已经声明struct A { int a }; 就不能在声明 union A{ int a };

                   说明:之所以让所有的tag组成一个命名空间,由于tag前面总是带struct,enum或union关键字,所以编译器可以将它们与其他的标识符区分开。

           2.3  struct和union的成员属于一个命名空间,而且是相互独立的。

                   例如:如果你已经声明struct A { int a };
                   其成员的名称为a,你仍然可以声明 struct B{ int a };或者union B{ int a };

                   说明:之所以让struct和union的成员各自成为一个命名空间,是因为它们的成员访问时,需要通过 "."或"->"运算符,而不会单独使用,所以编译器可以将它们与其他的标识符区分开。由于枚举类型enum的成员可以单独使用,所以枚举类型的成员不在这一名称空间内。

            2.4  其他所有的标识符,属于同一个名称空间。包括变量名、函数名、函数参数,宏定义typedef的类型名、enum的成员 等等
                   注意:如果标识符出现重名的情况,宏定义覆盖所有其它标识符,这是因为它在预处理阶段而不是编译阶段处理。除了宏定义之外其它类别的标识符,处理规则是:内层作用域会隐藏掉外层作用域的标识符。

    举例说明并分析

    #include <stdio.h>   
    #include <stdlib.h>  
              
    int main()
    {
    struct A // “结构体的tag”和“结构体成员”不在同一个命名空间,所以名称可以相同
        { int A; };
        union B /* 根据第二条,这个union的tag不能是A,但是根据第三条,
                   其成员的名称可以与struct A的成员名称相同
               
    */

        {
    int A; };
       struct A A; // “结构体的tag”和“普通变量”不在同一个命名空间,所以名称可以相同 union B B; /* 上面的“结构体变量”和 这行的“联合体变量”属于同一个命名空间,
                       名称不能相同,即不能是 union B A
                   
    */

    int my_label = 1; // “普通变量”和“标签”不属于同一个命名空间,所以名称可以相同 A.A = 1; B.A = 20; printf("B.A == %d /n/n", B.A);
    my_label:
    /* 这里label 的名称与上面变量的名称 相同 */ printf("A.A == %d /n", A.A); A.A +=1; if(A.A <= 5)
        {
    goto my_label; } system("pause"); return EXIT_SUCCESS; }

    3.标识符的链接属性

       主要用于处理多次声明相同的标识符名称后,如何判断这些标识符是否是同一个。 
        原文对链接属性(linkage)的定义如下:

        An identifier declared in different scopes or in the same scope more than once can be made to refer to the same object or function by a process called  linkage.

       注意:链接属性(linkage)是相对于相同的标识符名称来说的,对于不同的标识符,没有链接属性。

       按照C99(章节6.2.2),链接属性分为三种:external(外部的), internal(内部的), none(无)。

    ------------------|------------------------|----------------------------
            类型       |           说明          | 默认(即不使用extern和static)
    ------------------|------------------------|----------------------------
       外部(external)  | 同一个标识符,即使在不同的   |  (1)具有文件作用域的变量和函数  
                      | 文件中,也表示同一个实体。   |  (2)代码块作用域内部的函数声明  
    ------------------|------------------------|----------------------------
      内部(internal)   | 同一个标识符,仅仅在同一个   |  无(如果不使用static,那么默  
                      | 文件中才表示同一个实体。    |  认没有内部链接属性的标识符。  
                      |                        |  只有被static修饰的具有文件作  
                      |                        |  用域的标识符,才具有internal  
                      |                        | 链接属性)                    
    ------------------|------------------------|----------------------------
       无(none)        | 表示不同的实体           |  所有其他的标识符。如:函数的  
                      |                        |  参数、代码块作用域的变量、标  
                      |                        |  签等    
    ------------------|------------------------|---------------------------

    extern和static的使用:

     3.1  文件作用域的变量和函数定义,即在所有代码块和参数列表之外的标识符,使用static修饰,则具有内部链接属性。

     3.2  一个标识符声明为extern,并且前面已经对同一个标识符进行了声明,那么
           (1)如果前一个声明时internal或者external,那么后一个声明与前一个相同。(即尽管后一个使用了extern,但其链接属性由前一个决定)
           (2)如果前一个声明为none,或者前一个声明在当前作用域不可见,那么这个标识符的链接属性为external。 
     

    举例说明并分析:(注意所有文件都在同一个工程中)

    /* 文件《test1.c》 */  
    int a=1 ;   /* 这里的a为external */   
    int b=1;    /* 这里的b为external */   
      
    void print_in_test1()
    {  
        static int a;   /* 这里是重新声明一个变量a, 并且会隐藏掉外层的a。由于是
                           static静态类型,其默认初始化为0,所以下面的打印结果应为 0*/   
        extern int b;   /* 虽然这里将b用extern声明,但是由于文件前面声明的b是
                           external,所以b的链接属性也没有改变,依然是
                           external,所以下面的打印结果应为 1 */
    printf("test1.c: a == %d \n", a); printf("test1.c: b == %d \n", b); }
    /*文件《test2.c》 */  
    static int a=2; /* 这里的a为internal */   
      
    void print_in_test2()
    {
    extern int a; /* 虽然这里将a用extern声明,但是由于文件前面声明的a是
                          internal,所以a的链接属性并没有改变,依然是internal
    */ int b =2; //这里b为none,不会链接到test1.c中的 b,所以下面的打印结果应为 2 printf("test2.c: a == %d \n", a); /* 所以下面的打印结果应为 2 */ printf("test2.c print_in_test2() : b == %d \n", b); } void print2_in_test2()
    {
    extern int b; /* b会链接到test1.c中的 b,而不是上面的函数中的 b,
                           所以下面的打印结果应为 1
    */ printf("test2.c: b == %d /n", b); }


    /* 文件《main.c》 */  
    #include   
    #include   
      
    extern int a; /* 会链接到test1.c中的 a,所以下面的打印结果应该为 1 */   
      
    void print_in_test1();  /* 函数原型,会链接到test1.c中的 print_in_test1()*/  
      
    int main(int argc, char *argv[])  
    {  
      
        void print_in_test2();  /* 函数原型,会链接到test2.c中的
                                   print_in_test2()
                               
    */
    void print2_in_test2(); /* 函数原型,会链接到test2.c中的
                                   print2_in_test2()
                               
    */
    printf("main.c: a == %d \n", a); print_in_test1(); print_in_test2(); print2_in_test2(); system("PAUSE"); return 0; }


     

       3.3  如果不使用static和extern:

      1.对于函数声明:一定是external,无论是否在代码块内部。

      2.对于变量声明:如果在代码块外,则是 external;否则是none

      例子可以参照上面的程序代码,《main.c》中声明函数原型时,print_in_test1()在main函数外,print_in_test2() 和print2_in_test2()在main函数内,虽然位置不同,但都是external的,都会正确链接到相应的函数。

    4.变量的生命周期、存储类型

     变量的生存期(Storage durations),也就是变量的生命周期(lifetime),可以理解为:程序运行期间,变量从分配到地址地址被释放 这一过程。

     根据C99描述,变量的生存期分为三种类型:static(静态), automatic(自动), and allocated(动态分配)。

      1.  属于文件作用域(即external或internal链接属性)、以及被static修饰的变量,具有static静态生存期

      2.  链接属性为none,并且没有static修饰 的变量,具有automatic自动生存期

      3.  allocated动态分配生存期,是指使用malloc函数,在进程的堆空间分配内存的变量。

    说明:

      4.1  生命周期、存数类型 都是针对变量,对于函数等其他标识符没有这个说法。

        因为在程序运行期间,只有变量才需要分配内存和释放内存,其他的诸如函数等都不需要。

      4.2  变量的生命周期存储类型密切相关。

      (1)静态生存期的变量存储在静态内存中。其中使用static修饰的变量,在C语言书籍中也被称为“静态变量”。静态存储的变量,在程序运行之前就已经创建,在程序整个执行期间一直存在,如果声明时没有被显式的初始化,就会被自动初始化为0.

        注意静态变量当然是属于静态存储方式,但是属于静态存储方式的变量不一定就是静态变量, 例如外部变量虽属于静态存储方式,但不一定是静态变量,必须由 static加以定义后才能成为静态变量。

      (2)自动生存期的变量存储于栈或寄存器中。其中在代码块内部声明的变量,在C语言书籍中也被称为“自动变量”,使用auto修饰符,默认可以省略。对于自动存储的变量当程序执行到含有自动变量的代码段时,自动变量才被创建,并且不会被自动初始化,代码段执行结束,自动变量就自动销毁,释放掉内存。如果代码段被反复执行,那么自动变量就会反复被创建和销毁。注意这一点和静态变量不同,静态变量只创建一次,到程序结束才销毁。

      (3) 动态分配生存期的变量存储于中,也不会被自动初始化,使用free函数释放内存。

      4.3  修改变量的存储类型(如用static将自动变量变为静态变量),并不会修改变量的作用域,变量的作用域仍然有其声明的位置决定。

      4.4  变量的存储类型修饰符一共有五个:static、auto、register、extern、typedef。

      4.5  函数的形式参数,如果使用修饰符,只能使用register修饰,表示运行时参数存储在寄存器上。注意:形式参数是不能用auto修饰的。

    5、总结

            下图为一个变量声明,在 不同的作用域 对应的其他属性:

    ----------|------------|-----------|-----------|-----------|-------------
       作用域  |   声明位置   |  链接属性   | 存储类型    |  默认初始  | 使用static修饰 
              |            |           |           |    化值   |  
    ----------|------------|-----------|-----------|-----------|------------
     file     | 在所有“代码  |  external |  static   |      0    |   internal
    | 块”和“参数  | | | | | 列表”之外 | | | | ----------|------------|-----------|-----------|-----------|----------- block | 在“代码块”或 | none | automatic | 形式参数调  | none | 者“函数的参 | | | 用时被初始  | | 数列表”内部 | | | 化;代码块  | | | | | 内部的不自  | | | | | 动初始化   | ----------|------------|-----------|-----------|-----------|---------- function | 函数体内   | ----- | ----- | 标签,不   | ----- | | | | 需要初始化  | ----------|------------|-----------|-----------|-----------|---------- function | 声明的函数 | ----- | ----- | 不需要初始  | -----
    prototype
    | 原型的参数 | | | 化 | | 列表中(注 | | | | | 意与“函数定 | | | | | 义”不同) | | | | ----------|------------|-----------|-----------|----------|----------
  • 相关阅读:
    在传统软件公司十年深恶痛绝的感受
    前端 100 问:能搞懂80%的请把简历给我
    中专毕业的他,是如何逆袭为 360 资深程序员?
    别再参加领导力培训课程了,这本领导力提升书籍推荐给你
    企业管理书籍推荐,读完这个系列的书就是上完了整个MBA
    如何做好人才管理?人才管理书籍推荐
    如何管理好员工?你可能需要看看这本人员工管理方面的经典书籍
    领导与管理的区别和异同:什么是领导?什么是管理?
    一名优秀的HR需要具备哪些素质与能力?
    销售书籍推荐:做销售你究竟该看什么书?
  • 原文地址:https://www.cnblogs.com/openix/p/2778552.html
Copyright © 2011-2022 走看看