当前位置: 首页 > news >正文

【C语言】static和extern的作用

本文首发于 ❄️慕雪的寒舍

简单介绍C/C++中static关键字和extern关键字的作用。

1.简介

在之前的博客中,提到过static的三个作用,但是没有详细说明这三个作用的场景,现在回过头来记录一下。

  1. 修饰函数
  2. 修饰全局变量
  3. 修饰函数内变量

static还有一个隐藏的特性,即变量会被默认设置为0,因为静态区/全局区的内存区域在初始化的时候都是0。

在C++的类和对象中,static还多了一个作用,即修饰C++类的成员变量或函数。被修饰的成员属于整个类,可以直接通过类的作用域来访问(前提是公有),这不是本文的重点。

2.static修饰函数/变量

对于修饰函数和变量而言,作用基本是一致的,即限制这个函数/变量的作用域,让他只对当前文件可见。

修饰变量

给定下面两个文件

// a.cpp
int aGlobal = 10;
static int bGlobal = 20;
// b.cpp
#include <iostream>
using namespace std;// 声明文件a.cpp中的全局变量
extern int aGlobal;int main()
{cout << aGlobal << endl;return 0;
}

b.cpp中,使用了extern关键字来声明属于另外一个cpp文件中的全局变量,用如下命令编译成可执行文件test。

g++ b.cpp a.cpp -o test

在Ubuntu上进行测试,编译成功,运行能成功打印出10,符合预期。

❯ g++ a.cpp b.cpp -o test
❯ ./test
10

而被static修饰过的bGlobal就不能用这种方式被另外一个文件访问了。

// b.cpp 修改后
#include <iostream>
using namespace std;// 声明文件a.cpp中的全局变量
extern int bGlobal;int main()
{cout << bGlobal << endl;return 0;
}

使用相同的命令进行编译,此时就会报错了。因为static关键字将bGlobal这个全局变量的作用域限制在了a.cpp文件中,其他文件无法访问!

❯ g++ a.cpp b.cpp -o test
/usr/bin/ld: /tmp/ccj43Xl9.o: warning: relocation against `bGlobal' in read-only section `.text'
/usr/bin/ld: /tmp/ccj43Xl9.o: in function `main':
b.cpp:(.text+0xa): undefined reference to `bGlobal'
/usr/bin/ld: warning: creating DT_TEXTREL in a PIE
collect2: error: ld returned 1 exit status

这里有个必须要注意的点,我们在b.cpp中并没有#include "a.cpp",如果添加了include,那么上面的结论就无效了。因为include会在预编译期间被展开,此时a.cpp中的全局变量定义直接被添加在了b.cpp上面,并不属于两个文件的情况。比如下面的代码就可以正常编译通过。

// 错误的测试逻辑
#include <iostream>
#include "a.cpp" // 引用了a.cpp
using namespace std;// 声明文件a.cpp中的全局变量?此时a.cpp都已经被展开了,完全不是另外一个文件了!
// extern int bGlobal; // 这一行加不加没有任何区别int main()
{cout << bGlobal << endl;return 0;
}

如下所示,编译成功且打印了bGlobal的值

❯ g++ b.cpp -o test
❯ ./test
20

注意这里编译的时候不能再添加a.cpp了,因为此时已经被展开到了b.cpp之前,如果这样编译就会报错aGlobal重定义。

