在C语言中,存储类定义了变量/函数的范围(可见性)和生命周期。这些说明符放在编译器前以理解变量的工作方式。C语言中有以下类型的存储类:
- 自动(Auto):这是所有局部变量的默认存储类。在函数体、循环体等内部声明的变量是自动变量。
- 寄存器(Register):这是自动的变体类,它告诉编译器使用寄存器来存储变量,而不是RAM。
- 静态(Static):指示编译器保留变量,即使超出了它的范围也是如此。它还指示编译器在程序启动时对变量进行初始化。
- 外部(Extern):指示我们的变量是在程序的其他地方定义的,也就是说,这是一个已存在的全局变量的引用。
- 类型限定符(Type Qualifiers):const, volatile, 和restrict是类型限定符,不是存储类,但它们也可以改变变量的存储类。
auto存储类
auto是C语言中的一个存储类,用于声明自动变量。这是所有局部变量的默认存储类。”auto”只能在函数内部使用,它是局部变量的默认类型。下面是一个简单的例子:
#include <stdio.h> void function(){ int i = 0; // same as "auto int i = 0;" printf("%d", i); i++; } int main(){ function(); // prints: 0 function(); // prints: 0 return 0; }
在这个例子中, i就是一个auto变量。当function()被调用时,变量i被初始化为0。当函数再次被调用时,i又被重新初始化为0。
需要注意的是,在C语言中,我们通常省略auto关键字,直接写变量的类型和名称。因为如果没有指定存储类,编译器默认变量为auto。
register存储类
register是C语言中的一个存储类,用于建议编译器将某个变量存储在寄存器中,而不是RAM中。这样做的原因是,从寄存器中访问变量比从RAM中快得多。然而,这只是一个建议,编译器可能会忽略这个建议。
register只能用于局部变量和函数的输入参数。寄存器的数量是有限的,且可能已经被用于其他目的,因此并非所有请求都能被满足。
下面是一个使用register的例子:
#include <stdio.h> void function(){ register int i; // "register" suggests to store "i" in a register for(i = 0; i < 10000; i++){ printf("%d", i); } } int main(){ function(); return 0; }
在这个例子中,我们建议编译器将循环变量i存储在寄存器中。这样做是因为i将在循环中被频繁访问,将其存储在寄存器中可能会提高速度。
需要注意的是,对于现代的优化编译器,你可能不需要显式声明register,编译器会自动进行寄存器分配优化。
static存储类
static是C语言中的一个存储类,它可以用于声明静态变量。
当变量被声明为static时,空间将在程序的生命周期内一直存在,即使超出了它的范围也是如此。同时,static变量在程序启动时只初始化一次。
static存储类有以下用途:
- 如果在函数体内部声明静态变量,那么这个变量就会在函数调用之间保持其值。
- 在函数外部声明的静态变量可以在整个文件中访问,而不仅仅是在声明它的函数中。这样,它们就像全局变量,但只能在一个文件中使用。
下面是一个使用static变量的例子:
#include <stdio.h> void function(){ static int i = 0; // "static" means "i" keeps its value between function calls printf("%d", i); i++; } int main(){ function(); // prints: 0 function(); // prints: 1 return 0; }
在这个例子中,变量i是在函数内部声明的静态变量。尽管function()被调用了两次,但i在两次调用之间保持了它的值。
需要注意的是,如果static变量没有显式初始化,那么它会被隐式初始化为0。
extern存储类
extern是C语言中的一个存储类,它用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当你使用extern时,对于变量或者函数的定义来说,你是在告诉编译器这个变量或者函数在别的地方已经定义过了,现在只需要使用就可以了。
下面是一个使用extern存储类的例子:
假设有两个C文件,file1.c和file2.c。
file1.c:
#include <stdio.h> int count; // Global variable, can be accessed by other files using extern void main(){ printf("The value of count is: %d", count); }
file2.c:
#include <stdio.h> extern int count; // count is defined in another file, this is just a declaration void func(){ count = 5; } void main(){ func(); printf("The value of count is: %d", count); // prints: 5 }
在这个例子中,count在file1.c中声明并定义,而在file2.c中,我们使用extern关键字来声明变量count。这告诉编译器,变量count是在别的地方定义的,只需要引用它就可以了。
注意,使用extern声明的变量,没有分配存储空间。其存储空间是在变量定义的地方分配的。
类型限定符(Type Qualifiers)
const,volatile和restrict被称为类型限定符,它们并不是存储类,但是它们可以影响变量的行为。
const
这个关键字让我们可以声明常量。常量是在程序执行期间不会改变的值。尝试改变常量的值会导致编译错误。
const int a = 10; // "a" is a constant, its value cannot be changed a = 20; // Error
volatile
这个关键字用来告诉编译器,该变量的值可能会在外部被意外(不可预测的)改变,这样,编译器就不会对这个变量进行优化,每次引用时都会从其所在的内存中提取,而不会使用保存在寄存器中的备份。这对硬件访问操作、多线程编程等方面非常有用。
volatile int a = 10; // The value of "a" can be changed unexpectedly
restrict
这个关键字是C99标准中引入的,用于告诉编译器,所有修改某个对象或者对象所指向的值的操作,都必须直接通过该对象来完成(也就是说,不存在其他途径可以用来修改该对象或者对象所指向的值)。这个关键字主要被用在指针类型中,能够帮助编译器进行更好的优化。
int arr[10]; int *restrict restp = arr; // restp is the only way to access arr for the scope of restp
请注意,const和volatile可以同时应用于一个变量,例如const volatile int a;声明了一个既是const又是volatile的变量。