C 语言 - 存储类说明符

news2024/12/25 1:05:55

【预备知识】
1)C 语言 - 存储时期
2)C 语言 - 链接属性
3)C 语言 - 作用域

1. 分类:

· C 语言为变量提供了 5 中不同的存储模型(即存储类)。
C语言中的5种存储模型
(此外还有基于指针的第 6 种存储模型,不在本篇笔记范围内,后续补充)。
· C 语言提供了以下 5 个存储类说明符(storage-class-specifier):
auto
register
static
extern
typedef
 由于 typedef 和其他四个的语义不同,所以不在这里进行讨论。
存储类说明符

2. 自动变量 auto

自动变量具有代码块作用域、自动存储时期和空链接。
1) 默认情况下,在代码块函数头部定义的任意变量都是自动变量。
2)可以使用关键字 auto 对块作用域变量进行显式声明。

// ivar, ilen 都是自动变量
void func (int ivar) {
	auto int ilen = 5;
	// to do
	return 0;
}

· 注意不带 ‘{}’ 的代码块,比如 if 或者 while 的子句。
· 注意 auto 只能用于块作用域变量的声明。虽然函数头部定义的变量也是自动变量,但不可以使用 auto 来显式声明。
2)auto 关键字的作用:
· 显式声明该变量为自动存储模型;
· 用来有意覆盖一个外部函数定义;
3)对于自动变量,只能在变量定义所在的代码块(包含其嵌套的代码块)通过名字访问该变量。
【注意】
a. 可以用参数向其他函数传送自动变量的值和地址,但那是间接的方式。
b. 另一个代码块(包括其嵌套的代码块)可以存在同名变量,但那是存储在不同内存位置的另一个独立变量。
4)对于嵌套代码块中的同名变量,外层代码块的变量会被内层代码块中的同名变量覆盖(程序离开内层代码块时就会恢复外层代码块中的同名变量)。
5)自动变量的优点:在程序离开当前代码块时,自动变量消失了,其占用的内存可以用来做别的事情。
6)自动变量需要显式初始化!切记!
  一些编辑器会默认初始化变量,但最好自己来初始化!给变量赋初值是一个好习惯。
7)auto 关键字的实际用途?
在我查阅的大部分资料里,可以说,auto 基本是没有什么用的,因为 auto 声明了一个变量是自动变量,且 auto 只能用在代码块作用域内,但是别忘了,代码块作用域内定义的变量默认就是自动变量……这样一来,auto 的显式声明就显得十分鸡肋。
那为什么 C 语言还保留了 auto 关键字呢?(C++11 开始弃用了 auto 作为自动存储期的语义)主要是为了向后兼容,C 语言之前的代码使用 auto 来声明变量,在过去有很多代码需要移植到新的 C 语言中,在这种情况下,将关键字 auto 添加到语言中是很有帮助的。

3. 寄存器变量 register

1)通常,寄存器变量存储在速度最快的可用内存中(通常是 CPU 寄存器),目的是为了比普通变量更快地被访问和操作。
2)使用 register 声明符声明寄存器变量,该声明符只能用于块作用域的对象和函数原型作用域的对象。
3)因为寄存器变量多是存放在一个寄存器中,因此无法对一个寄存器变量进行一元取值运算(&),也不能将声明为 register 的数组转换为指针。
4)register 仅仅是一个请求而不是指令,如果没有可用的寄存器或者高速内存,那么编译器会把它当自动变量处理,即便如此依然存在 3)中的限制。
注意,虽然使用 register 修饰变量理论上可以提高效率,但不应该在程序中大量使用 register,毕竟寄存器的数量是有限的,而且将变量存储在寄存器中也会消耗一定的处理时间,反而会降低程序的运行效率。
5)使用 register 声明的类型是有限的(例如,处理器可能没有足够大的寄存器来容纳 double)。
6)register 关键字在实际开发中的用途?
很遗憾,在实际的开发中很少会用到 register 这个关键字,但这并不是因为它和 auto 一样鸡肋。早期的编译器不太善于决定是否要将机器寄存器应用于一个变量,因此 C 语言提供了 register 以便开发人员手动执行这个操作。随着技术的改进编译器变得更聪明了,在决定变量存储方面往往比开发人员做得更好(很多时候开发人员的决定只会让程序变得更慢),这就是为什么很多编译器在涉及存储时总是忽略 register
不过,使用 register 声明的变量无法进行取址操作,可以使用 register 来确保变量不会被地址访问导致变量在期望的范围之外被更改。(奇怪的思路,不建议)
7)关于寄存器的补充。
a. 寄存器存在的原因:CPU 是负责进行计算的硬件单元,为了方便运算,需要先把数据从内存读取到 CPU 内,这就要求 CPU 具有一定的数据临时存储能力,但 CPU 并不是在需要开始计算时才把特定数据读进 CPU 里面,那太慢了。 因此现代 CPU 内都集成了一组叫做寄存器的硬件,用于保存临时数据。
b. 寄存器存在的本质: 在硬件层面上提高计算机的运算效率(因为不需要反复从内存里读取数据)。

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