❯ g++ a.cpp b.cpp -o test
/usr/bin/ld: /tmp/cckcj9a5.o:(.data+0x0): multiple definition of `aGlobal'; /tmp/ccsNnuaN.o:(.data+0x0): first defined here
collect2: error: ld returned 1 exit status

修饰函数

修饰函数的作用同理,没有修饰的函数可以被另外一个文件extern后调用,修饰后的不可以。

// a.cpp
int Add(int a,int b){return a+b;
}
// b.cpp
#include <iostream>
using namespace std;// 声明文件a.cpp中的函数
extern int Add(int,int);int main()
{cout << Add(10,20) << endl;return 0;
}

编译成功,且调用Add函数成功。

❯ g++ a.cpp b.cpp -o test
❯ ./test
30

Add函数添加了static后就无法被extern调用了

❯ g++ a.cpp b.cpp -o test
/usr/bin/ld: /tmp/ccFe9oSW.o: in function `main':
b.cpp:(.text+0x13): undefined reference to `Add(int, int)'
collect2: error: ld returned 1 exit status

3.static修饰函数内变量

你可能见过这样的写法,在函数内定义一个static变量,并作为返回值

char* Add(int a,int b){static char arr[2];arr[0] = (char)a + '0';arr[1] = (char)b + '0';return arr; 
}

使用static修饰函数内的变量后,这个变量的作用域不再是函数体内了,而是扩展到了全局(可以理解为他就是一个全局变量)。

这种方式可以避免使用动态内存管理(malloc/free)的空间作为返回值,因为那样可能会出现内存泄漏问题。比较常见的一个应用就是linux下的inet_ntoa函数,这个函数可以将IP地址的结构体转为IP的字符串,其内部就是用static的char数组作为返回值的,这也是为什么该函数不能在printf中连续调用,会导致后续的调用覆盖前面的结果。

如果有函数是利用static作为返回值传参的,则应该用另外一个变量拷贝来保存这个结果,再执行下一次调用。

需要注意的是,static的这一行定义只会在第一次进入函数的时候执行,后续不再会执行。比如下面的代码,如果你对static修饰的作用不太了解,你可能会认为isGood这个变量每次进入该函数都会被设置为false,从而只会让他进入if判断体的A区域,搞得这个if判断都没有意义了。

void TestFunc(){static bool isGood = false;if(!isGood){// A...} else {// B...}
}

但实际上,这个变量只有第一次调用这个函数的时候会被创建且赋值为false,后续不再会执行static这一行,所以函数体内(A区域和B区域可能会做一些处理再设置isGood变量的值)对isGood值的修改会沿用到下一次调用这个函数!

4.static和编译

为什么被static修饰的函数/变量不会被其他文件看见?

因为在生成符号表的时候,每一个static变量即便变量名相同,也会生成不同的符号表项。所以在链接阶段符号表合并的时候,并不会将两个文件中同名的static变量合并在一起,所以其他文件也就没有办法访问到当前文件中被static修饰的函数/变量。

而extern了一个static变量,就会因为符号表无法找到,而链接失败。注意观察G++的报错,都是ld和.o相关字样的,说明这个并不是编译器通过语法检查出来的错误,而是在链接.o目标文件的时候,发现无法链接该变量的时候因为错误而退出编译的。

❯ g++ a.cpp b.cpp -o test
/usr/bin/ld: /tmp/ccj43Xl9.o: warning: relocation against `bGlobal' in read-only section `.text'
/usr/bin/ld: /tmp/ccj43Xl9.o: in function `main':
b.cpp:(.text+0xa): undefined reference to `bGlobal'
/usr/bin/ld: warning: creating DT_TEXTREL in a PIE
collect2: error: ld returned 1 exit status

相关文章:

【C语言】static和extern的作用

本文首发于 ❄️慕雪的寒舍 简单介绍C/C中static关键字和extern关键字的作用。 1.简介 在之前的博客中&#xff0c;提到过static的三个作用&#xff0c;但是没有详细说明这三个作用的场景&#xff0c;现在回过头来记录一下。 修饰函数修饰全局变量修饰函数内变量 static还有…...

全新分支版本!微软推出Windows 11 Canary Build 27686版

已经很久没有看到 Windows 11 全新的分支版本了&#xff0c;今天微软发布 Windows 11 Canary 新版本&#xff0c;此次版本号已经转移到 Build 27xxx&#xff0c;首发版本为 Build 27686 版。 此次更新带来了多项改进&#xff0c;包括 Windows Sandbox 沙盒功能切换到 Microsof…...

