精通C语言深度解析C变量作用域链接和存储期的含义

Posted 从善若水

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了精通C语言深度解析C变量作用域链接和存储期的含义相关的知识,希望对你有一定的参考价值。

本人就职于国际知名终端厂商,负责modem芯片研发。
在5G早期负责终端数据业务层、核心网相关的开发工作,目前牵头6G算力网络技术标准研究。


C变量作用域、链接和存储期的含义

错误的定义C语言变量的作用域、链接和存储区在某种程度上会直接影响程序的设计,我们通过这篇博文介绍如何正确合理的定义一个C变量。

从定义一个C变量开始成为一个C高手


一、基本术语

  1.1 什么是对象(object)

       C语言中的对象含义与C++的完全不同,C语言中的对象指的是存储数据的一块内存。对象可以存储一个或多个值,一个对象可能并未存储实际的值,但是在存储适当的值时一定具有相应的大小。对象可以存在于程序的执行期,也可以仅存在于它所在函数的执行期。

  1.2 什么是标识符(identifier)

       标识符就是一个名称,通过这个标识符可以修改对象的内容。标识符可以在源代码的多文件中共享、可用于特定文件的任意函数中、可仅限于特定函数中使用,甚至只在函数中的某部分使用。

举个例子,我们定义一个int型变量point:

int point;

对象与标识符的关系如下图

二、作用域、链接和存储期三者关系

       存储期用来描述对象,所谓的存储期就是指对象在内存中可以保留多长时间

       标识符用于访问对象,使用作用域(scope)和链接(linkage)描述标识符,标识符的作用域和链接表明了程序的哪些部分可以使用它


三、作用域详解

作用域描述程序中可访问标识符的区域
一个C变量的作用域可以是块作用域函数作用域函数原型作用域文件作用域,下面我们详细讲解每个作用域的含义。

  3.1 块作用域

       块是用一对花括号括起来的代码区域,定义在块中的变量具有块作用域块作用域变量的可见范围是从定义处到包含该定义的块的末尾

       另外,虽然函数的形式参数声明在函数的左花括号之前,但它们也具有块作用域,属于函数体这个块。

int Star_CongShanRuoShui(int user_id)
{
	int res = 0;
	......
	return 
}

👆上面code中“user_id”和“res”都具有块作用域

int Star_CongShanRuoShui(int user_id[] , int n)
{
	int res = 0;
	for(int i=0;i<n;++i)
	{
		......
		int q=0;   //q的作用域开始
		
		......
	}              //q的作用域结束
	......
	return 
}

👆上面code中变量“q”的作用域仅限于for循环的循环体中

    3.1.1 块概念的扩展

       C99将块概念扩展到包括for循环、while循环、do while循环和if语句所控制的代码,即使这些代码没有使用花括号括起来,也算是块的一部分,下面的code👇

#include<stdio.h>

int main()
{
	int Star_CSRS = 8;
	printf("[main] value of Star_CSRS is %d | address of Star_CSRS:%p\\n",
			Star_CSRS ,&Star_CSRS);
	
	for(int Star_CSRS=0;Star_CSRS<4;++Star_CSRS)
	{
		printf("[for index] value of Star_CSRS is %d | address of Star_CSRS:%p\\n",
			Star_CSRS ,&Star_CSRS);
			
		int Star_CSRS = 6;
		printf("[for] value of Star_CSRS is %d | address of Star_CSRS:%p\\n",
			Star_CSRS ,&Star_CSRS);
		
		++Star_CSRS;
	}
	
	printf("[main] value of Star_CSRS is %d | address of Star_CSRS:%p\\n",
			Star_CSRS ,&Star_CSRS);

	return 0;
} 

输出如下:

  • 在main中定义了变量Star_CSRS,在for循环头中定义的变量Star_CSRS 隐藏了main中的Star_CSRS
  • for的循环体中的Star_CSRS又隐藏了for循环头中的Star_CSRS

  3.2 函数作用域

       函数作用域的概念仅限于goto语句的标签。这意味着即使一个标签首次出现在函数的内层块中,它的作用域也延伸至整个函数中(详细可以参考我的这篇博文)。


  3.3 函数原型作用域

       用于函数原型的形参变量名,如下所示:

int Star_CongShanRuoShui(int user_id);

       函数原型作用域的范围是从形参定义处到原型声明结束。对于函数原型中的形参编译器只关心形参类型,不关系具体的形参名即使有形参名也不必与函数定义中的形参名相匹配

       只有在变长数组中,形参名才有意义,如下例(变长数组详细参考我的这篇博文):