1)静态指的是变量的地址是固定的,变量的值是可以修改的。
2)使用 static 声明代码块作用域变量具有代码块作用域的静态
【注】对于常量字符串,使用 static 是有用的,因为它减轻了频繁初始化经常调用的函数的开销。
3)具有代码块作用域的静态变量具有全局生存期(在程序载入时创建,在程序结束时销毁),但只在定义的代码块内可见。
4)作用:保存程序运行时的中间结果。

void checkNum(int ivar) {
	int iflag = 0;
	static int icnt = 0;
	
	if (ivar == iflag) {
		printf("Found %d %d time(s).\n", iflag, ++icnt);
	}

上面例子中,“int iflag = 0;” 是 checkNum() 的一部分,该语句在每次调用函数时都会执行。
但语句 “static int icnt = 0;” 实际上并不是函数 checkNum() 的一部分,该语句放在 checkNum() 函数中只是为了告诉编译器,icnt 的可见范围仅限于函数 checkNum() 的函数体内。并且它不是在运行时执行的语句(可以在调试的时候进行测试,程序会跳过这一语句)。
程序离开了 checkNum() 后并不会销毁 icnt 变量,只是这个时候变量 icnt 在外层不可见,当重新回到该函数时,可以看到该变量保持了上次离开时的状态(值和地址)。
5)不应该在结构体中声明静态变量。
C 语言要求结构体中的所有成员都放置在内存中的同一地方,即结构成员的内存分配应该是连续的,因为 struct 成员的值是通过计算元素从所在结构体的起始地址的偏移量来获取的。如果确实有需要,可以使整个结构保持静态。
(【注意】可以在函数内部(堆栈段 stack segment)声明一个 struct,或动态分配内存(堆段 heap segment),甚至可以是全局的(BSS或数据段 data segment),只要能保证内存连续。)

5. 具有外部链接的静态变量

具有外部链接的静态变量,也称为外部存储类(external storage class),外部变量(external variable)。
1)外部存储类具有文件作用域、外部链接和静态存储时期。
2)外部存储类声明在所有函数之外。
3)可以在使用外部变量的函数中通过使用 extern 关键字再次声明它,这个声明是可选的。

// assist.c
int ierrupt;	// 定义在所有函数之外的变量;
double darr[100];	// 外部定义的数组;
char cflag = 'Q';

int func() {
	extern int ierrupt;	// 可选的声明
	extern double darr[];	// 可选的声明
	char cflag = 'q';	// 这里的 cflag 覆盖了外部的 cflag
	// to do 
	return 0;
}

注意,这里 “可选的声明” 的意思是:整个声明语句是可选的。而不是声明语句中的 extern 关键字是可选的。如果在函数中声明了和外部变量同名的变量,且漏掉了 extern,那么将创建一个独立的自动变量!(比如上面例子中的 cflag
注意上面的数组 darr,不必在可选的声明中指明数组大小,因为第一次声明的时候已经提供了这一信息。
上面例子中的两句可选声明完全可以省略并且不会影响程序的逻辑。
一个建议是,在程序中(尤其是复杂的程序中)写上这句可选的声明,以提高程序的可读性。
另外,如果变量定义在别的文件中,那么必须使用 extern 来声明该变量,这个声明是必要的。

// main.c
extern char cflag;	// 必要的声明,
					// 因为 cflag 定义在 assist.c 中
int main() {
	// to do
	return 0;
}

4)不同于自动变量,外部变量在没有显示初始化的情况下,会自动赋初值 0。
a. 还是那句话,给变量赋初值是一个好习惯。
b. 一个外部变量只可以进行一次初始化,而且一定是在变量被定义的时候被初始化。(区别于赋值)

