目录
[1. auto 存储类型](#1. auto 存储类型)
[1.1 自动变量特性](#1.1 自动变量特性)
[1.2 举例](#1.2 举例)
[2. register 存储类型](#2. register 存储类型)
[2.1 寄存器变量特性](#2.1 寄存器变量特性)
[2.2 举例](#2.2 举例)
[3. extern 存储类型](#3. extern 存储类型)
[3.1 extern 存储类型特性](#3.1 extern 存储类型特性)
[3.2 举例](#3.2 举例)
[3.2.1 extern全局变量](#3.2.1 extern全局变量)
[3.2.2 extern函数](#3.2.2 extern函数)
[4. static 存储类型](#4. static 存储类型)
[4.1 static 存储类型特性](#4.1 static 存储类型特性)
[4.2 举例](#4.2 举例)
[4.2.1 静态局部变量](#4.2.1 静态局部变量)
[4.2.2 静态全局变量](#4.2.2 静态全局变量)
[4.2.3 静态函数](#4.2.3 静态函数)
C语言存储类型关键字(storage-class specifier)有:
- auto
- register
- static
- extern
这些说明符放置在它们所修饰的类型之前。
1. auto 存储类型
默认情况下,不加任何存储类型说明符修饰的所有在函数块内定义的变量或函数形参都是自动变量,但由 malloc,calloc 函数在堆中申请的内存除外,自动变量具有块作用域,生存期在定义它的块内。
1.1 自动变量特性
- 所有自动变量在函数被调用时在栈中创建,并在函数返回时销毁。
- 未初始化的自动变量存储的是一个垃圾值,除非显式初始化了它
- 函数形参默认也是自动变量,程序会在调用该函数前在栈中创建形参,并在函数返回时销毁
- 不能在所有函数外加 auto 关键字声明全局自动变量,编译器会报全局范围的声明不能用 auto 存储类型
1.2 举例
(1)试图在所有函数外加 auto 关键字声明全局自动变量,这时编译器报错
(2)函数形参加 auto 显式声明 和 函数体内加 auto 显式声明自动变量
cpp
#include <stdio.h>
int my_add(auto int a, int b) {
return a + b;
}
int main() {
int m = 1;
printf("m = %d\n", m);
for (auto int i = 1; i < 2; i++)
printf("i = %d\n", i);
printf("sum = %d\n", my_add(1, 2));
return 0;
}
- main 函数体内,m 是自动变量,具有块作用域,生存期在整个main函数体内
- main 函数体内,i 是自动变量,具有块作用域,生存期在定义它的for循环体内
- my_add 函数的形参 a 和形参 b 也是自动变量,具有函数作用域,生存期在 my_add 整个函数体内
注意:
auto 关键字在 C++ 中的用法完全不同,为了更好的兼容C++程序,不建议在声明变量时用 auto 关键字声明。
2. register 存储类型
所有在函数块内定义的在数据类型前用 register 修饰的变量或由 register 修饰的函数形参都是寄存器变量。
2.1 寄存器变量特性
- 用 register 关键词修饰的变量,只是一个建议,具体会不会接受编译器的这个建议,需要看硬件是否支持
- 寄存器变量在寄存器中,不在内存中,所以不能用 & 取地址,即使这个变量最终不一定在寄存器上
- 寄存器变量的访问速度比普通变量快,只用于需要快速访问的变量,比如计数器,而且变量占用的最大字节数不能超过寄存器的最大字节数。
2.2 举例
cpp
#include <stdio.h>
int my_add(register int a, int b) { // 寄存器形参 a
return a + b;
}
int main() {
register int sum = 0; // 寄存器变量 sum
sum = my_add(1, 2);
printf("sum = %d\n", sum);
return 0;
}
3. extern 存储类型
3.1 extern 存储类型特性
extern 关键字用于声明该全局变量或函数在其他文件中有定义,extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候。
3.2 举例
3.2.1 extern全局变量
global.c
cpp
const int SIZE = 1000;
main.c
cpp
#include <stdio.h>
extern const int SIZE;
int main() {
printf("SIZE= %d\n", SIZE);
return 0;
}
3.2.2 extern函数
global.c
cpp
int my_add(int a, int b) {
return a+b;
}
main.c
cpp
#include <stdio.h>
extern int my_add(int a, int b);
int main() {
printf("sum = %d\n", my_add(1, 2));
return 0;
}
4. static 存储类型
4.1 static 存储类型特性
- 用 static 声明的局部变量,改变了这个局部变量的生存期为静态生存期,即整个程序运行期间都存在。不改变局部变量的作用域。
- 用 static 关键字修饰的全局变量,改变了这个全局变量的作用域为文件作用域,即限制该全局变量只在定义它的源文件可见。不改变全局变量的生存期
- 用 static 声明的函数,改变了这个函数的作用域为文件作用域,即限制该函数只在定义它的源文件可见。
类型 | 作用域 | 生存期 |
---|---|---|
static 局部变量 | 局部作用域 | 整个程序运行期间都存在 |
static 全局变量 | 文件作用域 | 整个程序运行期间都存在 |
static 函数 | 文件作用域 | 整个程序运行期间都存在 |
注意:
变量的作用域和变量的生存期是两个不同的概率,不能搞混了。
- 变量的作用域是描述程序可以访问变量的区域,比如块作用域,函数作用域,文件作用域,全局作用域。
- 变量的生存期是描述变量在程序运行期间的生存期,比如函数形参在调用函数前在栈中创建,函数返回时销毁;局部变量在函数调用时在栈中创建,函数返回时销毁;用 malloc或calloc 在变量在堆创建,不用的时候需要程序员用 free 销毁;全局变量在静态存储区,程序在启动的时候进行创建并初始化,在整个程序运行期间都存在。
- 用 static 关键字修饰的局部变量,改变了这个局部变量的生存期为静态生存期,即整个程序运行期间都存在。
- 用 static 关键字修饰的全局变量,改变了这个全局变量的作用域为文件作用域,即限制该全局变量只在定义它的源文件可见。
4.2 举例
4.2.1 静态局部变量
cpp
#include <stdio.h>
typedef enum {
ADD,
SUB,
GET
}ENUM_TYEP;
int counter(ENUM_TYEP t, int n) {
static int c = 0; // 静态局部变量,存储在静态区,,生存期在程序运行期间都有存在,具有局部作用域。
if (t == ADD)
c += n;
else if (t == SUB)
c -= n;
return c;
}
int main() {
printf("counter = %d\n", counter(GET, 0)); // counter = 0
printf("counter = %d\n", counter(ADD, 10)); // counter = 10
printf("counter = %d\n", counter(SUB, 1)); // counter = 9
return 0;
}
静态局部变量,存储在静态区,,生存期在程序运行期间都有存在,具有局部作用域。
4.2.2 静态全局变量
global.c
cpp
static const int SIZE = 1000;
main.c
cpp
#include <stdio.h>
extern const int SIZE;
int main() {
printf("SIZE = %d\n", SIZE);
return 0;
}
在该程序中,global.c 源文件中的 static 全局常量 SIZE 具有文件作用域,只在定义它的源文件中可见,在 main.c 源文件不可见,所以编译报错。
4.2.3 静态函数
global.c
cpp
// 静态函数,只在定义它的源文件中可见
static int my_add(int a, int b) {
return a+b;
}
main.c
cpp
#include <stdio.h>
extern int my_add(int a, int b);
int main() {
printf("sum = %d\n", my_add(1, 2));
return 0;
}
在该程序中,global.c 源文件中的static函数具有文件作用域,只在定义它的源文件中可见,在 main.c 源文件不可见,所以编译报错。