详解static、volatile、const

2023-11-15

1、背景

在查阅相关资料的时候,无意间看到一个大佬对于static关键字的讲解,如雷贯耳,写得非常容易理解,这是大佬的链接

本人在学习相关知识的时候,喜欢也习惯把从各种书籍或者是各位大佬的博客中学到的知识用自己的逻辑和自己的语言重新组织一下(当然,如果书中或者各位大佬的描述我觉得非常好理解,我就直接使用啦),方便自己记忆,加深影响。

废话不多说,我们进入正题

2、static关键字介绍

2.1、static的作用

static是“静态”的意思,一般我们使用static定义的变量,我们就称为静态变量,那么静态变量和普通变量有什么区别呢?

2.1.1、局部变量

首先从局部变量的角度看看普通变量和静态变量有什么区别

我们都知道,局部变量是定义在函数内部,在进入函数的时候定义并分配存储空间(局部变量是存储在进程栈空间的),但函数执行完毕后,系统就会回收变量内存空间,定义局部变量,有两种方式

void funtion(void)
{
	int a;			//定义了一个局部变量a,但没有进行初始化,也就是这个时候a的值是不确定的
	int b = 0;		//定义了一个局部变量b,在定义的时候顺便初始化为0,这个时候b的值是确定的,为0
}

用static定义的局部变量和普通变量有什么区别呢? 主要有两点区别:

  • 用static定义的局部变量,当该变量在定义的时候没有赋初值,编译器会将它初始化为0;
  • 用static定义的局部变量,存储在进程的全局数据区,也就是当它所在的函数返回时,它的值也会保持不变,但是因为它是局部变量,所以作用域依旧是局部作用域。

我们来用一个例子加深一下印象

void function_normal(void)
{
	int a = 6;
	
	printf("function_normal a = %d\n", a);

	a++;
	printf("function_normal a++ = %d\n", a);

}

void function_static(void)
{
	static int b = 6;
	printf("function_static b = %d\n", b);

	b++;
	printf("function_static b++ = %d\n", b);
	
}

int main(void)
{
	printf("第一次调用\n");
	function_normal();
	function_static();

	printf("第二次调用\n");
	function_normal();
	function_static();

	return 0;
}

输出的结果是:所以是符合我们的结论的:static定义的局部变量存储在进程的全局数据区,当其所在的函数退出时,它的值是保持不变的, 可见,静态局部变量的效果跟全局变量有一拼,但是位于函数体内部,就极有利于程序的模块化了。

第一次调用
function_normal a = 6
function_normal a++ = 7
function_static b = 6
function_static b++ = 7
第二次调用
function_normal a = 6
function_normal a++ = 7
function_static b = 7
function_static b++ = 8

2.1.2、全局变量

除了局部变量,还有一种变量是全局变量,全局变量跟静态局部变量一样,是定义在进程的全局数据内存区,那普通全局变量和静态全局变量又有什么区别呢?

  • 普通全局变量对整个工程可见,其他文件可以使用extern外部声明后直接使用。也就是说其他文件不能再定义一个与其相同名字的变量了(否则编译器会认为它们是同一个变量)。

  • 静态全局变量仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响。

2.1.3、函数

static还有一个重要的使用场景,那就是在函数中使用。 我们经常可以在内核的代码中看到,很多函数前面都会加static关键字进行修饰,这是为什么呢?

我们知道,如果我们定义一个普通的函数,那么我们在其他的文件中,只要使用extern这个关键字对该函数进行声明后,就可以使用,但是, 使用了static关键字定义的函数,就只在当前文件中可用,其他文件无法通过extern来进行声明后调用。 我们举个例子来加深下印象:

//首先创建第一个文件 first.c

//定义一个普通函数
void first_func_normal(void)
{
	printf("I am first_func_normal\n");
}

//再定义一个static修饰的函数
static void first_func_static(void)
{
	printf("I am first_func_static\n");
}

//创建第二个文件 second.c

extern void first_func_normal(void);

int main(void)
{
	first_func_normal();	//先试一下调用普通函数
	return 0;

}

//打印 I am first_func_normal
//创建第二个文件 third.c
extern void first_func_static(void);

int main(void)
{
	first_func_static();	//调用静态函数
	return 0;

}

//打印 undefined reference to `first_func_static'

这又验证了我们的结论:使用static定义过的函数,只能在该函数所在的文件中被调用,外部函数无法通过extern关键字进行定义调用

3、volatile关键字

讲了static关键字,那么就顺便将另一个很容易被大家混淆的——volatile 关键字,我们从网上或者从书籍上查阅到的资料,大体都差不多是这一句话来解释volatile关键字,那就是 不要进行编译优化/不可被编译器优化的,按照我们正常的逻辑,就会产生一个问题了,优化是什么样的?不优化又是怎么样的?

  • 使用volatile:volatile 告诉编译器变量是随时可能发生变化的,每次使用它的时候必须从变量的地址中读取(从内存中读取)
  • 不使用volatile(编译器优化做法):由于编译器发现两次从变量读数据的代码之间的代码没有对变量进行过操作,它会从寄存器中读取变量值