【Linux】ARM服务器命令行安装虚拟机

在Arm服务器上安装虚拟机操作笔记 一、基础环境准备1、环境准备2、检查KVM支持3、启动并启用libvirtd服务4、创建虚拟网络&#xff08;可选&#xff09;5、使用virt-manager创建虚拟机&#xff08;支持KVM&#xff09;6、管理虚拟机9、监控和日志 二、软虚拟化替代方案1、查看虚…...

Android 10.0 锁屏页面忘记锁屏密码情况下点击5次解锁图标弹出锁屏密码功能实现

1. 前言 在10.0的系统ROM定制化开发中,在一些产品中带锁屏密码的功能中,系统默认是滑动解锁,但是客户会设置锁屏密码,在某些时候会 忘掉锁屏密码,导致需要进入恢复出厂设置然后才能进入系统桌面,这样就导致系统的保存的资料都丢失了,所以需要要求在锁屏密码页面在忘记解…...

Java-CompletableFuture工具类

CompletableFuture 是 Java 8 引入的一个强大的异步编程工具,它提供了对异步计算的高级支 持,包括组合多个任务的能力、处理结果、异常处理等。为了方便地使用 CompletableFuture,你 可以创建一个工具类来封装常用的操作。 CompletableFuture 工具类 下面是一个 Complet…...

C语言:递归

递归简单来说就是函数自己调用自己。 特点&#xff1a;一般代码比较简洁&#xff0c;没有出口。 例子1&#xff1a;用一个函数计算阶乘 #include<stdio.h>//不用递归 int fac(int n) {int val 1;for (int i 1; i <n;i){val * i;}return val; }//用递归 int fac1(…...

自动化测试框架pytest+allure+requests

最近复习了一下关于自动化测试的内容&#xff0c;结合[码尚教育] 相关的思路来对测试框架进行开发。 争取实现零代码来实现自动化测试环境的搭建 AutoTestFrame 介绍 AutoTestFrame是一个基于Python的自动化测试框架&#xff0c;旨在帮助测试人员快速、高效地完成测试任务。…...

Python 笔记 numpy.ndarray切片

NumPy 的 ndarray 类型提供了非常灵活的切片功能&#xff0c;可以方便地访问和操作数组中的元素。切片允许您通过指定索引来选择数组的一部分。下面是一些基本的切片操作及其解释。 一维数组的切片 对于一维数组&#xff0c;切片操作类似于 Python 列表的切片。 示例 impor…...

一、HTML5知识点精讲

一、HTML5介绍 html是用来描述网页的一种语言&#xff08;就是写网页的一种语言&#xff09;。 它和CSS&#xff0c;JS称为网页三要素。 HTML负责把元素简单呈现在网页上&#xff0c;是网页的身体CSS负责给网页元素添加各种样式&#xff0c;是网页的衣服JS负责实现各种动态、…...

【杂乱算法】前缀和与差分

前缀和 文章目录 前缀和一维应用 二维差分一维 二维扩展1、前缀和与哈希表 一维 一个数组prefix中&#xff0c;第i个元素表示nums[0]至nums[i-1]的总和&#xff0c;那么我们就称这个prefix数组是nums数组的前缀和。 prefix [ i ] ∑ j 0 i nums [ j ] \text{prefix}[i] \s…...

Arduino调试ESP32常见问题 exit status 1

问题1&#xff1a;代码上传&#xff08;烧录&#xff09;报Failed uploading: uploading error: exit status 1大概率原因&#xff1a;没有安装对应的驱动&#xff0c;我的ESP32驱动是CH340点击这里下载CH340 下载后打开&#xff0c;若出现乱码不用在意&#xff0c;点击第一个按…...

“决胜面试:高频题目与算法策略一览”