// 这个语句是错误的。
// 因为 extern 说明这是一个引用声明,
// 而非定义声明!
extern char c_permis = 'Y';	// error!!

5)只能用常量表达式来初始化文件作用域变量。
常量表达式是指在编译时可以完全确定其值的表达式
6)外部变量可以被程序的任一文件中所包含的函数使用。
外部变量不需要使用参数和指针就能完成值的传递,使用上确实方便,但这是有代价的,需要谨慎对待。因为它太不明显,可能会在不知不觉中被偷偷修改,而又很难排查。
一个建议:不要随意使用外部变量。

6. 具有内部链接的静态变量

1)具有内部链接的静态变量具有静态存储时期、文件作用域和内部链接。
2)使用 static 关键字在所有函数外部对变量进行定义。
a. 区别于上面讨论的具有外部链接的静态变量,具有内部链接的静态变量在定义的时候多了一个 static
b . extern 关键字是在引用声明时使用而不是定义时使用!!
c. 可以在函数中使用 extern 来再次声明任何具有文件作用域的变量,但这并不改变链接!链接是在定义的时候就决定的!
3)只可以被与它在同一个文件中的函数使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/817386.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【外卖系统】文件上传与下载

文件上传 文件上传又称upload,将本地图片、视频等文件上传到服务器上,供其他用户下载或者浏览。 form表单:HTML中的form元素用于创建一个包含表单字段的区域,用户可以在该区域输入数据,并通过提交表单将数据发送到服务…

MySQL初探

Background 通过阅读小林coding,大致了解了mysql数据库的种种特点,与之前学的数据库实现大体思路相同,感觉学习不能停留在理论层面,要调研生产级别的中间件实现。 一条代码运行在mysql上的流程 1. 连接的过程需要先经过 TCP 三次…

[回馈]ASP.NET Core MVC开发实战之商城系统(四)

经过一段时间的准备,新的一期【ASP.NET Core MVC开发实战之商城系统】已经开始,在之前的文章中,讲解了商城系统的整体功能设计,页面布局设计,环境搭建,系统配置,及首页【商品类型,ba…

Structure Guided Lane Detection 论文精度