更加详细的介绍,大伙可以看看这篇博客,我这里就不多赘述了。

4、const关键字

前面介绍的,使用volatile关键字修饰的变量,是不可被编译器优化的,而使用const关键字进行修饰的变量,表示的意思是变量是不可被更新的,const关键字有下面这几个应用场景。

直接上一个例子看看:

void main(void)
{
	int a = 5;
	int b = 6;
	int c = 7;
	const int* p1 = &a;
	int* const p2 = &b;

	printf("p1 = %d, p2 = %d\n", *p1, *p2);
	
	*p1 = 80;
	p2  = &c;
	printf("p1 = %d, p2 = %d\n", *p1, *p2);	
}

以上程序是一个const正确使用的例子,主要的比较容易混淆的地方, 就是p1、p2的定义,如何区分呢?我自己总结了一个很容易记住的法则,去掉变量类型,const后面跟着什么,说明后面的这个就是不可变的, 比如,const int* p1 = const * p1,const后面跟着的是什么?是*,代表的是一个指针,所以说明p1指向的地方被const修饰了,不可变;int * const p2, = * const p2, const后面跟着的是什么?是p2,代表的是p2指向的这个内存值是不可变的。

再来结合一下上面的例子:

  • const int* p1 = &a;
    这一句代表什么?套入我们的“公式”来看一下,const int* p1 = &a就相当于 const * p1 = &a,也就是将p1指向变量a的那块内存,并且p1就只能指向这块内存了(因为const后面接着的是*号呀)

    那我们前面是不是说了,p1指向这块内存不能变,但是p1指向的这块内存的值可以变呀,所以*p1 = 80,将p1指向的这块内存的值变为80就是合法的。这里可能有的同学会产生疑问,那么p1指向的这块内存的值变为80了,那a的值会不会也改变了? 答案是会的,因为我们前面const int* p1 = &a已经将p1指向变量a的内存了,修改p1的内存也就是修改变量a的值。 但是如果使用p1 = &c就是在修改p1的值,也就是在修改p1指向的内存位置,这样就是不合法的了。

  • int* const p2 = &b;
    按照前面的“公式”, int* const p2 = &b就相当于 * const p2 = &b,const后面跟着的是p2,代表的是p2所指向的内存中的变量值不能修改,但是p2本身的值是可以修改的,也就是p2 = &c,将p2指向变量c的内存空间是合法的。这个时候可能又有人有疑问了,那么运行了p2 = &c后,说明p2的地址值已经改变了,那么是不是就可以对*p2指向的内存(也就是变量c的内存)进行操作了呢?答案是不可以的,你们可以这么理解,在p2初始化的时候,它就已经是int * const 的类型了,这代表今后没办法通过它来修改它所指的内存空间中的值,也就是*p2 = 99这样的操作是不被允许的,但是,因为前面p2指向的是变量c的地址,c还是可以对该内存的值进行修改,进而影响p2所指向的内存值。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

详解static、volatile、const 的相关文章

