C|一个小实例了解变量存储属性、作用域、生命周期

一个变量的声明、定义包括存储类型、类型修饰符、类型以及标识符或初始值:

static unsinged int i = 22;

变量根据其存储类型声明,声明位置(函数外、函数内不同的语句块)而具有不同的存储属性、作用域和生命周期。

1 变量与常量

在C/C++程序里,参与计算的数据都是通过存放在内存中不同类型的常量或者变量来表示的。

变量是内存或寄存器中用一个标识符命名的存储单元,可以用来存储一个特定类型的数据,并且数据的值在程序运行过程中可以进行修改。可见,变量首先是一个标识符或者名称,就像一个客房的编号一样,有了这个编号我们在交流中就可方便言表,否则,我们只可意会,那多不方便。为了方便,我们在给变量命名时,最好能符合大多数人的习惯,基本可以望名知义,便于交流和维护;其次,变量是唯一确定的对应内存若干存储单元或者某个寄存器。这些是编译器来保证的,用户一般不用费心。

程序员一旦定义了变量,那么,变量就至少可为我们提供两个信息:一是变量的地址,即操作系统为变量在内存中分配的若干内存单元的首地址;二是变量的值,也就是,变量在内存中所分配的那些内存单元中所存放的数据。

由于程序的多样需要,我们对变量也有各种各样的要求,比如:变量的生命期,变量的初始状态,变量的有效区域,变量的开辟地和变量的开辟区域的大小等等;为了满足这些要求,C语言的发明者就设置了以下各类变量:

1.1 不同数据类型的变量

如:

<code>char cHar;
int iTimes;
float faverage;/<code>

I 全局变量;

II 局部变量;

III 静态变量,静态全局变量和静态局部变量,关键词:static

IV 寄存器变量,关键词,register;

V 外部变量,关键词,extern;

C语言中,变量在内存中开辟地的大小由数据类型决定的,由于PC机中规定一个地址单元存一个字节, 不同的数据类型的变量,为其分配的地址单元数是不一样的。 C语言中除几种基本的数据类型外用户还可以通过typedef来自己定义所需要的数据类型。 比如:

<code>typedef int INT;
typedef unsigned int UINT;
typedef unsigned short USHORT;
typedef char CHAR;

typedef struct _Node
{
    int val;
    struct _Node *next;
}Node, *PNode, NodeArr[6];/<code>

其中的INT、UINT、USHORT、CHAR、Node、PNode就是定义的新的数据类型。为什么有了int、char等类型,还要重新定义一个INT和CHAR呢?实际上这样的好处是一旦平台发生了变化,char和int的定义发生了变化,自己的程序库因为使用的是自己的CHAR,INT等类型,可以改动很小,而MS等程序库也正是这么做的。

在函数内部说明的变量为局部变量,只有在函数执行时,局部变量才存在,当函数执行完退出后,局部变量随之消失。 也就是,当函数执行完退出后,原先在函数内定义的变量现在不能用。

与局部变量不同,全局变量在整个程序都是可见的,可在整个程序运行过程中,对于任何一个程序都是可用的。全局变量的说明的位置在所有函数之外,但可被任何一个函数使用,读取或者写入。但是在多个线程里访问全局变量的时候,需要注意多线程安全问题。

静态变量是分配在存储器中C程序所占据的数据段内, C程序运行的整个过程中一直保留,不会被别的变量占用。静态变量可以定义成全局静态变量或局部静态变量,当定义为全局静态变量时,在它定义的程序的整个运行期间均存在并且保持原来的存储单元位置不会改变,但是不能被同一项目内的其它文件通过extern关键字声明访问。

同静态全局变量定义一样,当在局部变量名前加静态变量说明符static,该变量就定义为静态局部变量。编译器为该变量建立永久存储单元。永久是指C程序运行的整个过程中一直保留,不会被别的变量占用。静态局部变量和静态全局变量的根本区别在作用区域上,静态局部变量只在它被说明的函数或复合语句中有效,并且在两次函数调用期间仍然保存其值不变,即就是有记忆功能;它们的生命期是相同的,和C程序整个运行期同在。

普通局部变量函数退出后,值就无效了。而静态局部变量具有记忆功能,能够记住上次执行后的值。

静态局部变量只初始化一次,并且默认初始化为0。

以下面的程序为例:

<code>int func1(int x)
{
    int z = 0; //z是一个普通的局部变量,每次函数调用,z都为0
    z += x;
    return z;
}
int func2(int x)
{
    static int z = 0; //z是一个静态局部变量,只进行第一次初始化,以后每次函数调用,都保留上次调用的值
    z += x;
    return z;
}
int main(void)
{
    for(int i = 0; i < 10; i++)
    {
        //此处10次循环,func1的输出分别为0,1,2,3,4,5,...,9;func2的输出分别为:0,1,3,6,10,...
        printf("func1(%d) = %d, func2(%d) = %d\n", i, func1(i), i, func2(i));
    }

    return 0;
}/<code>

寄存器变量不像其他变量那样在内存中存放数据,而是在CPU的寄存器中暂存数据,使用寄存器变量比使用内存变量的操作速度快得多。只有整型和字符型变量可定义为寄存器变量。定义方式如下:

register int i;

由于CPU中寄存器有限,尽量减少使用数量和和占用时间,用完马上释放;不能定义为全局变量,也不能定义在结构或者类中。如果寄存器已经无可用资源,那么寄存器变量将转化为普通局部变量。

