c++中什么时候应该使用extern关键字?

目录

1. 共享全局变量

2. 共享常量(const)变量

3. 与C语言交互

4. 显式模板实例化声明

5. 动态库符号管理

注意事项

关键总结


在C++中,extern关键字主要用于声明变量、函数或模板的外部链接性,表明其定义存在于其他编译单元中。以下是extern的主要使用场景及示例:

1. 共享全局变量

当需要在多个源文件之间共享全局变量时,使用extern避免重复定义:

问题场景
在多个源文件中直接定义同名全局变量:

// file1.cpp
int globalVar = 10;

// file2.cpp
int globalVar = 20;  // 重复定义!

后果

  • 链接阶段会触发 "multiple definition" 错误,违反一次定义规则(ODR)。

解决方案
使用extern声明,单一定义:

// header.h
extern int globalVar;  // 声明

// file1.cpp
int globalVar = 10;    // 定义(唯一)

// file2.cpp
#include "header.h"
void useVar() { globalVar = 30; }  // 正确使用

示例
考虑如下情形:

.
├── file1.cpp
├── file1.h
├── file2.cpp
├── file2.h
├── global.h
└── main.cpp

1 directory, 6 files

代码:

//global.h
#pragma once
int global_var = 10;

/***********************************************/
//file1.h
#pragma once
#include "global.h"

void func1();

/***********************************************/
//file1.cpp
#include "file1.h"
#include<iostream>

void func1()
{
    std::cout<<"func1:"<<global_var<<","<<&global_var<<std::endl;
}

/***********************************************/
//file2.h
#pragma once
#include "global.h"

void func2();

/***********************************************/
//file2.cpp
#include "file2.h"
#include<iostream>

void func2()
{
    std::cout<<"func2:"<<global_var<<","<<&global_var<<std::endl;
}

/***********************************************/
//main.cpp
#include "file1.h"
#include "file2.h"

int main(int argc,char* argv[])
{
    func1();
    func2();
    return 0;
}

编译报错:

g++   file1.cpp file2.cpp main.cpp  -o demo 

/usr/bin/ld: /tmp/ccoKZ0nw.o:(.data+0x0): multiple definition of `global_var'; /tmp/ccMc4Z3v.o:(.data+0x0): first defined here
/usr/bin/ld: /tmp/cc743UGw.o:(.data+0x0): multiple definition of `global_var'; /tmp/ccMc4Z3v.o:(.data+0x0): first defined here
collect2: error: ld returned 1 exit status

解决办法:

修改global.h:

#pragma once
extern int global_var;

增加global.cpp:

#include "global.h"

int global_var = 10;

编译:

g++  global.cpp  file1.cpp file2.cpp main.cpp  -o demo 

运行:

func1:10,0x55b9f94d3010
func2:10,0x55b9f94d3010

 可见file1.cpp和file2.cpp中用的是同一个global_var。


2. 共享常量(const)变量

const全局变量默认具有内部链接性(仅在当前文件可见)。若需跨文件共享,需用extern

问题场景
头文件中直接定义const变量:

// constants.h
const double PI = 3.14159;  // 每个包含此头的源文件生成独立副本

// file1.cpp
#include "constants.h"  // 生成 PI 副本1

// file2.cpp
#include "constants.h"  // 生成 PI 副本2

后果

  • 浪费内存空间(多份相同常量)。

  • 若需统一修改值,需重新编译所有包含该头文件的目标文件。

解决方案
使用extern声明,单一定义:

// constants.h
extern const double PI;  // 声明

// constants.cpp
const double PI = 3.14159;  // 唯一定义(extern使常量具有外部链接性)

示例
考虑如下情形:

.
├── constants.h
├── file1.cpp
├── file1.h
├── file2.cpp
├── file2.h
└── main.cpp

1 directory, 6 files

代码:

//constants.h
#pragma once
const float PI = 3.14;

/*********************************/
//file1.h
#pragma once
#include "constants.h"

void func1();

/*********************************/
//file1.cpp
#include "file1.h"
#include<iostream>

