相关阅读C语言https://blog.csdn.net/weixin_45791458/category_12423166.html?spm1001.2014.3001.5482目录作用域块作用域变量定义位置函数作用域函数原型作用域 特殊情况文件作用域链接无链接内部链接外部链接存储期自动存储期静态存储期动态存储期线程存储期作用域作用域(Scope)描述程序中标识符的生效区域可访问区域一个C语言变量的作用域可以是块作用域、函数作用域、函数原型作用域、文件作用域。块作用域块是用一对花括号括起来的代码区域例如整个函数体是一个块函数中的复合语句也是一个块。定义在块中的变量具有块作用域块作用域变量的可访问区域是从定义位置到变量所属块结尾。即使没有在代码块中函数的形参也被视为具有块作用域。例1给出了一个简单的例子。// 例1 void func(int t) { // t具有块作用域 int a 10; // a具有块作用域 { int b 20; // b具有子块作用域 } // b的作用域结束 // 这里可以继续使用a和t但不能再使用b }变量定义位置在C90及以前的标准也叫ANSI C中所有具有块作用域的变量必须在块的最开头声明在任何可执行语句比如if、for语句之前例2所示的语句在C90及以前的标准也叫ANSI C中是不合法的。// 例2 void func() { int a 10; printf(%d\n, a); int b 20; // C90标准不允许这样在执行语句后再声明变量 } // 报错error: ISO C90 forbids mixed declarations and code [-Werrordeclaration-after-statement]C99标准放宽了这一限制允许在块中的任意位置声明变量甚至在for语句头中定义变量如例3所示。需要注意的是for语句头中定义的变量和循环块中定义的变量具有块作用域即使循环体没有使用花括号括起来。// 例3 void func() { for (int i 0; i 5; i) { // i具有子块作用域 int square i * i; // square具有子块作用域 } }函数作用域函数作用域是只针对标签名而言的即标签名在整个函数内都有效如例4所示需要注意的是标签名是指goto语句跳转用的标签而不是普通的变量名。// 例4 void func() { label1: // 这是一个标签具有函数作用域 printf(At label1\n); goto label1; // 在函数内任何地方都能跳到label1 }函数原型作用域函数原型作用域用于函数声明中的形参定义范围是从形参定义处到原型声明结束。这意味着编译器在处理函数原型中的形参时只关心它的类型而形参名如果有的话通常无关紧要即使有形参名也不必与函数定义中的形参名相匹配如例5所示。// 例5 int add(int a, int b); // a和b具有函数原型作用域 int add(int x, int y) { // 这里即使参数名换了也没问题 return x y; }特殊情况当变长数组(VLA)出现在函数原型作用域时作为变长数组索引的形参名不能省略如例6所示。同样形参名依旧不必与函数定义中的形参名相匹配。// 例6 int use_a_VLA(int n, int m, int ar[n][m]); // a、b和ar具有函数原型作用域 int use_a_VLA(int x, int y, int br[x][y]) { // 这里即使参数名换了也没问题 return 0; }文件作用域定义在函数外的变量具有文件作用域文件作用域变量的可访问区域至少是从定义位置到变量所属翻译单元(translation unit)的结尾如例7所示这里的“至少”是因为文件作用域变量可以拥有内部链接和外部链接两种属性由于可以被多个函数访问因此文件作用域变量又被称为全局变量。// 例7 // 文件test1.c int var 100; // var具有文件作用域 #include test1.h void global() { int a var; } // 文件test1.h int b var;翻译单元指的是由预处理器完成了所有插入展开后并作为gcc的命令行参数的一个源代码文件。如果gcc的命令行参数有多个文件将在编译、汇编后使用链接器对目标文件(.o)进行链接。链接链接(Linkage)描述程序中标识符在多个翻译单元之间的生效区域可访问区域一个C语言变量的链接属性可以是无链接、内部链接和外部链接。无链接具有块作用域、函数作用域和函数原型作用域的变量都是无链接的意味着这些变量只能在一个翻译单元中的部分区域访问私有。内部链接具有文件作用域的变量可以是内部链接内部链接代表其可以在一个翻译单元内使用如文件作用域一节所述。外部链接具有文件作用域的变量可以是外部链接外部链接代表其可以在多个翻译单元共之间享如例8所示。// 例8 // 文件test1.c extern int var; // var具有文件作用域外部链接 void external_global() { int a var; } // 文件test2.c int var 100; // gcc test1.c test2.c -o my_program存储期作用域和链接描述了变量的生效区域而存储期(Storage Duration)指的是变量在内存/寄存器中存在的时间一个C语言变量的存储期可以是自动存储期、静态存储期、动态存储期和线程存储期。自动存储期块作用域的变量通常具有自动存储期当程序进入定义这些变量的块时为这些变量分配内存但不一定立刻初始化当退出这个块时释放为变量分配的内存。需要注意的是子块的变量一般在函数调用时就会分配内存大部分现代编译器都是这么做的而不是等进入子块时才分配内存在函数返回时才释放内存而不是退出子块就释放内存这也就导致了可以通过指针间接访问如例9所示。// 例9 void func(int t) { // t具有自动存储期 int *a; // a具有自动存储期 { int b 20; // b具有自动存储期 a b; } printf(%d, *a); // 大部分现代编译器没问题但是不要这么做 }静态存储期如果变量具有静态存储期那么该变量在程序执行期间一直存在。所有具有文件作用域的变量都是具有静态存储期对于具有块作用域的变量如果使用static关键字定义则其具有静态存储期否则其具有自动存储期如例10所示。具有静态存储期的变量只能用常量初始化如果不进行显式初始化则初始化为0。// 例10 int c; // c具有静态存储期初始化为0 int* func() { int *d; static int a; // a具有静态存储期初始化为0 { static int b 20; // b具有静态存储期初始化为20 d b; } printf(%d, *d); // 没有任何问题 return a; }对于例10而言变量a、b、c都具有静态存储期即变量a、b在程序开始执行时就存在并不是执行到函数时才分配内存但依旧受到作用域的访问限制和例9一样可以使用指针间接访问也可以返回静态存储期变量的指针给其他函数访问。动态存储期使用malloc()函数可以创建动态存储期的内存块完全由程序员管理因此需要记得在使用完毕后用free()函数释放否则会造成内存泄漏如例11所示。// 例11 void func() { int *d; { d (int*)malloc(sizeof(int)); *d 20; } printf(%d\n, *d); free(d); }线程存储期线程存储期用于并发程序设计程序可创建多个子线程每个线程拥有属于自己的专属变量该变量在线程结束时释放使用_Thread_local关键字定义C11标准引入如例12所示。// 例12 #include stdio.h #include pthread.h _Thread_local int x 0; // 每个线程有自己的x void* thread_func(void* arg) { x; printf(Thread %d: x %d\n, *(int*)arg, x); return NULL; } int main() { pthread_t t1, t2; int id1 1, id2 2; pthread_create(t1, NULL, thread_func, id1); pthread_create(t2, NULL, thread_func, id2); pthread_join(t1, NULL); pthread_join(t2, NULL); return 0; }