随机推荐

  • 去了家新公司,技术总监不让用 IntelliJ IDEA想离职了

    最近有个小伙伴微信和我说 新去的一家公司 技术团队全部规定要用的 Eclipse 开发 技术总监不让用 IntelliJ IDEA 付费也不行 说想离职了 问我该怎么办 首先听到这件事情的时候 我表示十分理解该公司技术总监的决定 虽然我没有
  • 小米6/6X/米8/米9手机刷入鸿蒙HarmonyOS.4.0系统-刷机包下载-遥遥领先

    小米手机除了解锁root权限 刷GSI和第三方ROM也是米粉的一大爱好 这不 在华为发布了HarmonyOS 4 0系统后不久 我们小米用户也成功将自己的手机干山了HarmonyOS 4 0系统 虽然干上去HarmonyOS 4 0系统目前
  • Ubuntu16.04下使用python3,pycharm 安装django

    这里做个总结吧 一直用Ubuntu终端安装django老是安装不上去 我用的是Ubuntu16 04 里面有python2 7 和python3 5两个版本 注意 如果没有更改默认python版本的话 安装pip 直接使用 命令 sudo
  • 修改elementui的导航菜单样式,悬浮、聚焦、失焦,超方便

    最近工作中用到饿了么中的导航菜单 原悬浮背景色不符合要求 且失焦后 无背景色 文档中居然没有给修改这些的接口 急得我要去改源码了 但是吧 看了看源码我又放弃了 我觉得还不如自己重新写 这个问题放了两三天 今天又看了看 突然发现浏览器的一个小
  • 多线程进阶篇Step2

    目录 CAS 乐观锁实现方式之一 CAS操作流程 应用1 使用CAS实现了原子类 AtomicInteger实现i 实现原理 应用2 使用CAS来实现自旋锁 应用3 CAS引发的ABA问题 问题描述 解决办法 引入版本号 synchroni
  • React脚手架搭建项目

    React提供了一个用于创建react项目的脚手架库 create react app 一 项目的搭建 第一步 全局安装 npm i g create react app 第二步 切换到准备创建项目的目录 使用命令 create react
  • $.ajax()方法从服务器获取json数据

    一 什么是json json是一种取代xml的数据结构 和xml相比 它更小巧但描述能力却很强 网络传输数据使用流量更少 速度更快 json就是一串字符串 使用下面的符号标注 键值对 json对象 json数组 双引号内是属性或值 冒号前为
  • 收银系统服务器搭建方法,如何搭建一个小型企业服务器机房?6个步骤学起来!...

    你是否担心依赖第三方在线服务提供商来存储你的业务数据 通过分析传统与云服务器的优势 云服务器真的淘汰了传统服务器 你怎么看 不少企业选择了搭建传统的服务器 并通过内部部署IT解决方案来减少在线数据存储的安全问题 甚至完全避免这些问题 但问题
  • C#中的线程(一)入门

    文章系参考转载 英文原文网址请参考 http www albahari com threading 作者 Joseph Albahari 翻译 Swanky Wu 中文翻译作者把原文放在了 google 协作 上面 GFW屏蔽 不能访问和查
  • 两种点云分割(一)— RANSAC分割平面

    本文为博主原创文章 未经博主允许不得转载 本文为专栏 python三维点云从基础到深度学习 系列文章 地址为 https blog csdn net suiyingy article details 124017716 点云分割的目的是将点
  • mysql中幻读出现的原因及解决方案

    今天分享 mysql中幻读出现的原因及解决方案 一 首先明确什么是幻读 事务A按照一定条件进行数据读取 期间事务B插入了相同搜索条件的新数据 事务A再次按照原先条件进行读取操作修改时 发现了事务B新插入的数据称之为幻读 二 幻读出现的场景
  • openwrt--内核编译及生成

    重要文件 在下面的目录中包含了编译过程中调用的makefile 很重要的 root localhost openwrt openwrt trunk include ls autotools mk device table txt bak k
  • 基于Python+Selenium的web自动化测试框架详解(完整视频教程+项目实战源码供你学习)

    目录 简介 Python Selenium Web自动化测试框架概述 Python Selenium Web自动化测试框架目标 Python Selenium Web自动化测试框架流程 1 测试计划和设计 2 测试脚本开发 3 测试执行和管
  • service mysql start出错,mysql启动不了,解决mysql: unrecognized service错误

    service mysql start出错 mysql启动不了 解决mysql unrecognized service错误的方法如下 root ctohome com service mysql start mysql unrecogni
  • Spark 配置远程DEBUG

    Spark远程调试 本例子介绍简单介绍spark一种远程调试方法 使用的IDE是IntelliJ IDEA 1 了解jvm一些参数属性 Xdebug Xrunjdwp transport dt socket server y suspend
  • SpringCloud是什么?能干什么?组件现状?

    目录 一 SpringCloud 1 1 什么是SpringCloud 1 2 SpringCloud缺点 二 什么是微服务架构 2 1 什么是微服务 2 2 微服务有什么优点 2 3 微服务面临的问题 2 4 架构类型划分 2 4 1 单
  • windows10安装WSL(Ubantu18.04)后无法实现ssh远程链接的解决办法

    文章目录 一 安装Ubantu18 04并打开wsl权限 1 在程序与功能中勾选并开放wsl的权限 并重启电脑进行加载wsl的配置文件 2 打开Microsoft Store 应用商店下载Ubantu18 04 二 配置并启动ssh服务 1
  • STM32单片机多功能电子秤点数秤食物热量卡路里称重

    实践制作DIY GC0132 多功能电子秤 一 功能说明 基于STM32单片机设计 多功能电子秤 二 功能介绍 STM32F103C系列最小系统 lcd1602 HX711 5Kg电子秤 去皮键 模式选择按键 重量设置键 上键 下键 有3种
  • 数据采集与埋点简介之 代码埋点、可视化埋点与无痕埋点

    博主做移动手机系统中的数据采集与埋点也有近两年 那段时间内一方面是集中在具体的开发和问题细节处理 另外一方面则是在把采集系统适配到不同的平台手机 平板 tv 车载的过程中 有Android和C 两个版本 有一天见到了 神策数据 的这篇博文
  • 详解static、volatile、const

    1 背景 在查阅相关资料的时候 无意间看到一个大佬对于static关键字的讲解 如雷贯耳 写得非常容易理解 这是大佬的链接 本人在学习相关知识的时候 喜欢也习惯把从各种书籍或者是各位大佬的博客中学到的知识用自己的逻辑和自己的语言重新组织一下