C语言之存储类说明符

作用域 链接属性 存储时期 存储类型内容请先参考博文

C语言有5中存储类型修饰符: auto register static extern typedef
下面分别介绍各说明符的用法:

  • auto: 表明变量具有自动存储类型
    auto说明符只能用在具有代码块作用域的变量的声明中, 但是由于这类变量本身就具有自动存储类型(存储于运行时堆栈中), 所以auto通常只是起显式说明的作用.

  • register: 表明变量具有硬件寄存器存储类型
    register也只能用在具有代码块作用域的变量的声明中, 表示程序员希望将该变量放在CPU的寄存器中, 从而可以比普通变量更快的访问和操作该变量. 但是无法获得寄存器存储类型的变量的地址, 并且具体是否会将register声明的变量存放于寄存器中由编译器决定.

  • static:表明变量具有静态存储类型或则标识符具有内部链接属性

    • static用于具有代码块作用域的变量的声明时, 使该变量具有静态存储类型(存储于静态内存中).
    • static用于具有文件作用域的标识符(变量名, 函数名)时, 表明该标识符具有内部链接属性.
  • extern: 表明标识符具有外部链接属性或者该变量在别处定义
    想要理解extern的用法首先要理解C语言的声明与定义的区别:
    声明: 描述 程序其他地方定义的对象
    定义: 为对象 分配内存

如果下列语句出现在所有函数体之外

  • int i; 定义了int型变量, 为其分配内存, 并执行默认初始化为0
  • int i = 1; 定义了int型变量, 为其分配内存, 并初始化为1
  • extern int i = 1; 定义了int型变量, 为其分配内存, 并初始化为1, 由于标识符i具有文件作用域, 所以默认具有外部链接属性, extern的作用只是显式的说明标识符i具有外部链接属性
  • extern int i; 描述了变量i为int型, 并且在程序的 其他地方 定义(分配内存), 这里的其他地方包括当前编译单元(同一个源文件)或者其他编译单元(其他源文件), z这种用法可以称为 extern声明, 这种用法是的具有外部链接属性的标识符可以在多个源文件中使用, 并且指向同一个实体
// main.c
#include 
extern int i; // i的extern声明
int main(void)
{
    int i = 1; // i在当前编译单元定义
    printf("%d", i);
    return 0;
}
// main.c
#include 
extern int i; // i的extern声明
int main(void)
{
    printf("%d", i);
    return 0;
}
// define.c
int i = 1; // i在另一个编译单元定义

如果下列语句出现在函数体内

  • int i; 定义了int型变量, 为其分配内存, 但是不执行任何初始化, i的值随机, 访问该变量非法
  • int i = 1; 定义了int型变量, 为其分配内存, 并初始化为1
  • extern int i = 1; 该语句同样试图定义一个int变量i, 并且将标识符i设置为外部链接属性, 但是由于C语言规定无法在函数内部初始化外部变量, 所以该语句非法. 那么如果我们在函数外部使用语句 int i = 0 定义并初始化, 而将 extern int i = 1 看作赋值操作呢? 由于函数外部的 int i = 0 是i的定义, 函数内部的 extern int i = 1 也是i的定义, 这就造成i的重复定义错误
  • extern int i; 这条语句放在函数体内部时比较复杂, 举几个例子;
// program 1
// main.c
#include 
int main(void)
{
    extern int i;
    return 0;
}

program 1编译通过..

// program 2
// main.c
#include 
int main(void)
{
    extern int i;
    printf("%d", i);
    return 0;
}

program 2在win7+VS2015平台下报错: error LNK2001: 无法解析的外部符号 _i

// program 3
// main.c
#include 
int main(void)
{
    printf("%d", i);
    return 0;
}

program 3在win7+VS2015平台下报错: error C2065: “i”: 未声明的标识符

// program 4
// main.c
#include 
int i = 1;
int main(void)
{
    extern int i;
    printf("%d", i);
    return 0;
}

program 4编译成功, 打印出1.

// program 5
// main.c
#include 
int main(void)
{
    int i = 1;
    extern int i;
    return 0;
}

program 5在win7+VS2015下报错 error C2086: “int i”: 重定义

// program 6
// main.c
#include 
int main(void)
{
    extern int i;
    int i = 1;
    return 0;
}

program 6在win7+VS2015下报错 error C2086: “int i”: 重定义

// program 7
// main.c
#include 
int main(void)
{
    extern int i;
    printf("%d", i);
    return 0;
}
// define.c
int i = 1;

program 7编译通过, 打印出1.

综合上面例子可以猜想:
编译程序时首先判断i是否定义(存在内存), 若是未定义则将 extern int i; 当作对i的extern声明; 若是存在i的定义, 则再判断i的链接性, 若是外部链接则 extern int i; 作为i的extern声明, 若是i为无链接属性, 则 extern int i 作为对i的定义, 并且使用extern强制i为外部链接属性(由于未赋初始值所以不会产生 无法在函数内部初始化外部变量 的报错), 这样就会有两个i的定义, 所以报错 重复定义.

以上关于extern的用法只是个人猜想

  • typedef
    typedef说明符不会为对象预留存储空间, 将其归类到存储类说明符只是为了语法描述方便.

关于存储类说明符有以下几点要说明:

  • 一个声明最多只能有一个存储类说明符
  • 存储类说明符无法改变标识符的作用域
  • static 既可以用来修改存储类型也可以用来修改链接属性, 具体起什么作用由其所修饰的标识符的作用域决定
  • 只有当 extern 修饰的标识符具有外部链接属性时才会被当作 extern声明
  • register 修饰的变量实际会不会存放在寄存器中有编译器决定

你可能感兴趣的:(C语言)