干货分享&#xff0c;感谢您的阅读&#xff01; &#xff08;暂存篇---后续会删除&#xff0c;完整版和持续更新见高频面试题基本总结回顾&#xff08;含笔试高频算法整理&#xff09;&#xff09; 备注&#xff1a;引用请标注出处&#xff0c;同时存在的问题请在相关博客留言…...

Node-RED的安装

最近对Node-RED比较感兴趣&#xff0c;因为在上OpenHarmony课程的时候&#xff0c;一直想找一个可以通过MQTT控制设备的低代码客户端解决方案。第一次指导Node-RED是在试用聆思开发板的时候&#xff0c;它的云端就是使用的Node-RED。 在安装Node-RED之前&#xff0c;请确保您的…...

java中的Collections

Java 的集合框架(Collections Framework)提供了一组标准的数据结构接口和类,用于存储和操作数据。Java 集合类位于 java.util 包中,主要包括以下几个核心接口和实现类。 1. 核心接口 1.1. Collection 接口 Collection 是集合框架的根接口,但它本身并不提供任何直接实现…...

linux Qt QkeyEvent及驱动键盘按键捕获

基于正点原子 QT中有专门的类处理键盘事件的类QKeyEvent 1.include “QKeyEvent” 查看它的说明中的描述 也就是说接受按键事件在keyPressEvent和keyReleaseEvent这两个函数&#xff0c;继续查看 重构这个函数 查看输入的QKeyEvent类&#xff0c;发现有一个方法key返回哪一个按…...

【GH】【EXCEL】P6: Shapes

文章目录 componentslinepicture components line picture Picture A Picture object Input parameters: Worksheet (Generic Data) A Worksheet, Workbook, Range Object, Excel Application, or Text Worksheet NameName (Text) An optional object nameLocation (Point) A p…...

google浏览器chrome用户数据(拓展程序,书签等)丢失问题

一、问题背景 我出现这个情况的问题背景是&#xff1a;因为C盘块满了想清理一部分空间&#xff08;具体看这&#xff1a;windows -- C盘清理_c盘softwaredistribution-CSDN博客&#xff09;&#xff0c;于是找到了更改AppDatta这个方法&#xff0c;但因为&#xff0c;当时做迁移…...

数据结构——链式队列和循环队列

目录 引言 队列的定义 队列的分类 1.单链表实现 2.数组实现 队列的功能 队列的声明 1.链式队列 2.循环队列 队列的功能实现 1.队列初始化 (1)链式队列 (2)循环队列 (3)复杂度分析 2.判断队列是否为空 (1)链式队列 (2)循环队列 (3)复杂度分析 3.判断队列是否…...

数据库死锁解决方法,学费了吗?

避免死锁&#xff1a;尽量设计良好的数据库结构&#xff0c;避免出现死锁的情况。可以使用合适的事务隔离级别&#xff0c;以及良好的并发控制策略。 死锁检测和回滚&#xff1a;当检测到死锁时&#xff0c;可以使用死锁检测算法来确定死锁的存在&#xff0c;并回滚其中一个或…...

API网关之Apache ShenYu

Apache ShenYu&#xff08;原名Soul&#xff09;是一个开源的API网关&#xff0c;旨在支持高性能、跨语言和云原生架构。它为管理和控制客户端与服务之间的数据流提供了一种高效且可扩展的解决方案。 文档见 Apache ShenYu 介绍 | Apache ShenYu 以下是Apache ShenYu的详细介…...

Arcmap专题制图保姆级教程:从横向页面布局到网格样式自定义(附南海小图制作)

Arcmap专题制图全流程实战&#xff1a;从页面布局到南海小图制作 当你面对一堆地理数据却不知如何转化为专业地图时&#xff0c;Arcmap的专题制图功能就是你的救星。不同于简单的数据可视化&#xff0c;专题制图需要兼顾科学性与美学表达&#xff0c;既要准确传达空间信息&…...