void func1()
{
    std::cout<<"func1:"<<PI<<","<<&PI<<std::endl;
}

/*********************************/
//file2.h
#pragma once
#include "constants.h"

void func2();

/*********************************/
//file2.cpp
#include "file2.h"
#include<iostream>

void func2()
{
    std::cout<<"func2:"<<PI<<","<<&PI<<std::endl;
}

/*********************************/
//main.cpp
#include "file1.h"
#include "file2.h"

int main(int argc,char* argv[])
{
    func1();
    func2();
    return 0;
}

编译运行:

$ g++  main.cpp  file1.cpp  file2.cpp   -o demo
$ ./demo 
func1:3.14,0x55c615beb008
func2:3.14,0x55c615beb01c

可以发现,file1.cpp和file2.cpp中的PI不是同一个PI。

解决办法:

修改constants.h:

#pragma once
extern const float PI;

新增constants.cpp:

#include "constants.h"

extern const float PI = 3.14;// 测试发现,此处不写extern效果一样

目录结构变为:

.
├── constants.cpp
├── constants.h
├── file1.cpp
├── file1.h
├── file2.cpp
├── file2.h
└── main.cpp

1 directory, 7 files

编译运行:

$ g++  main.cpp constants.cpp  file1.cpp  file2.cpp   -o demo
$ ./demo
func1:3.14,0x5639097c8004
func2:3.14,0x5639097c8004

可以看到,file1.cpp和file2.cpp中用的PI是同一个PI。


3. 与C语言交互

避免C++的名称修饰(name mangling),确保C++代码能调用C编译的函数:

问题场景
C++直接调用C函数时,未禁用名称修饰(name mangling):

// c_utils.h
void c_function();  // C函数声明

// main.cpp
#include "c_utils.h"
int main() {
    c_function();  // C++编译后可能生成 _Z11c_functionv 的符号
}

后果

  • C语言编译的库中函数名为c_function,但C++生成修饰名,导致 链接器无法找到符号

解决方案
使用extern "C"禁用修饰:

// c_utils.h
#ifdef __cplusplus
extern "C" {  // 按C规则编译
#endif
void c_function();
#ifdef __cplusplus
}
#endif

4. 显式模板实例化声明

减少编译时间和代码体积,通过extern template避免隐式实例化:

问题场景
多文件中频繁使用同一模板实例:

// utils.h
template<typename T>
class Vector { /*...*/ };

// file1.cpp
Vector<int> v1;  // 隐式实例化Vector<int>

// file2.cpp
Vector<int> v2;  // 再次隐式实例化Vector<int>

后果

  • 每个编译单元生成相同模板实例化代码,导致 编译时间增加 和 二进制文件膨胀

解决方案
使用extern template声明:

// utils.h
extern template class Vector<int>;  // 声明:不在此处实例化

// template_instances.cpp
template class Vector<int>;  // 显式实例化(唯一)

5. 动态库符号管理

问题场景
Windows动态库未显式导出符号:

// dll_lib.h
void dll_function();  // 未声明导出

// dll_lib.cpp
void dll_function() {}  // 编译为DLL时符号未导出

后果

  • 其他模块调用时出现 "unresolved external symbol" 错误

解决方案
结合extern和平台特性导出符号:

// dll_lib.h
#ifdef MYLIB_EXPORTS
#define API __declspec(dllexport)
#else
#define API __declspec(dllimport)
#endif

extern "C" API void dll_function();  // 声明为导出接口

注意事项

  • 一次定义规则(ODR)extern声明的变量/函数必须在程序中唯一一处定义

  • 避免全局变量滥用:过度使用extern变量可能导致代码耦合度高,建议优先使用命名空间或单例模式。

  • 类型一致性extern声明必须与定义处的类型严格匹配。


关键总结

场景不使用的后果extern的作用
跨文件共享全局变量链接错误(ODR违规)分离声明与定义,避免重复定义
跨文件共享常量内存冗余,修改不一致强制外部链接,统一内存实例
C/C++混合编程链接符号找不到禁用c++名称修饰,保持符号一致性
模板优化编译冗余,二进制膨胀抑制隐式实例化,提升编译效率
动态库接口符号不可见,链接失败控制符号可见性,明确导出/导入

