C语言中的作用域、链接属性与存储属性

C语言中的作用域、链接属性与存储属性

一、作用域(scope)

  1. 代码块作用域

    表示{}之间的区域,下例所示,a可以在不同的代码块里面定义。

    #include<stdio.h>
    
    int main()
    {
        int f(int g){
            return g;
        }
    
        int a = 0;
        {
            int a = 2;
            printf("inner: %d
    ", a);
        }
        printf("outter: %d
    ", a);
        printf("inline function:%d
    ", f(10));
    }
    
    $ ./a.out
    inner: 2
    outter: 0
    inline function:10
    

C语言居然支持了内部函数,好神奇。

```c
//error: ‘i’ redeclared as different kind of symbol
int fun(int i)
{
    int i = 0;
    return i;
}
```
ANSI C中,形参的作用域为函数最外层的那个作用域,不能在函数体内声明同名的变量(K&R C可以,屏蔽形参)。
  1. 文件作用域

    任何在所有代码块之外声明的标识符都具有文件作用域(file scope)。并且,通过#include指令包含到其他文件中的声明就好像它们是直接写在那些文件中一样。它们的作用域并不局限于头文件的文件尾。

  2. 原型作用域

    int fun(int a);
    

    原型作用域(prototype scope)只适用于在函数原型中声明的参数名,将它独立出来只是为了这个名字不能与其他作用域的符号混淆,它其实没有什么用得,甚至都可以不写。事实上,唯一可能出现的冲突就是在同一个原型中不止一次使用同一个名字。

  3. 函数作用域

    它只适用于语句标签,用于goto语句。《C和指针》的作者说愿你永远不需要这方面的知识,哈哈!!!

二、链接属性

当组成一个程序的各个源文件分别被编译之后,所有的目标文件以及那些从一个或多个函数库中引用的函数链接在一起,形成可执行文件。问题是,如果相同的标识符出现在几个不同的源文件中时该怎么办?标识符的链接属性决定如何处理在不同文件中出现的标识符。标识符的作用域与它的链接属性有关,但这两者并不相同。

链接属性一共有3种:

  1. none(无)

    总是被当做单独的个体,也就是说该标识符的多个声明被当作独立不同的实体。

  2. internal(内部)

    在同一个源文件中的所有声明中都指向同一个实体,但位于不同源文件的多个声明则分属不同的实体。

  3. external(外部):

    不论声明多少次、位于几个源文件都表示同一个实体。

    extern和static两个关键字可以用于设定标识符的链接属性。当没有这两个关键字时,默认的链接属性与标识符的作用域相关。

    typedef char *a;
    int b;
    int c(int d)
    {
        int e;
        int f(int g);
        ...
    }
    

    上面的代码块中,b、c、f有external链接属性,f在本代码中被调用,定义在其他源文件或者库中,所以也是external属性。其他的标识符都是none属性。

    static关键字可以把一个默认为external属性的标识符改为internal,如上例中,可以把b、c的链接属性改为internal,使其在其他源文件中不可见。

    static int b;
    static int c(int d);
    

    extern 关键中可以把none属性改为external属性。

    // linkage_test1.c
    #include<stdio.h>
    
    extern int a; // 可选,因为默认就是external
                  // 但是应该写上,增加程序可读性
    
    int main() {
        printf("a = %d
    ", a);
        extern int b; // 必需,默认为none
        printf("b = %d
    ", b);
    }
    
    // linkage_test1.c
    int a = 1;
    int b = 2;
    
    

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

    static int i;
    int func()
    {
        extern int i; //i的链接属性仍然为static
    }
    

三、存储类型

变量的存储类型是指存储变量值的内存类型。变量的存储类型决定变量何时创建、何时销毁以及它的值将保持多久。有三个地方可以用于存储变量:普通内存、运行时堆栈、硬件寄存器。在这三个地方存储的变量具有不同的特性。

  1. 普通内存变量

    变量的缺省存储类型取决于它声明的位置(作用域),凡是在任何代码块之外声明的变量总是存储与静态内存中,也就是不属于堆栈的内存,这类变量成为静态变量(static),可以通过static关键字将一个代码块内部变量由堆栈类型变为静态类型。静态类型的标识符存在ELF文件的.data(已初始化)或者.bss段(未初始化,默认值为0)。这些变量在程序未运行之前(通过内存加载)已经存在。

  2. 堆栈变量

    在代码块内部声明的变量的默认存储类型是自动的(automatic),可以使用关键字auto指定,但它极少使用,因为完全没必要。这些自动变量存在堆栈中。

  3. 硬件寄存器变量

    你可以通过关键字register来指定,提示程序运行时用硬件寄存器来存储该变量,但是编译器可以不鸟你,因为它认为它比你更清楚那个变量该用寄存器那个该用堆栈。

变量的初始化

静态变量只能用常数进行初始化(其他静态变量都不行),如果未显性初始化,则默认值为0;

堆栈变量可以通过任何合法表达式初始化,因为它是在运行时创建,如果不初始化,其默认值为垃圾。

引用

--C和指针/ (美)里科(Reek, K. A.)著;徐波译. —北京:人民邮电出版社,2008. 4(2016. 5重印)

原文地址:https://www.cnblogs.com/keviwu/p/5903962.html