int Star_CongShanRuoShui(int n ,int user_id[n]);

  3.4 文件作用域

       定义在所有函数外的变量具有文件作用域。具有文件作用域的变量,从它的定义处到该定义所在文件的末尾均可见(文件作用域变量也称为全局变量)。

翻译单元与文件

  • 编译器将一个源代码文件和所有的头文件都看成一个包含信息的单独文件,这个文件被称为翻译单元
  • 一个翻译单元包括一个源代码文件和它所include的文件
  • 如果一个程序有多个源代码文件,那么这个程序也将有多个翻译单元
  • 一个文件作用域变量的可见范围其实是整个翻译单元(一个源代码文件+头文件)

四、链接详解

C变量有3中链接属性:无链接、内部链接、外部链接

  4.1 无链接

       具有块作用域、函数作用域和函数原型作用域的变量都是无链接变量。这意味着这些变量属于定义它们的块、函数或原型私有

  4.2 内部链接

       内部链接变量只能在一个翻译单元中使用,该变量使用存储类别说明符static修饰,如下:

int Star_CSDN=1;                     //文件作用域,外部链接
static int Star_CongShanRuoShui=2;   //文件作用域,内部链接

int main()
{
	......
	return 0
}

  4.3 外部链接

       外部链接变量可以在多个文件中使用。外部链接变量的声明分为“定义性声明”和“引用性声明”。C编译器要求一个变量只能定义一次,重复定义编译器会报错。如果需要在其它文件中使用外部链接变量需要使用extern引用性声明这个变量,如下面cdoe:

//file a.c

//在文件a.c中定义一个外部链接变量Star_CongShanRuoShui
int Star_CongShanRuoShui = 2;

......
//file b.c

//文件b.c中使用extern引用性声明变量Star_CongShanRuoShui
extern int Star_CongShanRuoShui;

......

五、存储期详解

C对象有4种存储期:静态存储期、线程存储期、自动存储期、动态分配存储期。

  5.1 静态存储期

       具有静态存储期的对象,它在程序的执行期间一直存在文件作用域变量具有静态存储期

注意,对于文件作用域变量,关键字static表明了其链接属性,而非存储期。以static声明的文件作用域变量具有内部链接属性,但是无论是内部链接还是外部链接,所有文件作用域变量都具有静态存储期

  5.2 线程存储期

       线程存储期用于并发程序设计,程序执行可被分为多个线程。具有线程存储期的对象,从被声明时到线程结束一直存在。以关键字__Thread_local声明一个对象时,每个线程都获得这个变量的私有备份

  5.3 自动存储期

       块作用域的变量通常都具有自动存储期。当程序进入定义这些变量的块时,为这些变量分配内存;当退出这个块时,释放刚才为这些变量分配的内存。通过这种做法可以实现内存的重复利用。

变长数组稍有例外,它的存储期从声明处到块的末尾,而不是从块的开始处到块的末尾

       我们上面说块作用域的变量通常都具有自动存储期,但是也能具有静态存储期。为了创建这样的变量,要把变量声明在块中,且在声明前加上关键字static,如下例:

int Star_CongShanRuoShui(int user_id)
{
	static int isStar = 0;
	......
	return 
}

       变量isStar存储在静态内存中,它从程序被载入到程序结束期间都存在。但是,它的作用域定义在Star_CongShanRuoShui()函数块中,只有在执行该函数时,程序才能使用isStar访问它所指定的对象(当然,也可以存储该变量的地址实现间接访问该对象)


  5.4 动态分配存储期

       程序运行时通过malloc()等内存分配函数分配的对象具有动态分配存储期,这样的对象需要使用free()函数进行销毁。

动态内存分配和变长数组在功能上有些重合,但是还是有所不同:

  • 变长数组是自动存储类型
  • 用malloc函数创建的数组不必局限在一个函数中使用

以上是关于精通C语言深度解析C变量作用域链接和存储期的含义的主要内容,如果未能解决你的问题,请参考以下文章

存储类,作用域,生命周期和链接属性

精通C语言一图搞清C语言到底有多少种变量存储类别

精通C语言一图搞清C语言到底有多少种变量存储类别

4.7 C语言的存储类,作用域,生命周期,链接属性

C语言笔记系列--存储类

C语言笔记系列--存储类