核心原则extern通过 声明与定义的分离 和 链接性控制,解决跨编译单元、跨语言的协作问题,是C++模块化开发的关键工具,但需严格遵循语言规范以避免链接错误。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Redis中常见的延迟问题

使用复杂度高的命令 Redis提供了慢日志命令的统计功能 首先设置Redis的慢日志阈值&#xff0c;只有超过阈值的命令才会被记录&#xff0c;这里的单位是微妙&#xff0c;例如设置慢日志的阈值为5毫秒&#xff0c;同时设置只保留最近1000条慢日志记录&#xff1a; # 命令执行超过…

LangGraph实战:构建智能文本分析流水线

LangGraph实战:构建智能文本分析流水线 1. 智能文本分析 LangGraph是基于图结构的工作流开发框架,通过节点函数和条件流转实现复杂业务逻辑。四大核心能力: 1.1 状态容器 统一管理流程执行上下文,支持JSON序列化存储 1.2 智能路由 基于条件判断实现动态分支跳转 1.3 可…

【北京迅为】itop-3568 开发板openharmony鸿蒙烧写及测试-第1章 体验OpenHarmony—烧写镜像

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

MyBatis - 单元测试 参数传递 注解 CRUD

目录 1. MyBatis 简介 2. 简单使用 MyBatis 2.1 创建 MyBatis 项目 2.2 连接数据库 2.3 创建 Java 类 2.4 创建 Mapper 接口 2.5 在测试类中执行 3. 单元测试 3.1 Test 3.2 SpringBootTest 3.3 BeforeEach / AfterEach 4. MyBatis 基础操作 4.1 配置 MyBatis 打印日…

课程2. 机器学习方法论

课程2. 机器学习方法论 训练算法并评估其质量将样本分成训练和测试。分层 交叉验证方法sklearn 接口算法模型训练模型的应用质量评估 数据预处理标准缩放Violinplot 数据集使用模型Pipeline 在上一讲中&#xff0c;我们讨论了机器学习专家面临的挑战。无论解决的问题类型和解决…

CentOS 7使用RPM安装MySQL

MySQL是一个开源的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;允许用户高效地存储、管理和检索数据。它被广泛用于各种应用&#xff0c;从小型的web应用到大型企业解决方案。 MySQL提供了丰富的功能&#xff0c;包括支持多个存储引擎、事务能力、数据完整…

涂层,如同一道守护之光,有效遏制了QD(量子点)那如星辰般忽明忽暗的闪烁与如垂暮手电筒般黯淡无光的褪色现象。

涂层&#xff0c;如同一道守护之光&#xff0c;有效遏制了QD&#xff08;量子点&#xff09;那如星辰般忽明忽暗的闪烁与如垂暮手电筒般黯淡无光的褪色现象。俄克拉荷马大学&#xff08;University of Oklahoma&#xff09;的一项卓越研究&#xff0c;犹如破晓之光&#xff0c;…

C++第六节:stack和queue

本节目标&#xff1a; stack的介绍与使用queue的介绍与使用priority_queue的介绍与使用容器适配器模拟实现与结语 1 stack&#xff08;堆&#xff09;的介绍 stack是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&#xff0c;只能从容器的一端进行元素的插…

五分钟快速学习优秀网站的HTML骨架布局设计

一.编写多级过滤脚本&#xff0c;在控制台执行copy方法进行提取&#xff1a; 过滤脚本脚本 // 在浏览器F12的控制台里&#xff0c;直接执行以下脚本 copy(document.documentElement.outerHTML// 一级过滤&#xff1a;移除动态内容.replace(/<script\b[^>]*>[\s\S]*?…

硬件学习笔记--47 LDO相关基础知识介绍

目录 1.LDO主要功能介绍 2.LDO相关参数介绍 3.使用方法 4.优、缺点 1.LDO主要功能介绍 LDO&#xff08;Low Dropout Regulator&#xff09;是一种线性稳压器&#xff0c;用于将输入电压转换为稳定的输出电压。其主要功能包括&#xff1a; 1&#xff09;稳压功能&#xff1…

