C语言_存储类别、连接和内存管理

文章目录

    • 0. 前言
    • 1. 存储类别
      • 1.1 作用域
        • 1.1.1 块作用域
        • 1.1.2 函数作用域(function scope)
        • 1.1.3 函数原型作用域(function protoype scope)
        • 1.1.4 文件作用域(file scope)
      • 1.2 链接
      • 1.3 存储期(变量生存周期)
        • 1.3.1 静态存储期:
        • 1.3.2 线程存储期:
        • 1.3.3 自动存储期
        • 1.3.4 动态分配存储区
      • 1.4 自动变量
      • 1.5 寄存器变量
      • 1.6 外部链接的静态变量
      • 1.7 内部链接的静态变量
      • 1.8 多文件
      • 1.9 存储类别说明符
      • 1.10 存储类别总结
      • 1.11 存储类别的程序演示
        • 1.11.1 多文件设置
        • 1.11.2代码实例

0. 前言

程序员可以通过C的内存管理系统指定变量的作用域和声明周期,实现对程序的控制。

合理使用内存存储数据是程序设计的一个要点

1. 存储类别

关键概念:

  1. 对象(object):从硬件方面看,被储存的每个值都占用一定的物理内存,C语言把这样的一块内存称为对象
  2. 可以用 存储期(storage duration) 描述对象。所谓存储期是指对象在内存中保留了多长时间
  3. 标识符用来访问对象,可以用作用域(scope)和连接(linkage)描述标识符,标识符的作用域和连接表明了程序的哪些部分可以使用它

对象可以存储一个或多个值。
一个对象可能并未储存实际的值,但是它在储存适当的值时一定具有相应的大小

说明:

  1. 面向对象编程中的对象指的是类对象,其定义包括数据和允许数据进行的操作

1.1 作用域

作用域描述程序中可访问标识符的区域。一个C变量的作用域可以是块作用域、函数作用域、函数原型作用域或文件作用域

1.1.1 块作用域

块是用一对花括号括起来的代码区域

定义在块中的变量具有块作用域(block scope)

块作用域的可见范围是从定义处到包含该定义的块的末尾。

虽然函数的形参,声明在函数的左花括号之前,但是它们也具有块作用域,属于函数体这个块
声明在内层块中的变量,其作用域仅局限于该声明所在的块

1.1.2 函数作用域(function scope)

C语言_存储类别、连接和内存管理_第1张图片

1.1.3 函数原型作用域(function protoype scope)

函数原型作用域用于函数原型中的形参名(变量名)

函数原型作用域的范围是从形参定义处到原型声明结束

意味着,编译器在处理函数原型中的形参时只关心其类型,而形参名无关紧要。即使有形参名,也不必于函数定义中的形参名相匹配

注意:

  1. 在变长数组中,函数声明中的形参名有用
void use_a_VLA(int n,int m, arr[n][m]);

上述代码方括号中必须使用在函数原型中已声明的名称

1.1.4 文件作用域(file scope)

定义在函数外名的变量具有文件作用域

通常源代码(.c文件)中包好一个或多个头文件(.h扩展名)。头文件会一次包含其它文件,所以包含多个单独的物理文件。但是C预处理实际上用包含的头文件替换#include指令。所以编译器源代码文件和所有的头文件都看成是一个包含信息的单独文件。这个文件被称为翻译单元(translation unit)

描述一个具有文件作用域的变量时,它的实际可见范围是整个翻译单元。

1.2 链接

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

具有块作用域、函数作用域或函数原型作用域的变量都是无链接变量

具有文件作用域的变量可以是外部链接或内部链接。

外部链接变量可以在多个文件程序中使用,内部链接变量只能在一个翻译单元中使用

内部链接的文件作用域 简称文件作用域

外部链接的文件作用域 简称全局作用域程序作用域

区分文件作用域是内部链接还是外部链接的方式:----static

int giants=5;     //文件作用域,外部链接
static int dodgers=3;       //文件作用域,内部链接

程序说明:

giants 变量可以在多个翻译单元中使用,而dodgers变量只能在本翻译单元中使用

1.3 存储期(变量生存周期)

作用域和链接描述了标识符的可见性。

存储期描述了通过通过这些标识符的对象的生存期

C语言有四种存储期:
静态存储期、线程存储期、自动存储期、动态分配存储期

1.3.1 静态存储期:

如果对象具有静态存储期,那么他在程序的执行期间一直存在。

文件作用域变量具有静态存储期

1.3.2 线程存储期:

C语言_存储类别、连接和内存管理_第2张图片

1.3.3 自动存储期

块作用域通常具有自动存储期。当程序进入定义这些变量的块时,为这些变量分配内存;当退出这些块时,释放刚才为变量分配的内存。

对于定义在块作用域的变长数组,他们的存储期从声明处到块的末尾,而不是块
的开始到块的末尾

块作用域也可以具有静态存储期,其实现方式如下(static)

void more( int number)
{
     
	int index;
	static int ct=0;   //声明一个具有静态存储期的变量
}

1.在块内部定义的静态变量,只有程序其所在函数时才能访问,但是函数可以通过提供该存储区地址的方式使其它函数间接访问该对象
2. 不能给函数形参声明静态变量
3. 静态变量(static variable)中静态的意思是该变量在内存中原地不动

附表:5种常见的存储类别
C语言_存储类别、连接和内存管理_第3张图片

1.3.4 动态分配存储区

动态分配存储期的内容在后文论述

1.4 自动变量

具有自动存储类别的变量具有自动存储其、块作用域且无连接

关键字auto用来显示的声明某个变量为自动变量,声明方式如下:

