【转】C语言中标识符的作用域、命名空间、链接属性、生命周期、存储类型

Technorati 标签: C,标识符,作用域,命名空间,链接属性,生命周期,存储类型,scope,name space,linkage,storage durations,lifetime
        无论学习哪一种语言,都免不了要讨论这些问题。而且这些问题,深究起来有时也让我们很迷惑。
       标识符的定义无需多讲,只需注意不仅仅是指变量,还有函数,标签等。
1. 标识符的作用域
        作用域是指允许对标识符进行访问的位置范围。按照C99(章节6.2.1),C语言的作用域共有 4 种类型:文件作用域、代码块作用域、函数作用域、函数原型作用域。
类型
位置
说明
文件作用域  (file)    在所有 代码块和参数列表 之外    整个文件内都可以访问
代码块作用域 ( block)    在“代码块”或者“函数的参数列表”内部    只有所在的代码块内可以访问
函数作用域 (function)    函数体内    具有此作用域的只有一种语句:只有goto语句要使用的“语句标签”。简化为一条规则:一个函数中的语句标签(即label)不可相同。
函数原型作用域  (function prototype)    声明的函数原型的参数列表中(注意与“函数定义”不同)    由于函数原型的参数名称可以省略,即使不省略,也不要求和“函数定义”中的形参列表中名称相同。
只有一种情况会发生冲突:参数列表中的有重复的变量名。(这时编译报错: redefinition of parameter )
        说明:当出现两个标识符名称相同的情况,而且都属于同一个命名空间,那么在内层代码块,内层的那个标识符会隐藏外层的那个标识符。
举例说明并分析:
[cpp] view plaincopy
int my_func(int a, int b);  /* myfunc是“文件作用域”;a,b是 “函数原型作用域” */
int a;/* a是文件作用域。 注意:虽然上面的函数原型中将参数名称声明为a, 但是由于作用域不同,是合法的。下一行的b也是这种情况 */
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. 标识符的命名空间
        命名空间是为了解决 “在相同作用域内如何区分 相同的标识符”。
        说明:①只有在相同作用域的情况下才能使用到命名空间去区分标识符,在嵌套的作用域、不同的作用域区分标识符都用不到命名空间的概念。
                  ②在相同的作用域内,如果命名空间不同,标识符可以使用相同的名称。否则,即如果命名空间不同,编译器会报错,提示重复定义。
        按照C99(章节6.2.3),命名空间可以分为四种:
            2.1  所有的标签(label)都属于同一个命名空间。
                    说明:①在同一个函数内,你的标签不能相同。②在同一个函数内,标签可以和其他变量名称相同。因为它们所属的命名空间不同。
            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的成员 等等。
                  注意:如果标识符出现重名的情况,宏定义覆盖所有其它标识符,这是因为它在预处理阶段而不是编译阶段处理。除了宏定义之外其它类别的标识符,处理规则是:内层作用域会隐藏掉外层作用域的标识符。
举例说明并分析:
[cpp] view plaincopy
">#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;
}
运行结果为:
[cpp] view plaincopy
B.A == 20  

A.A == 1
A.A == 2
A.A == 3
A.A == 4
A.A == 5  

3. 标识符的链接属性
        主要用于处理多次声明相同的标识符名称后,如何判断这些标识符是否是同一个。
        原文对链接属性(linkage)的定义如下:An identi?er 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    同一个标识符,即使在不同的文件中,也表示同一个实体。    ①具有文件作用域的变量和函数。