在大型程序中,会将一个大型的程序分成若干个独立的模块和文件分别编译,然后统一链接在一起。为了解决全局变量和函数的共用问题,就引入了 extern关键字。这样只需在一个文件中定义全局变量和函数,在另一个文件中要用到这些变量和函数时,只需将那个文件中的变量和函数说明表复制过来,在前面加上extern,告诉编译器,这些变量和函数已经在别的文件中定义说明,通过extern声明的变量就称为外部变量

常量,就是不同类型中不变的值。比如’A’, 100, 3.1415926,"hello world"等字面量,对于字符串字面量,存入在静态区只读数据段,对于其它字面量,是程序代码的一部分。

另外,还可以通过宏来定义一些常量,比如:

#define PI 3.1415926;

上面就用一个常量符号PI来定义了圆周率的值。以后就可以在程序中使用这个PI来计算圆相关的数值。

也可以用const来声明:

const int up = 9999;

2 变量的存储空间

我们知道,无论是程序的代码还是数据,都是被加载和保存到内存中,从而被运行和访问的。以下图的X86系统为例。在X86系统中, 内存的有效范围为4GB,其中高2GB为内核空间,用来存放内核的代码和数据;低2GB为用户空间,运行进程的用户态代码,并且低2GB的内存是进程私有空间。一个进程不能跨进程访问别的进程空间的用户态地址。

C|一个小实例了解变量存储属性、作用域、生命周期


如下图所示,程序在内存中分为如下几部分(section):

C|一个小实例了解变量存储属性、作用域、生命周期

1).text 代码段,这段是存放代码的,从汇编角度来看就是指令。

2).rdata 只读数据段,不能修改,存放常量,字符常量,const常量。

3).data 数据段,存放已经初始化好的全局变量和静态变量。

4).bss(Block Started by Symbol) 存放未初始化的全局变量和静态变量。

.rdata、.data、.bss都是用来存放全局/静态数据的。除了.bss段,.rdata、.data段的值都是在编译的时候就确定了,并且将其编译进了可执行文件,经过反汇编都能找得到。bss段是在代码运行的时候将其初始化为0的(这就是未初始化的全局和静态变量默认值为0的根源)

5).stack,如下图所示,栈中存放普通的局部变量、形参返回地址等,大小有限制,Windows应用层栈大小默认为1MB,内核栈系统根据CPU架构而定,x86系统上为12KB,x64系统上为24KB,安腾系统上为32KB ;Linux应用层程序默认栈大小为10MB,内核栈大小4KB或8KB。

C|一个小实例了解变量存储属性、作用域、生命周期

6)堆:存放任意数据。

所以,综上所述:

1)全局初始化变量存放在静态存储区中的.data区;

2)全局未初始化变量存放在静态存储区中的.bss区;

3)局部变量存放在栈;

4)全局和局部静态变量存放在静态存储区中的.data区和.bss区;

5)常量存放在静态区中的.rdata区,.rdata区域是只读内存,不能修改;

6)寄存器变量存放在寄存器中。

3 变量的作用域

变量的作用域是指变量能在代码什么地方被访问到。比如下面的代码:

<code>int g_mycount = 10;
int func1(void)
{
    int x = 10;
    printf("%d,%d\n", x*x,g_mycount);
}
int func2(void)
{
    int y = 10;
    printf("%d,%d\n", y+y,g_mycount);
}/<code>

变量g_mycount是全局变量,变量x和y是函数func1和func2的局部变量,因此func1不能访问y,func2也不能访问x,但它们都能访问g_mycount。这就是所谓的变量的作用域。

1)全局变量:所有程序中的代码都能访问。当然,如果全局变量在工程中一个源文件中定义,而要在另一个源文件中访问,需要在另一个源文件里使用extern关键字来导入。但是在多个线程里访问全局变量的时候,需要注意多线程安全问题。

2)静态全局变量:在同一个源文件中可以访问,在不同的源文件中,不能访问,也不能使用extern来导入。静态全局变量主要是为了防止全局变量带来的命名冲突。

3)局部变量:只能函数内部访问。

4)静态局部变量:只能函数内部访问。

5)寄存器变量:和局部变量一致。

4 变量的生命周期

变量的生命周期是指变量在程序运行期间的有效时间。

1)全局变量:整个程序运行期间有效。

2)静态全局变量:整个程序运行期间有效。

3)局部变量:函数范围内或者代码块范围内({}括来的部分)有效,函数退出无效。

4)静态局部变量:整个程序运行期间有效。

5)寄存器变量:和局部变量一致。

下面是一个综合各类变量存储类型、作用域、生命周期的实例:

<code>#include  // malloc()

int count_g = 0;         // 全局初始化变量
char *pFile_g;           // 全局未初始化变量
static int num_gs = 10;  // 全局初始化静态变量
void func(void)
{
    int k = 0;             // 局部变量
    char arrc[]  = "123";  // 局部变量,字符数组,用"123"字符串常量进行初始化
    char *str    = "hello";// 局部变量,字符指针,指向"hello"字符串常量
    pFile_g       = (char *)malloc(128); // 全局变量指向堆内存
    char *content = (char *)malloc(256); // 局部指针变量指向堆内存
	static int is  = 0; // 静态局部变量,已初始化
	register int r = 0; // 寄存器变量
    free(pFile_g);      // 显示释放p1指向的堆内存
    free(content);      // 显示释放p2指向的堆内存
}
int main(void)
{
    func();
	return 0;
}/<code>

各类变量存储位置、作用域、生命周期:

C|一个小实例了解变量存储属性、作用域、生命周期

-End-


分享到:


相關文章: