深入C语言:探究static关键字的奥秘

文章目录

  • 一、链接属性
  • 二、static变量
    • 1、定义静态局部变量
    • 2、在函数内部使用静态变量
    • 3、函数中静态局部变量与递归
  • 三、static变量与全局变量的区别
    • 1、存储期与生命周期
    • 2、可见性与作用域
    • 3、使用场景
    • 4、静态与动态内存分配
  • 注意事项

当用于不同的上下文环境时, static关键字具有不同的意思。

当用于函数定义时,或用于代码块之外的变量声明时,static关键字用于修改标识符的链接属性(从 external 变为 internal ),但标识符的存储类型和作用域不受影响。用这种方式声明的函数或变量只能在声明它们的源文件中访问。

当用于代码块内部的局部变量的声明的时候,static用于修改变量的存储类型,从自动变量修改为静态变量,但变量的链接属性和作用域不受影响。用这种方式声明的变量在程序执行之前创建,并在程序的整个执行期间一直存在,而不是每次在代码块开始执行时创建,在代码块执行完毕之后销毁。

一、链接属性

链接属性一共有3种–external(外部)、internal(内部)和 none(无)。

  • 没有链接属性的标识符(none)总是被当作单独的个体,也就是说该标识符的多个声明被当作不同的独立实体。

  • 属于internal链接属性的标识符在同一个源文件内的所有声明中都指同一个实体,但位于不同源文件的多个声明则分属不同的实体。

  • 属于 external链接属性的标识符不论声明多少次,位于几个源文件都表示同一个实体。

关键字 externstatic用于在声明中修改标识符的链接属性。如果某个声明在正常情况下具有external链接属性,在它前面加上static关键字可以使它的属性变为internalstatic int a;那么变量a就将为这个源文件私有。其他源文件中,如果也链接到一个叫做变量 a 的变量,那么它所引用的是另一个不同的变量。类似的也可以把函数声明为 static

static int func(); 

这样可以防止被其他源文件调用。

extern 关键字的规则更为复杂。一般而言,它为一个标识符指定external链接属性,这样就可以访问在其他任何位置定义的这个实体。

具有 external属性的变量我们一般称之为全局变量,所有源文件中的所有函数均可以访问它。只要变量并非声明于代码块或函数定义内部,它在缺省的情况下的链接属性即为external。如果一个变量声明于代码块内部,在它前面添加extern关键字讲使它所引用的是全局变量而非局部变量。

具有 external链接属性的实体总是具有静态存储类型。全局变量在程序开始执行前创建,并在程序整个执行过程中始终存在。从属于函数的局部变量在函数开始执行时创建,在函数执行完毕后销毁,但用于执行函数的机器指令在程序的生命期内一直存在。

局部变量由函数内部使用,不能被其他函数通过名字引用。它在缺省情况下的存储类型为自动。这是基于两个原因:其一,当这些变量需要时才为它们分配存储,这样可以减少内存的总需求量;其二,在堆栈上为它们分配存储可以有效地实现递归。如果你觉得让变量的值在函数的多次调用中始终保持原先的值非常重要的话,那么可以修改它的存储类型,把它从自动变量改为静态变量。


二、static变量

在C语言中,当在函数内部定义static变量时,该变量具有静态存储期,这意味着该变量的生命周期将贯穿整个程序的执行过程,而不仅仅是局限于函数调用的局部范围。此外,静态局部变量只会被初始化一次,即在程序开始执行时,后续函数调用中不会再次初始化。由于这种特性,静态局部变量常用于在函数调用之间保持状态,如计数或累积数据。

1、定义静态局部变量

在函数内部,使用static关键字定义变量。

int main() {static int myStaticVar = 0; // 静态局部变量,只初始化一次// ... 函数的其他代码 ...
}

2、在函数内部使用静态变量

像使用普通局部变量一样使用静态局部变量,通过变量名进行访问和修改。可以在多次函数调用中保持状态,由于静态局部变量在函数调用之间保持其值,因此它们可用于跨函数调用维护状态。

void myFunction() {static int count = 0;count++; // 每次调用函数时,count增加1printf("Function has been called %d times.\n", count);
}

在这里插入图片描述

在上面的例子中,myFunction函数有一个静态局部变量count,它只会在程序开始时初始化一次。每次调用这个函数时,count都会递增,并且由于它是静态的,所以它的值会在函数调用之间保持。

需要注意的是:

  • 静态局部变量不会自动初始化为它们的默认值(比如int类型的0),除非显式地进行初始化。如果未初始化,它们将包含未定义的值。
  • 静态局部变量不会占用函数每次调用时的栈空间,因为它们的存储期是整个程序的执行期间,通常存放在程序的数据段中。
  • 在多线程环境中,静态局部变量可能不是线程安全的,因为所有线程共享同一份静态变量的存储。如果需要跨线程保持状态,应使用线程安全的机制,如互斥锁(mutex)或原子操作。

所有不加static的全局变量和函数具有全局可见性,可以在其他文件中使用;加了static之后只能在该文件所在的编译模块中使用。

在这里插入图片描述

一个函数被static修饰,使得这个函数只能在本源文件内使用,不能在其他源文件内使用。

在这里插入图片描述

在C语言中,static关键字在函数调用中的主要用法是用于声明函数内部的局部变量,使其具有静态存储期。这意味着这些局部变量不会在函数调用结束时被销毁,而是会保留其值,直到程序结束。这在需要跨函数调用保留状态时特别有用。(注意:main函数所在文件需声明 add函数。)

3、函数中静态局部变量与递归

在递归函数中,static局部变量也很有用,因为它们可以在递归调用之间保持其值。例如,下面是一个计算阶乘的递归函数:

unsigned long long factorial(int n) {static unsigned long long result = 1; // 静态局部变量用于累积结果if (n > 0) {result *= n;return factorial(n - 1); // 递归调用} else {return result; // 返回累积的结果}
}

在这个例子中,result是一个static局部变量,用于在递归调用之间累积阶乘的结果。如果没有static关键字,每次递归调用都会创建一个新的局部变量result,导致结果不正确。


三、static变量与全局变量的区别

1、存储期与生命周期

全局变量:全局变量在程序开始执行时分配内存,并在整个程序的执行期间都保持其存在。无论函数是否被调用,全局变量都占据内存空间,并在程序结束时释放其内存。

static变量:无论是全局还是局部,static变量都具有静态存储期。这意味着它们在程序开始执行时分配内存,并且在程序的整个执行期间都存在。对于static局部变量来说,这是一个关键区别,因为普通的局部变量只在函数被调用时存在,并在函数返回时释放其内存。

2、可见性与作用域

全局变量:全局变量的作用域是整个程序,这意味着它们可以在任何函数内部被访问和修改(除非被同名的局部变量覆盖)。同时,由于它们具有外部链接属性,它们可以在其他文件中通过extern声明被引用。

static全局变量:尽管它们在存储期上与全局变量相同,但static关键字改变了它们的可见性。static全局变量仅在其定义的文件中可见,即使其他文件包含了相应的头文件,也无法直接访问它们。这是因为static全局变量具有内部链接属性,使得它们的链接仅限于定义它们的文件。

static局部变量:这些变量在函数内部定义,其作用域仅限于该函数。但是,由于它们的静态存储期,它们的值在函数调用之间保持不变。这意味着每次函数被调用时,它都会看到上次调用时留下的static局部变量的值。

3、使用场景

全局变量:全局变量通常用于需要在多个函数或文件之间共享的数据。然而,过度使用全局变量可能导致代码难以理解和维护,因为它们可以被程序的任何部分修改。

static变量static全局变量通常用于限制变量的可见性,以避免命名冲突和提高代码模块的独立性。static局部变量则常用于需要在函数调用之间保持状态的场景,如计数器或累加器。

总结来说,static变量和全局变量在存储期、可见性、作用域、初始化以及使用场景方面存在显著的区别。选择使用哪种类型的变量取决于具体的应用需求和编程风格。在编写代码时,应该谨慎考虑变量的存储期和可见性,以确保程序的正确性和可维护性。

4、静态与动态内存分配

static并不直接涉及动态内存分配(如mallocfree),但理解静态和动态内存分配的不同是很重要的。静态变量在编译时分配内存,而动态内存分配在运行时进行。


注意事项

  • static局部变量只在定义它们的函数内部可见,它们不是全局变量。
  • static局部变量只会被初始化一次,即使函数被多次调用。
  • static变量在程序的整个执行期间都存在,这意味着它们会占用内存,直到程序结束。因此,过度使用static局部变量可能会导致内存浪费。

总结来说,static在函数调用中的主要用法是定义具有静态存储期的局部变量,以便在函数调用之间保留其值。这在需要跨函数调用保留状态或累积数据的场景中特别有用。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

手搓 Docker Image Creator(DIC)工具(02):预备知识

此节主要简单介绍一下 Docker、Dockerfile 的基本概念,Dockerfile 对的基本语法,Windows 和 macOS 下 Docker 桌面的安装,Docker 镜像的创建和运行测试等。 1 关于 Docker Docker 是一个开源的应用容器引擎,它允许开发者打包应用…

各种坐标系辨析

坐标系辨析 0. 地球椭圆体1. 大地坐标系2. eci地心惯性坐标系3. 地心地固坐标系(ECEF坐标系,E系)4. 站心坐标系(ENU坐标系)5. LTM坐标系6. IMU坐标系7. 代码部分7.1 LLA(大地坐标系坐标、经纬度海拔)坐标转LTM系(ENU系)下的三维笛卡尔坐标 0. 地球椭圆体 地球表面是…

游泳耳机哪个牌子最好?2024好评率爆表的四款游泳耳机推荐!

在当今快节奏的生活中,运动成为了我们保持健康和放松身心的重要方式之一。游泳作为一项全身运动,不仅可以帮助我们塑造身材,还能让我们在水中尽情享受自由的感觉。然而,在游泳过程中,音乐却往往因为防水问题而成为一种…

【氮化镓】同质GaN垂直PiN二极管的SEB

【Single-event burnout in homojunction GaN vertical PiN diodes with hybrid edge termination design. Appl. Phys. Lett. 124, 132101 (2024)https://doi.org/10.1063/5.0189744】 概括: 本研究探讨了具有混合边缘终止设计(Hybrid Edge Terminati…

LabVIEW挖坑指南

一、挖坑指南 1.1、输出变量放在条件框内 错误写法: 现象:如果没进入对应的分支,输出为默认值 正常写法: 让每个分支输出的值都在预料之内。 1.2、统计耗时不准 错误写法 现象:统计出来的耗时是2000ms 正常写法&a…

【Java代码审计】SSRF篇

【Java代码审计】SSRF篇 1.SSRF漏洞2.Java-SSRF漏洞常见接口3.SSRF漏洞演示URLConnectionURLConnection绕过 4.SSRF修复白名单方式过滤方式通用预防SSRF方法 1.SSRF漏洞 SSRF 是 Server-Side Request Forge 的英文首字母缩写,中文意思是服务器端请求伪造。Web 应用…

Coze工作流介绍(一)

Coze工作流介绍 工作流支持通过可视化的方式,对插件、大语言模型、代码块等功能进行组合,从而实现复杂、稳定的业务流程编排,例如旅行规划、报告分析等。 当目标任务场景包含较多的步骤,且对输出结果的准确性、格式有严格要求时…

鱼哥赠书活动第17期:看完这本《Python数据分析》菜鸟也能做Python数据分析?

鱼哥赠书活动第17期:看完这本《Python数据分析》菜鸟也能做Python数据分析? 一、Python是办公自动化的重要工具二、Python是提升职场竞争力的利器三、Python是企业数字化的重要平台四、Python是AI发展的重要通道之一内容简介:第一部分(第1~7章…

c++|vector使用及模拟实现

目录 一、vector的介绍 二、vector的使用(常用接口) 2.1string类的成员函数 2.1.1构造函数 2.1.2析构函数 2.1.3“”运算符重载函数 2.2 迭代器(iterator) 及 对象的遍历访问 2.2.1iterator 2.2.2 operator[] && at() 2.2.4 back() && front() 2.2…

Node.js环境调用百度智能云(百度云)api鉴权认证三步走

方式一 :Postman脚本的方式生成v1版本的认证字符串 Postman脚本下载 下载Postman pre-request Script 设置 Authorization 示例脚本 方式二:在线签名工具生成 (试用于验证编程字符串签名是否有错误) 签名计算工具 https://cloud.baidu.com/signature/index.html …

【蓝桥杯嵌入式】六、真题演练(一)-1演练篇:第 14 届真题

温馨提示: 真题演练分为模拟篇和研究篇。本专栏的主要作用是记录我的备赛过程,我打算先自己做一遍,把遇到的问题和不同之处记录到演练篇,然后再返回来仔细研究一下,找到最佳的解题方法记录到研究篇。 目录 解题记录&…

索引的概念

索引的概念    1.索引是一种可选的与表相关的数据库对象,用于提高数据的查询效率。    2.索引是一种有序的数据结构。    3.如果一个表没有创建索引,则对该表进行查询时需要进行全表扫描;如果创建了索引,则在有条件查询时…