②代码块作用域内部的函数声明
内部  internal    同一个标识符,仅仅在同一个文件中才表示同一个实体。    无(如果不使用static,那么默认没有内部链接属性的标识符。只有被static修饰的具有文件作用域的标识符,才具有internal链接属性)
无  none    表示不同的实体    所有其他的标识符。如:函数的参数、代码块作用域的变量、标签等
        extern和static的使用:
        3.1  文件作用域的变量和函数定义,即在所有 代码块和参数列表之外的标识符,使用static修饰,则具有 内部链接属性。
        3.2  一个标识符声明为extern,并且前面已经对同一个标识符进行了声明,那么
              ①如果前一个声明时internal或者external,那么后一个声明与前一个相同。(即尽管后一个使用了extern,但其链接属性由前一个决定)。
              ②如果前一个声明为none,或者前一个声明在当前作用域不可见,那么这个标识符的链接属性为external。
             举例说明并分析:(注意所有文件都在同一个工程中)
[cpp] view plaincopy
/* 文件《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;
}
        运行结果:
[cpp] view plaincopy
main.c:  a == 1
test1.c:  a == 0
test1.c:  b == 1
test2.c:  a == 2
test2.c print_in_test2() :  b == 2
test2.c:  b == 1
        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  变量的生命周期和存储类型密切相关。
              ① 静态生存期的变量存储在静态内存中。其中使用static修饰的变量,在C语言书籍中也被称为“静态变量”。静态存储的变量,在程序运行之前就已经创建,在程序整个执行期间一直存在,如果声明时没有被显式的初始化,就会被自动初始化为0。          注意:静态变量当然是属于静态存储方式,但是属于静态存储方式的变量不一定就是静态变量, 例如外部变量虽属于静态存储方式,但不一定是静态变量,必须由 static加以定义后才能成为静态变量。
              ② 自动生存期的变量存储于栈或寄存器中。其中在代码块内部声明的变量,在C语言书籍中也被称为“自动变量”,使用auto修饰符,默认可以省略。对于自动存储的变量当程序执行到含有自动变量的代码段时,自动变量才被创建,并且不会被自动初始化,代码段执行结束,自动变量就自动销毁,释放掉内存。如果代码段被反复执行,那么自动变量就会反复被创建和销毁。注意这一点和静态变量不同,静态变量只创建一次,到程序结束才销毁。
              ③  动态分配生存期的变量存储于堆中,也不会被自动初始化,使用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    声明的函数原型的参数列表中(注意与“函数定义”不同)    ---------    --------    不需要初始化    ---------
 
时间: 2024-12-29 23:25:55

【转】C语言中标识符的作用域、命名空间、链接属性、生命周期、存储类型的相关文章

C/C++——C++变量的作用域与生命周期,C语言中变量的作用域和生命周期

全局变量 作用域:全局作用域(全局变量只需在一个源文件中定义,就可以作用于所有的源文件.) 生命周期:程序运行期一直存在 引用方法:其他文件中要使用必须用extern 关键字声明要引用的全局变量. 内存分布:全局数据区 注意:如果在两个文件中都定义了相同名字的全局变量,连接出错:变量重定义 全局静态变量 作用域:文件作用域(只在被定义的文件中可见.) 生命周期:程序运行期一直存在 内存分布:全局数据区 定义方法:static关键字,const 关键字 注意:只要文件不互相包含,在两个不同的文件中

【转】作用域、链接属性、存储类型总结--转载学习,很清晰,很详细

标识符: 首先,在讨论这三种东西之前,详细说明一下C语言中的标识符. 标识符是用户编程为变量.常量.函数.语句等指定的名字,就好比人名一样的东西. 标识符的命名规则如下: 1.只能由字母.数字.下划线组成,并且首字符不能是数字: 2.不能把C语言的关键字作为标识符: 3.对大小写敏感: 其次,需要明确,作用域和链接属性是在标识符范畴内讨论的,存储类型是在标识符中的变量范畴内讨论的. 作用域: 标识符的作用域就是程序中该标识符可以被使用的区域,由它的声明位置决定. 分为以下四类: 1.文件作用域

C语言中标识符声明的几个关键字总结

C语言中声明一个名称就是把一个标识符与某个C语言对象相关联,如变量.函数或类型,C语言中可以声明的名称包括:变量.函数.类型.类型标志.结构成员与联合成员.枚举常量.语句标号和预处理器宏.除了语句标号和预处理器宏之外,所有标识符都在C语言声明中声明,变量.函数.类型放在声明的声明符中,类型标志.结构成员与联合成员和枚举常量在声明的某种类型说明符中声明,语句标号在C语言函数中出现时声明,而预处理器宏用#define预处理器命令声明. 如下列声明: extern const volatile uns

关于extern和static关键字引出的一些关于作用域和链接属性和存储类型的问题

在进入正题前我们必须了解一些概念: 标识符:标识符不仅仅代表着变量的名字,main()函数的main也是一个标识符,这点很重要. 存储类型:即变量的存储位置及其生存周期:静态区:分为两块 .date 已显式初始化的全局变量了静态变量 .bss 存放未初始化的全局或者静态变量 注意:静态变量的初值是在编译时就进行初始化了:意思就是用static修饰的变量赋过数值的话就保存为他的初值,如果没有初始化的话就赋值为零,且整个程序只初始化一次:即不管static int i = 1:或者这 static

【SSH三大框架】Spring基础第一篇:搭建Spring环境、实例化Bean、管理Bean的作用域以及Bean的生命周期

一.搭建Spring环境: 在lib目录下引入jar包,然后add to path,这就不过多说了. 二.实例化Bean的三种方式: 首先,我们先写两个java类: 接口类: public interface PersonService { public abstract void save(); } 实现类: public class PersonServiceBean implements PersonService { @Override public void save(){ Syste

Spring中bean用法(2):生命周期

一:基本流程 把一个bean纳入到Spring IoC容器之中,这个bean的生命周期就会交由容器进行管理 1.Bean的建立 由BeanFactory读取Bean定义文件,并生成各个实例. 2.Setter注入 执行Bean的属性依赖注入. 3.BeanNameAware的setBeanName() 如果Bean类实现了org.springframework.beans.factory.BeanNameAware接口,则执行其setBeanName()方法. 4.BeanFactoryAwar

嵌入式 Linux C语言(八)——存储类型、作用域、生命周期、链接属性

嵌入式 Linux C语言(八)--存储类型.作用域.生命周期.链接属性 一.存储类型 C语言中,每个变量和函数都有两个属性:数据类型和数据的存储类型. 变量的存储类型是指存储变量值的内存类型.变量的存储类型决定变量何时创建.何时销毁以及它的值将保持多久.计算机中有三个地方可以用于存储变量:普通内存,运行时堆和栈,硬件寄存器.变量的存储类型取决于声明变量的位置. C语言存储类别说明符: 说明符 用    法 auto 只在代码块内变量声明中被允许, 表示变量具有本地生存期 extern 出现在顶

C语言中变量、全局变量与变量的作用域

什么是变量: 变量常量都是表征数据的一种形式:常量用来表示数据的值: 变量不仅可以用来表示数据的值:还可以用来存放数据:因为变量对应着一定的内存单元: 变量和常量必须先定义后使用. 变量名和常量名都是一种标识符,用来标识变量和常量的.变量和常量必须用标识符(或者可以理解成一个名字)来表示后才能使用. 赋值表达式语句把值赋给变量,或者更一般地说,把值赋给存储空间. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

C++中的作用域与生命周期

Pascal之父Nicklaus Wirth曾经提出一个公式,展示出了程序的本质:程序=算法+数据结构.后人又给出一个公式与之遥相呼应:软件=程序+文档.这两个公式可以简洁明了的为我们展示程序和软件的组成. 程序的运行过程可以理解为算法对数据的加工过程,程序的运行的结果,就是算法加工数据产生的结果数据.算法描述的是对数据加工的步骤,对应于程序中的函数.数据结构描述的是数据在计算机中的组织结构,对应于程序中的数据类型.程序中数据对应的就是无处不在变量.对于我们编程人员,面对的无非就是函数,数据类型