结构导向车道检测 摘要 近年来,随着深度神经网络和自动驾驶的快速发展,车道检测取得了长足的进步。然而,主要存在三个问题,包括车道的特征化、场景与车道之间的结构关系建模以及对车道的更多贡献(如实例和类型&#…

手搓 自然语言模型 各种对比数据

基础模型和设计思想 最优网络结构 import paddle import numpy as np from tqdm import tqdm class EmMask(paddle.nn.Layer):def __init__(self, voc_size19, hidden_size256, max_len48):super(EmMask, self).__init__()# 定义输入序列和标签序列self.embedding_layer pad…

Unity3d C#快速打开萤石云监控视频流(ezopen)支持WebGL平台,替代UMP播放视频流的方案(含源码)

前言 Universal Media Player算是视频流播放功能常用的插件了,用到现在已经不知道躺了多少坑了,这个插件虽然是白嫖的,不过被甲方和领导吐槽的就是播放视频流的速度特别慢,可能需要几十秒来打开监控画面,等待的时间较…

我的第一个前端(VS code ,Node , lite-server简易服务器,npm 运行)

第一种方式:使用Visual Studio Code创建并运行 第一个前端项目的步骤,如下: 1. 下载和安装Visual Studio Code: 访问Visual Studio Code官方网站(Visual Studio Code - Code Editing. Redefined)并根据你…

二十三种设计模式第二十篇--备忘录模式

备忘录模式,备忘录模式属于行为型模式。它允许在不破坏封装的情况下捕获和恢复对象的内部状态。保存一个对象的某个状态,以便在适当的时候恢复对象,该模式通过创建一个备忘录对象来保存原始对象的状态,并将其存储在一个负责管理备…

133. 克隆图

给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。 图中的每个节点都包含它的值 val(int) 和其邻居的列表(list[Node])。 class Node { public int val; public List&…

互联网医院系统开发:打造便捷高效的医疗服务平台

随着互联网技术的飞速发展,互联网医院系统的出现为医疗行业带来了许多新的机遇和优势。互联网医院系统是一种基于互联网技术的医疗服务平台,旨在提供便捷、高效、个性化的医疗服务。下面将介绍互联网医院系统开发的优势。   提供便捷的医疗服务&#x…

【模仿学习】:离线和在线模仿

一、说明 模仿学习(Imitation Learning )是机器学习的一种,代理通过观察和模仿专家的行为来学习。在这种方法中,为代理提供了一组所需行为的演示或示例,并通过尝试复制专家的行为来学习输入观察和输出操作之间的映射。…

【单机多卡】torch改造代码为DDP单机多卡分布式并行

torch分布式数据并行DDPtorch.nn.parallel.DistributedDataParallel代码修改记录。(要求pytorch_version>1.0) 目录 1.🍄🍄要修改的地方概览 2.✏️✏️初始化 3.✏️✏️设置当前进程GPU 4.✏️✏️设置sampler 5.✏️✏…

HTML笔记(1)

介绍 浏览器中内置了HTML的解析引擎,通过解析标记语言来展现网页;HTML标签都是预定义好的;Java工程师:后台代码的编写,和数据库打交道,把数据给网页前端的工程师;网页前端工程师:写H…

拯救者Y9000K无线Wi-Fi有时不稳定?该如何解决?

由于不同品牌路由器的性能差异,无法完美兼容最新的无线网卡技术,在连接网络时(特别是网络负载较大的情况下),可能会出现Wi-Fi信号断开、无法网络无法访问、延迟突然变大的情况;可尝试下面方法进行调整。 1…

go 如何知道一个对象是分配在栈上还是堆上?

如何判断变量是分配在栈(stack)上还是堆(heap)上? Go和C不同,Go局部变量会进行逃逸分析。如果变量离开作用域后没有被引用,则优先分配到栈上,否则分配到堆上。判断语句:…

Stable Doodle:Stability AI推出的一款零门槛AI绘画神器

Stable Doodle是由Stability AI推出的一款零门槛AI绘画神器,可以将简单的草图转化为精美的图像。它可以将随手的塗鴉草稿转化为高畫質的完成圖,让用户能够以更快的速度将想法转化为精美的艺术作品。Stable Doodle利用最新的Stable Diffusion模型&#xf…

智能车域控制器设计

摘要: 本文主要针对ADCU从硬件设计到软件设计的开发流程进行详细阐述,主要包含了需求场景、关键硬件电路、电路可靠性、AUTOSAR架构、CAN通信简介、CAN通信软件设计等。最后基于以上硬件技术和软件技术开发出一款产品级智能驾驶域控制器。 // 智能驾驶域控制器研究现状 //…

iOS开发-实现自定义Tabbar及tabbar按钮动画效果

iOS开发-实现自定义Tabbar及tabbar按钮动画效果 之前整理了一个继承UITabbarController的Tabbar效果 查看 https://blog.csdn.net/gloryFlow/article/details/132012628 这里是继承与UIViewController的INSysTabbarViewController实现及点击tabbar按钮动画效果。 一、INSysT…

学习记录——TransNormerLLM

Scaling TransNormer to 175 Billion Parametes 线性注意力的Transformer大模型 2023 Transformer 存在局限。首要的一点,它们有着对于序列长度的二次时间复杂度,这会限制它们的可扩展性并拖累训练和推理阶段的计算资源和时间效率。 TransNormerLLM 是首…

中小企业如何低成本实施MES管理系统

中小企业在市场竞争中需要有高效的管理体系来支持其运营和发展。中小企业MES管理系统是一种先进的管理系统,可以提升工厂智能化水平,提高生产效率,是中小企业必须采取的有效管理工具。然而,由于资金和技术的限制,中小企…