在C语言中,每一个变量和函数都有两个属性:数据的类型和数据的存储类别。
C语言的存储类别包括4种:自动的(auto)、静态的(static)、寄存器的(register)、外部的(extern)。根据变量的存储类别,可以知道变量的作用域和生存期。
一个C/C++程序运行时内存主要分为以下几个部分:代码区、文字常量区,全局(静态)存储区、堆区和栈区。当然,关于内存的分布区域图虽然五花八门,但大同小异,归根结底都是一样的,关于Linux下内存分配问题后面专门写一篇博客来进行详细的讨论。
C语言中,一个进程的内存空间逻辑上可分为3个部分:代码区、静态存储区、动态存储区。
静态存储区:全局变量和静态变量
动态存储区:堆和栈
参考文章:
C语言 变量的作用域和生命周期
对一个C/C++变量来说,有两个属性非常重要:作用域和生存周期。
作用域问题:在函数不同位置定义的变量,作用范围有什么不同?在一个函数中定义的变量,在其他函数中能否被引用?
生存周期问题:不同生存周期的变量,在程序内存中的分布是不一样的。C语言中,用户使用的存储空间可以分为:程序区、静态存储区、动态存储区,不同的内存区域,对应不同的生存周期。
局部变量:
①函数内部定义的变量,只有在本函数内才能引用它们,也只有在本函数范围内有效;
②复合语句内定义的变量(如for循环中的变量i),只有在本复合语句内才能引用它们,也只有在本复合语句范围内有效。
函数中的局部变量,如果不专门声明是static存储类别,都是动态地分配存储空间的(栈区)。函数中的形参和在函数中定义的局部变量(包括符合语句中的局部变量)都属于此类。如函数中定义变量 int a; 和 auto int a; 是等价的,关键字“auto”是默认省略的。
属于某个{},在{}外部不能使用此变量,在{}内部是可以使用。执行到普通局部变量定义语句,才会分配空间,离开{},自动释放。普通局部变量若不初始化,默认值为随机数。
作用域:函数内
生命周期:函数调用结束时释放
内存分布:栈区
注意:auto关键字(默认省略)
有时希望函数中的局部变量的值在函数调用结束后不消失,继续保留原值,即其占用的存储单元不释放,在下一次再调用该函数时,该变量已有值。此时应该用static声明该变量为“静态局部变量”。
注意:静态局部变量是在函数编译时赋初值的,在程序运行时它已经有初值。以后每次调用函数时就不再重新赋值,只是保留上一次函数调用结束时的值。虽然静态局部变量在函数调用结束后仍然存在,但是其他函数不能引用它,因为他是局部变量,只能被本函数引用,不能被其他函数引用。
属于某个{},在{}外部不能使用此变量,在{}内部是可以使用。在编译阶段就已经分配空间,初始化只能使用常量。static局部变量不初始化,默认值为0。离开{},static局部变量不会释放,只有整个程序结束才释放。
注意:静态局部变量的作用域属于某个{},但是它的生命周期却是从编译阶段到整个程序结束。
作用域:函数内
生命周期:编译阶段到整个程序结束
内存分布:全局(静态)数据区
注意:只被初始化一次,多线程中需加锁保护
关于局部变量:注意事项
A、静态局部变量属于静态存储类别,在静态存储区内分配存储单元。自动变量(即动态局部变量)属于动态存储类别,分配在动态存储空间中。
B、静态局部变量实在编译时赋初始值的,即只赋值一次。
C、如果在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值0(对数值型变量)或空字符‘\0’(对字符变量)。而对自动变量来说,它的值是一个不确定的值。这是由于每次函数调用结束后存储单元已经释放,下次调用时又重新另分配存储单元,所分配的单元中的内容时不可知的。
D、虽然静态局部变量在函数调用结束后仍然存在,但别的函数不能引用它。因为它是局部变量。
一般情况下,变量(包括动态存储和静态存储方式)的值是存放在内存中的,当程序用到哪一个变量时,有控制器发出指令将内存中改变量的值送到运算器中。经过运算器运算,如果需要存数,再从运算器中奖数据送到内存存放。如果一些变量的存储读取在一个函数中要反复进行,可以声明为register存储类型,将变量的值存放在CPU中的寄存器中,由于寄存器的存取速度远远高于内存的存取速度,因此这样可以提高执行效率。
注:由于现在的计算机的速度愈来愈快,性能愈来愈高,优化的编译系统能够识别出使用频繁的变量,从而自动地将这些变量放在寄存器中,而不需要程序设计者指定。因此,现在实际上用register声明变量的必要性不大。
全局变量又称外部变量,都是存储在全局(静态)存储区。全局变量的使用不像局部变量那么简单,下面讨论一下全局变量的三种使用方法。
全局变量如果不在文件的开头定义,其有效范围就是其定义处到文件结束,所以定义点之前的函数是不能引用该变量的。为了解决这个问题,引入了extern关键字。在引用之前对全局变量进行声明,表示把该变量的作用域扩展至此。
示例:
#include
int test()
{
extern int Num;
printf("The num is : %d\n", Num);
return 0;
}
int Num = 666;
int main()
{
test();
return 0;
}
//运行的结果时 The num is : 666
如果几个文件中都要用到一个全局变量Num,又不能在两个文件中分别定义一个Num外部变量(注:如果在两个文件中都定义了相同名字的全局变量,链接出错:变量重定义)。
测试1: 如果我们头文件中定义全局变量,多个.c文件引用同一个头文件,此时编译器会提示重定义的问题。
//header.h
#ifndef _HEADER_H
#define _HEADER_H
int Num = 10; //头文件中定义全局变量
#endif
//print.h
#ifndef _PRINT_H
#define _PRINT_H
void print();
#endif
//print.c
#include "print.h"
#include "header.h"
#include
void print()
{
printf("全局变量的地址:%p\n",&Num);
}
//main.c
#include
#include "header.h"
#include "print.h"
int main()
{
printf("全局变量的地址:%p\n",&Num);
print();
return 0;
}
运行结果:
测试2: 也就是正确的做法,在一个.c文件中定义全局变量Num,并在这个.c文件的头文件中使用extern声明一下,或者在其它.c文件中使用时,用extern做“全局变量声明”,告诉编译器,去其他的地方找这个变量的定义。
//print.h
#ifndef _PRINT_H
#define _PRINT_H
void print();
#endif
//print.c
#include "print.h"
#include
extern int Num; //全局变量声明
void print()
{
printf("全局变量的地址:%p\n",&Num);
}
//main.c
#include
#include <print.h"
int Num = 10; //定义全局变量Num
int main()
{
printf("全局变量的地址:%p\n",&Num);
print();
return 0;
}
运行结果:
注: extern即可以用来扩展外部变量在本文件中的作用域,又可以使外部变量的作用域从一个文件扩展到程序中的其他文件。编译器区别处理原则是:遇到extern时,先在本文件中找外部变量的定义,如果找到,就在本文件中扩展作用域;如果找不到,就在链接时从其他文件中找外部变量的定义,如果都没有就会报错。
有时在程序设计中希望某些全局变量只限制被本文件引用,而不被其他文件引用,这时可以在定义全局变量时加一个static关键字,称为静态全局变量。
注: 只要文件不互相包含,在两个不同的文件中是可以定义完全相同的两个静态变量的,它们是两个完全不同的变量
测试1: 本文件中的是static全局变量能否被其它文件引用
//print.h
#ifndef _PRINT_H
#define _PRINT_H
void print();
#endif
//print.c
#include "print.h"
#include
extern static int Num;
void print()
{
printf("静态全局变量的地址:%p\n",&Num);
printf("静态全局变量:%d\n",Num);
}
//main.c
#include "print.h"
#include
static int Num = 10; //定义静态全局变量
int main()
{
printf("静态全局变量的地址:%p\n",&Num);
printf("静态全局变量:%d\n",Num);
print();
return 0;
}
运行结果:
倘若我们在print.c文件中也定义一个静态全局变量,看看结果是怎样的。
print.c文件修改如下,print.h文件和main.c文件不变。
//print.c
#include "print.h"
#include
static int Num = 20;
void print()
{
printf("静态全局变量的地址:%p\n",&Num);
printf("静态全局变量:%d\n",Num);
}
运行结果:
我们发现,两个不同的.c文件完全可以定义相同的static全局变量,它们是不同的两个变量。
测试2: 静态变量放在.h文件,编译器会为每一个引用.h文件的.c文件生成一个static变量。即在两个不同的文件中是可以定义完全相同的两个静态变量的,它们是两个完全不同的变量
//print.h
#ifndef _PRINT_H
#define _PRINT_H
static int Num = 100; //头文件中定义静态全局变量
void print();
#endif
//print.c
#include "print.h"
#include
void print()
{
printf("print函数中,静态全局变量的地址:%p\n",&Num);
printf("print函数中,静态全局变量:%d\n",Num);
}
//main.c
#include "print.h"
#include
int main()
{
printf("main函数中,静态全局变量的地址:%p\n",&Num);
printf("main函数中,静态全局变量:%d\n",Num);
print();
return 0;
}
运行结果:
可以发现两个静态变量的地址并不一样,说明编译器会为每一个.c文件生成一个static全局变量。
注意: static关键字用于局部变量和全局变量其作用是不同的。局部变量原本存数在动态存储区(栈区),在用static声明之后,其存储空间就在静态存储区了;而全局变量,其存储空间一直在静态存储区,用static声明之后,只是限定了它作用域只能在本文件中。从作用域角度看,凡是static声明的,其作用域都是局限的,要么是局限于一个函数内(静态局部变量),要么是局限与本文件内(静态全局变量)。
参考文章:
C–变量的存储方式、作用域和生命周期
static 、全局变量、const变量,局部变量的作用域,生命周期问题
上一节我们已经详细分析了static关键字与全局变量的配合使用,这一节通过代码简单分析下static用法。
代码来自:C语言static 关键字总结
测试1: 变量不被static修饰
#include
void test()
{
int num = 0;
num++;
printf("%d ", num);
}
int main()
{
int i = 0;
for (i = 0; i < 10; i++)
{
test();
}
printf("\n");
return 0;
}
运行结果:
测试2: 变量被static修饰
#include
void test()
{
static int num = 0;
num++;
printf("%d ", num);
}
int main()
{
int i = 0;
for (i = 0; i < 10; i++)
{
test();
}
printf("\n");
return 0;
}
运行结果:
总结:
(1) 不加static修饰,函数或者代码块中的变量在函数或者代码块执行完毕后就直接回收销毁了,每次执行都会重新分配内存,每次都会销毁。
(2) 加 static 修饰,函数或者代码块中的变量在函数或者代码块执行第一次初始化分配内存后,就算函数或者代码块执行完毕,该变量也不会被回收 销毁,直到程序结束 static 变量才会被回收。
(3) 当 static 作用于函数定义时,或者用于代码块之外的变量声明时,static关键字用于修改标识符的链接属性。外部链接属性变为内部链接属性,标识符的存储类型和作用域不受影响。也就是说变量或者函数只能在当前源文件中访问,不能在其他源文件中访问。
(4) 当static 作用于代码块内部的变量声明时,static关键字用于修改变量的存储类型。从自动变量变为静态变量,变量的属性和作用域不受影响。
static关键字用法还有很多,且有很多是C++里面的内容,这里就不进行详细的介绍了。关于static关键字的详细用法,读者可参考下面的文章:
static的用法
c语言中static关键字用法详解
#include
#include
#include
int a = 0; //全局初始化区
char *p1; //全局未初始化区
int main()
{
int b; //栈
char s[] = "abc"; //栈
char *p2; //栈
char *p3 = "123456"; //"123456\0"在常量区,p3在栈上
static int c =0; //全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
//分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456");
return 0;
}
参考文章:
字符串常量到底存放在哪个存储区
c中字符串常量的若干问题