您的位置:首页 > 编程语言 > C语言/C++

关于C变量作用域和生存期的常见问题

2016-07-21 19:12 323 查看
作用域

作用域描述了程序中可以访问一个标识符的一个或多个区域,一个C变量的作用域可以是代码块作用域、函数原型作用域,或者文件作用域。代码块是包含在一对花括号内的一段代码,在代码块中定义的变量具有代码块作用域,从该变量被定义的地方到包含该定义的代码块的末尾该变量均可见,但只局限于代码块。因此,函数作用域也属于代码块作用域。

一个在所有函数之外定义的变量具有文件作用域,从定义开始到包含该文件结尾都可见,文件作用域变量也称为全局变量。

生存期

一个C变量有两种生存期:静态生存期和自动生存期。一个静态变量在程序执行期间将一直存在,一个自动变量在作用域结束后内存将释放。

一般使用 static 声明变量的静态生存期,而对于文件作用域变量,都具有静态生存期。

#include <stdio.h>
int lint = 1;   // lint 具有文件作用域、静态生存期
void print(int data)
{
int i, *p;  // 作用域开始,局部变量
for(i=0; i<lint; i++)
{
static int temp = 1;    // 具有代码块作用域、静态生存期
int pd = data * i;  // pd作用域开始
. . .
temp++;
p = &temp;  // p指向temp所在地址
printf(“data = %d\t”, pd);
}   // pd作用域结束
. . .
// printf(“temp = %s\n”, temp); // 错误,temp 已离开作用域
printf(“temp = %s\n”, *p);  // 正确,temp 不可见,但是一直存在,其地址也可用
}   // i 作用域结束


1 具有代码块作用域的静态变量

未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非被显示初始化)。

在调用包含具有代码块作用域静态变量的函数时,由于其一直存在,因此在每次调用时print()函数时,其它自动变量都被初始化,而temp只在编译print()时初始化一次。

int main(void)
{
int c;
for(c=0;c<=3;c++)
{
print(1);
}
return 0;
}


返回下列结果

data = 1      temp = 2

data = 1      temp = 3

data = 1      temp = 4

2 返回字符串的函数

在调用返回字符串的函数时,有时候返回的是垃圾信息。例如下面的程序

char *itoa(int n)
{
char retbuf[20]; // 错!
sprintf(retbuf, "%d", n);
return retbuf; // 错!
}


因为局部数组在函数调用完成之后,堆栈的空间将释放,因而返回指向数组的指针无效。

一种解决方案是把缓冲区声明成静态生存期,这样在程序运行期间局部数组将一直存在

static char retbuf[20];

另一种解决方案是使用 malloc申请动态内存空间

char * retbuf = malloc(20);

虽然指针retbuf 是局部变量,离开调用函数后将被释放,但是申请的内存空间在没有使用 free之前将一直存在。因此也可能产生了一个问题:申请的内存空间没有被释放,可行的方法是用完返回的字符指针之后,使用free将该地址处的内存释放,前提是这个指针没有移动。

3 static 与全局变量

由于全局变量自动具有静态生存期,在全局变量之前加上 static,表示全局变量的链接方式。

具有外部链接的全局变量,其他文件可以使用;具有部链接的全局变量,属于文件私有,但是可以被该文件中的任一函数使用。为了使程序更加清晰,可以在使用全局变量的函数中通过使用关键字extern 来再次声明它。

// file1.c
int giant = 5;  // 全局变量,外部链接
static int dodg = 3;    // 全局变量,内部链接
int main(void)
{
extern int giant;   // 可选的声明
extern int dodg;    // 可选的声明
. . .
}


如果变量是在别的文件中定义的,使用 extern来声明该变量是必须的。其它文件中引用声明

// file2.c
static int dodg = 3;    // 可以
int giant = 5;      // 命名冲突,在file1.c中已经定义了
int main(void)
{
extern int giant;   // 必须的声明
// extern int dodg; // 错误的声明,因为dodg为内部链接
. . .
}


两个具有相同名称的外部对象实际上代表的是同一个对象,当在两个不同源文件中都包括了一个相同的定义,那么这将表示程序错误(如果连接器禁止外部变量重复定义的话),或者两个源文件共享同一个实例。为了避免这类命名冲突的问题,static是一个有用的工具。

注意:当static用来修饰局部变量的时候,它就改变了局部变量的存储位置,从原来的栈中存放改为静态存储区。但是局部静态变量在离开作用域之后,并没有被销毁,而是仍然驻留在内存当中,直到程序结束,只不过我们不能再对他进行访问。

4 静态函数

在函数的返回类型前加上关键字static,函数就被定义成为静态函数。

函数的定义和声明默认情况下是extern的,与具有内部链接的全局变量一样,静态函数只是在声明它的文件当中可见,不能被其他文件所用。例如

// file1.c
void dis();
static void static_dis();
int main()
{
dis();
static_dis();
renturn 0;
}


// file2.c

void dis()
{
static_dis();
}
static void static_dis()
{
printf("static_dis() of file2.c has been called/n");
}


文件分别编译通过,但是连接的时候找不到函数static_dis()的定义,产生错误。

定义静态函数的好处:其他文件中可以定义相同名字的函数,不会发生冲突
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息