来源:公众号【编程珠玑】
作者:守望先生
前言
这些是编程语言中的基本概念,如果你还不是非常明确地清楚标题的问题,并且不知道作用域,链接属性,存储期等概念的具体含义,那么本文你不该错过。为了更加清晰的理解我们的问题,需要先了解三个概念:作用域,链接属性,存储期。
作用域
C语言中,作用域用来描述标识符能够被哪些区域访问。
而常见作用域有以下几种:
为了便于说明,我们来看一个例子,就很容易理解了:
/****************************作者:守望先生来源:公众号编程珠玑个人博客:https://www.yanbinghu.com***************************************/#include <stdio.h>int num1 = 222; //定位在函数外,具有文件作用域static int num2 = 111; //定义在函数外,具有文件作用域int swap(int *a,int *b); //这里的a,b是函数原型作用域int swap(int *a,int *b){ if(NULL== a || NULL == b) goto error; else { int temp = *a; //定义在函数内,块作用域 *a = *b; *b = temp; return 0; } //printf("temp is %d\n",temp); //因为temp具有块作用域,因此在这里不能直接使用 error://goto语句的标签,函数作用域,因此在前面就可以引用 { printf("input para is NULL\n"); return -1; }}int main(void){ printf("num1=%d,num2=%d\n",num1,num2); swap(&num1,&num2); //num1 num2具有文件作用域,可以在main函数中直接使用 printf("num1=%d,num2=%d",num1,num2); return 0;}
#include <stdio.h>
int num1 = 222; //定位在函数外,具有文件作用域
static int num2 = 111; //定义在函数外,具有文件作用域
int swap(int *a,int *b); //这里的a,b是函数原型作用域
int swap(int *a,int *b)
{
if(NULL== a || NULL == b)
goto error;
else
{
int temp = *a; //定义在函数内,块作用域
*a = *b;
*b = temp;
return 0;
}
//printf("temp is %d\n",temp); //因为temp具有块作用域,因此在这里不能直接使用
error://goto语句的标签,函数作用域,因此在前面就可以引用
{
printf("input para is NULL\n");
return -1;
}
}
int main(void)
{
printf("num1=%d,num2=%d\n",num1,num2);
swap(&num1,&num2); //num1 num2具有文件作用域,可以在main函数中直接使用
printf("num1=%d,num2=%d",num1,num2);
return 0;
}
可以看到,error标签具有函数作用域,整个函数内都可见,而temp具有块作用域,因此在大括号外部,不能直接使用它。而num1和num2具有文件作用域,因此main函数可以直接使用它。
链接属性
在《hello程序是如何变成可执行文件的》我们说到了编译的过程,最后一个步骤就是链接。链接属性决定了在不同作用域的同名标识符能否绑定到同一个对象或者函数。或者说,不同作用域的标识符在编译后是否是同一个实体。
c变量有三种链接属性:
再稍作解释,没有static修饰,且具有文件作用域的变量,他们在链接时,多个同名标识符的变量最终都绑定到同一个实体。而static修饰的具有文件作用域的变量就不一样了,不同文件内,即便标识符名字相同,它们也绑定到了不同的实体。
因此,如果我们希望某个变量或函数只在某一个文件使用,那么使用static修饰是一个很好的做法。
同样的,来看一个例子。
/****************************作者:守望先生来源:公众号编程珠玑个人博客:https://www.yanbinghu.com***************************************/#include <stdio.h>int a = 5; //文件作用域,外部链接属性,其他文件可通过extern int a的方式使用该文件的astatic b = 6; //文件作用域,内部链接属性,即便其他文件也有同名标识符,它们也是不同的int main(void){ int sum = 0 ; //无链接属性 sum = a + b; printf("sum is %d\n",sum); return 0;}
#include <stdio.h>
int a = 5; //文件作用域,外部链接属性,其他文件可通过extern int a的方式使用该文件的a
static b = 6; //文件作用域,内部链接属性,即便其他文件也有同名标识符,它们也是不同的
int main(void)
{
int sum = 0 ; //无链接属性
sum = a + b;
printf("sum is %d\n",sum);
return 0;
}
从代码中可以看到,a和b都具有文件作用域,a具有外部链接属性,而b具有内部链接属性,sum具有块作用域,因此无链接属性。
存储期
实际上作用域和链接属性都描述了标识符的可见性,而存储期则描述了这些标识符对应的对象的生存期。存储期,也分下面几种:
关于初始化,可参考《C语言入坑指南-被遗忘的初始化》。
同样地,我们通过下面的代码来更好地理解存储期:
/****************************作者:守望先生来源:公众号编程珠玑个人博客:https://www.yanbinghu.com***************************************/#include <stdio.h>int num1 = 222; //静态存储期static int num2 = 111; //静态存储期int add(int a,int b){ static int tempSum = 0; //静态存储期 tempSum = tempSum + a + b; return tempSum;}int main(void){ printf("num1=%d,num2=%d\n",num1,num2); int sum = 0; //自动存储期 sum = add(num1,num2); printf("first time sum=%d\n",sum);//sum = 333 sum = add(num1,num2); printf("second time sum=%d\n",sum); //sum = 666 return 0;}
#include <stdio.h>
int num1 = 222; //静态存储期
static int num2 = 111; //静态存储期
int add(int a,int b)
{
static int tempSum = 0; //静态存储期
tempSum = tempSum + a + b;
return tempSum;
}
int main(void)
{
printf("num1=%d,num2=%d\n",num1,num2);
int sum = 0; //自动存储期
sum = add(num1,num2);
printf("first time sum=%d\n",sum);//sum = 333
sum = add(num1,num2);
printf("second time sum=%d\n",sum); //sum = 666
return 0;
}
另外,如果我们通过nm命令查看编译出来的程序文件的符号表,我们可以找到num1,num2,tempSum,而没有sum,前者所用的内存数量在编译时就确定了。关于nm命令的使用可以参考《linux常用命令-开发调试篇》。
$ gcc -g -o lifetime lifetime.c $ nm lifetime|grep num10000000000601038 D num1$ nm lifetime|grep num2000000000060103c d num2$ nm lifetime|grep tempSum0000000000601044 b tempSum.2289$ nm lifetime|grep sum$|grep num1
0000000000601038 D num1
$ nm lifetime|grep num2
000000000060103c d num2
$ nm lifetime|grep tempSum
0000000000601044 b tempSum.2289
$ nm lifetime|grep sum
$
什么全局变量,局部变量,静态局部变量,静态全局变量
到这里,我们就可以很容易区分上面的变量类型了。实际上这里只是换了一种说法:
全局:具有文件作用域的变量
静态:具有静态存储期或内部链接属性
局部:具有函数或块作用域的变量
因而结合起来,也就很好理解了。
当然,这仅仅是为了区分它们,这并不是它们的严格定义。更好的方法,是通过代码来理解:
#include <stdio.h>int num1 = 222; //全局变量static int num2 = 111; //静态全局变量int add(int a,int b){ static int tempSum = 0; //静态局部变量 tempSum = tempSum + a + b; return tempSum;}int main(void){ printf("num1=%d,num2=%d\n",num1,num2); int sum = 0; //局部变量 sum = add(num1,num2); printf("first time sum=%d\n",sum);//sum = 333 return 0;}
int num1 = 222; //全局变量
static int num2 = 111; //静态全局变量
int add(int a,int b)
{
static int tempSum = 0; //静态局部变量
tempSum = tempSum + a + b;
return tempSum;
}
int main(void)
{
printf("num1=%d,num2=%d\n",num1,num2);
int sum = 0; //局部变量
sum = add(num1,num2);
printf("first time sum=%d\n",sum);//sum = 333
return 0;
}
总结
本文总结如下:
参考
https://en.wikipedia.org/wiki/Global_variables
https://en.wikipedia.org/wiki/Local_variable
《C11标准文档》
关注公众号【编程珠玑】,获取更多Linux/C/C++/Python/Go/算法/工具等原创技术文章。后台免费获取经典电子书和视频资源