int main()
{
     
	auto int plox;
}

auto关键字属于存储类别说明符(storage-class specifier)
auto在C++中的作用是申请内存,因此为了程序兼容性,组好减少使用auto定义自动变量

说明:

  1. 块中声明的变量仅限于该块及其包含的块使用
  2. 如果内层块声明的变量与外层块中的变量同名,内层块会隐藏外层块的定义。当程序离开内层块之后,外层块变量可以再次使用,而不发生变化
  3. 对于for循环等循环体里面定义的变量也相当于自动变量
  4. 自动变量不会初始化,除非显示的初始化它。

显示的声明也给自动变量的方式:

int main()
{
     
	int tents=5;    //显示声明方式1

	int ruth=3;
	int rance=5*ruth; //显示声明方式2
}

1.5 寄存器变量

通过程序将变量存储在寄存器中,使用关键字register,实现方式如下:

int macho(register int n)
{
     
	register int quick;
}

上述程序声明了两个寄存器变量,一个是形参n,另一个是int型变量quick

说明:

  1. 由于变量可能会存在内存中,所以寄存器变量的运算速度要比存储在内存中的变量计算速度更快
  2. register关键字更像是一种请求,即实际程序执行过程中会根据需求确定是否最终分配在寄存器中
  3. 不论寄存器变量是否最终存储在寄存器中,都无法使用取地址运算符,对该变量进行操作
  4. 由于寄存器存储空间有限,所以double类型的值无法声明

1.6 外部链接的静态变量

外部链接的静态变量具有文件作用域、外部链接和静态存储期。

属于该类别的变量称为外部变量

关键字extern的作用是指出某函数使用了外部变量

说明:

  1. 外部变量可以被显示的初始化,如果未初始化则默认为0
  2. 与自动变量不同,外部变量只能使用常量表达式初始化文件作用域变量
  3. 如果定义了外部变量,而在函数中使用需要添加extern关键字
int all=5int main()
{
     
	extern int all;//表示对全局变量all的使用
	
	int all=4//表示定义一个自动变量all,全局变量all将被屏蔽
}

第一次声明被称为定义式声明(defining declaraton)
第二次声明被称为引用式声明(referencing declaration)
关键字extern表明该声明不是定义,因为它指示编译器去别处查询其定义

  1. C99和C11标准都要求编译器识别局部标识符的前63个字符和外部标识符的前31个字符
  2. 外部变量只能初始化一次,且必须在定义该变量时进行

1.7 内部链接的静态变量

该存储类别的变量具有静态存储期、文件作用域和内部链接。

其声明方式如下:

static int svil=1; //静态变量,内部链接
int main()
{
     
	
}

该变量与外部链接的静态变量在作用范围上只能作用在同一个翻译单元,
而外部链接的静态变量可以在多个翻译单元使用

1.8 多文件

只有当程序由多个翻译单元组成时,才体现区别内部链接和外部连接的重要性

C通过在一个文件中进行定义式声明,然后在其它文件中进行引用式声明来实现共享

如果外部变量定义在一个文件中,其它文件的函数要使用必须用external关键字进行声明

1.9 存储类别说明符

auto:
C语言_存储类别、连接和内存管理_第4张图片
register:
C语言_存储类别、连接和内存管理_第5张图片
static:
C语言_存储类别、连接和内存管理_第6张图片

extern

C语言_存储类别、连接和内存管理_第7张图片

1.10 存储类别总结

C语言_存储类别、连接和内存管理_第8张图片

1.11 存储类别的程序演示

1.11.1 多文件设置

由于涉及到多文件操作,在VS中进行多文件的设计步骤是:

  1. 头文件选项下添加头文件,
  2. 源文件选项下添加与头文件名相同的.c文件
  3. 源文件下添加主函数文件,并在头部添加#include对头文件进行引用

如下图:
C语言_存储类别、连接和内存管理_第9张图片

设计好后,要对头文件进行引用
C语言_存储类别、连接和内存管理_第10张图片

1.11.2代码实例

题目:计算一个数的累加结果,要求每次输入都显示第几次执行,输出当前数的累加结果和所有数的累加结果。并用本博文所用知识实现

程序设计

  1. 设置全局变量统计执行次数
  2. 设置全局变量实现历史结果求和
  3. 设置局部静态变量进行单次结果累加

程序代码

bt1.h 文件

#pragma once
#include

void accumulate(int k);

bt1.c文件

#include

extern int count;  //引用式声明,外部链接
static int total = 0; //静态定义,内部链接

void accumulate(int k)
{
     
 	static int subtotal = 0; //静态 无连接

 	if (k <= 0)
 	{
     
  		printf("loop cycle: %d\n", count);
  		printf("subtotal: %d total: %d\n", subtotal, total);
  		subtotal = 0;
 	}
 	else
 	{
     
  		subtotal += k;
  		total += k;
 	}
}

main.c函数文件

#include
#include"bt1.h"

void report_count();
void accumulate(int k);

int count = 0;    // file scope external link

int main()
{
     
 	int value;  //auto variable
 	register int i;// register variable

 	printf("enter a positive integer(0 to quit):");
 	while (scanf("%d", &value) == 1 && value > 0)
 	{
     
  		++count;  //use file cope virable
  		for (i = value; i >= 0; i--)
  		{
     
   			accumulate(i);
  		}
  		printf("enter a positive integer(0 to quit):");
 	}
 	report_count();
 	getchar();
 	getchar();
 	return 0;
}
void report_count()
{
     
 	printf("Loop executed %d times\n", count);
}

输出结果
C语言_存储类别、连接和内存管理_第11张图片

你可能感兴趣的:(C/C++)