开源AI代码助手Codetie:本地部署、模型自选与实战调优指南

1. 项目概述&#xff1a;一个面向开发者的AI代码伴侣最近在GitHub上看到一个挺有意思的项目&#xff0c;叫codetie-ai/codetie。乍一看名字&#xff0c;可能以为是某个新的编程语言或者框架&#xff0c;但深入了解后&#xff0c;发现它的定位非常精准&#xff1a;一个开源的、本…...

别再傻傻分不清!RV、RVV、RVVP这些电工字母到底啥意思?一张图帮你搞定家庭布线选线

家庭电工实战指南&#xff1a;RV/RVV/RVVP线材选型与避坑手册 刚打开装修材料清单时&#xff0c;那些密密麻麻的字母组合让人瞬间头大——RV、RVV、RVVP、AVVR...这些看似天书的代号&#xff0c;直接决定了你家插座能否承载大功率电器、智能窗帘会不会信号中断&#xff0c;甚至…...

用Python和NumPy手把手教你仿真均匀线阵方向图(从公式到代码)

用Python和NumPy手把手教你仿真均匀线阵方向图&#xff08;从公式到代码&#xff09; 天线阵列的方向图分析是无线通信系统设计中的基础课题。对于刚接触阵列信号处理的工程师和学生来说&#xff0c;如何将教科书上的数学公式转化为可运行的代码&#xff0c;往往是一个令人头疼…...

基于MCP协议实现AI助手调用本地快捷指令的完整指南

1. 项目概述&#xff1a;一个为AI助手“开眼”的桥梁最近在折腾AI工作流的朋友&#xff0c;可能都听说过MCP&#xff08;Model Context Protocol&#xff09;这个概念。简单来说&#xff0c;它就像给Claude、Cursor这类AI助手装上了一套“万能遥控器”&#xff0c;让它们能直接…...

【独家拆解】Sora 2正式版底层架构升级:从DiT-XL到时空联合注意力v3.2,性能提升217%的关键证据

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;Sora 2正式版发布背景与核心定位 OpenAI 于2024年第三季度正式发布 Sora 2&#xff0c;标志着视频生成模型从实验性原型迈入工业级部署新阶段。此次发布并非简单迭代&#xff0c;而是基于对数百万小时真…...

基于MCP协议构建微信通知服务:解耦业务与通知逻辑的实践

1. 项目概述&#xff1a;一个面向开发者的轻量级通知集成工具最近在折腾一个自动化脚本&#xff0c;需要把运行结果实时推送到手机上&#xff0c;但又不想把各种IM的SDK耦合进代码里&#xff0c;太臃肿了。相信很多做后端服务、运维监控或者自动化脚本的朋友都遇到过类似的需求…...

Sidekiq工作分配与负载均衡终极指南:高效管理后台任务的10个技巧

Sidekiq工作分配与负载均衡终极指南&#xff1a;高效管理后台任务的10个技巧 【免费下载链接】sidekiq Simple, efficient background processing for Ruby 项目地址: https://gitcode.com/gh_mirrors/si/sidekiq Sidekiq是Ruby生态中最受欢迎的后台作业处理框架&#x…...

观察Taotoken在高峰时段的模型路由与容灾表现

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 观察Taotoken在高峰时段的模型路由与容灾表现 在构建依赖大模型能力的应用时&#xff0c;服务的稳定性是开发者关心的核心问题之一…...

【LeetCode刷题日记】一篇带你搞懂平衡二叉树高效判断法(110.平衡二叉树)

&#x1f525;个人主页&#xff1a;北极的代码&#xff08;欢迎来访&#xff09; &#x1f3ac;作者简介&#xff1a;java后端学习者 ❄️个人专栏&#xff1a;苍穹外卖日记&#xff0c;SSM框架深入&#xff0c;JavaWeb ✨命运的结局尽可永在&#xff0c;不屈的挑战却不可须臾或…...