利用矩阵相乘手动实现卷积操作

卷积&#xff08;Convolution&#xff09; 是信号处理和图像处理中的一种重要操作&#xff0c;广泛应用于深度学习&#xff08;尤其是卷积神经网络&#xff0c;CNN&#xff09;中。它的核心思想是通过一个卷积核&#xff08;Kernel&#xff09; 或 滤波器&#xff08;Filter&am…

STM32-HAL库初始化时钟

使能和失能外设GPIOA 时钟信号初始化函数 HAL_RCC_OscConfig函数&#xff1a; HAL_StatusTypeDef是该函数的返回值类型,最顶上的那句话只是这个函数的原型 HAL_RCC_ClockConfig函数&#xff1a; 因为FLASH实际上只能支持24MHz的时钟信号所以如果用高于24MHz的信号输入则要用到等…

windows环境执行composer install出错

现在的项目环境都是要求比较新的版本&#xff0c;就比如今天部署测试一个新框架遇到了下面这些问题&#xff0c;报错原因有以下几点&#xff1a; PHP版本低了&#xff0c;现在的新项目都是要求PHP8以上版本&#xff1b;指令废弃&#xff0c;配置文件禁用即可&#xff1b;切换P…

Three.js 入门(光线投射实现3d场景交互事件)

本篇主要学习内容 : 光线投射器交互事件 点赞 关注 收藏 学会了 1.光线投射器 Raycaster 此类旨在协助光线投射。光线投射用于鼠标拾取&#xff08;确定鼠标在 3D 空间中的哪些对象上&#xff09;等。 Raycaster( origin : Vector3, direction : Vector3, near : Float,…

蓝桥杯web第三天

展开扇子题目&#xff0c; #box:hover #item1 { transform:rotate(-60deg); } 当悬浮在父盒子&#xff0c;子元素旋转 webkit display: -webkit-box&#xff1a;将元素设置为弹性伸缩盒子模型。-webkit-box-orient: vertical&#xff1a;设置伸缩盒子的子元素排列方…

Unity 使用NGUI制作无限滑动列表

原理&#xff1a; 复用几个子物体&#xff0c;通过子物体的循环移动实现&#xff0c;如下图 在第一个子物体滑动到超出一定数值时&#xff0c;使其放到最下方 --------------------------------------------------------------》 然后不停的循环往复&#xff0c;向下滑动也是这…

网络安全蜜罐产品研究现状

&#x1f345; 点击文末小卡片 &#xff0c;免费获取网络安全全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、知识点总结 1、蜜罐&#xff08;Honeypot&#xff09;&#xff1a;诱捕攻击者的一个陷阱。 2、蜜网&#xff08;Honeynet&#xff09;&#xff1a;采用了技术…

SpringBoot3—场景整合:环境准备

一、云服务器 阿里云服务器开通安装以下组件 dockerrediskafkaprometheusgrafana 下载windterm&#xff1a;https://github.com/kingToolbox/WindTerm/releases/download/2.5.0/WindTerm_2.5.0_Windows_Portable_x86_64.zip 重要&#xff1a;开通云服务器以后&#xff0c;请一…

Ollama进行DeepSeek本地部署存在安全风险解决方案,nginx反向代理配置

文章目录 概要整体架构流程技术细节**## 1.下载nginx [https://nginx.org/en/download.html](https://nginx.org/en/download.html),推荐Stable version稳定版**2.下载完成解压文件,打开conf文件夹下的nginx.conf,贴上反向代理配置3.然后点击解压文件夹下的nginx.exe,启动成…

【音视频】ffmpeg音视频处理基本流程

一、ffmpeg音视频处理基本流程 首先先看两条命令 ffmpeg -i 1.mp4 -acodec copy -vcodec libx264 -s 1280x720 2.flv ffmpeg -i 1.mp4 -acodec copy -vcodec libx265 -s 1280x720 3.mkv-i :表示输入源&#xff0c;这里是1.mp4&#xff0c;是当前路径下的视频文件-acodec copy…