C++存储链接性原理详解

目录
  • 链接性
    • 外部链接性
    • 单定义规则
    • 内部链接性
    • 无链接性
  • 总结

链接性

链接性是指名称在不同文件之间能否共享,而作用域是指名称在文件内部哪些范围可见。

这里的文件并非开发时创建的文件,而是将文件的 include 内容全部递归包含进来之后,形成的大文件。

这一点也是与Go等一些语言不同。在Go语言中,文件是按包(package)组织,所有依赖的包都需要直接或间接import进来。也就是从main开始递归import得到的就是我们依赖的所有文件。

但C++不同。C++源文件主要分.h 和 .cpp两种,一般我们都只会include .h文件,而不会include .cpp文件。因此,.cpp文件之间其实没有直接关联,需要通过cmakelist等方式告诉编译器,我们的程序涉及到哪些源文件。

而C++在编译时,会首先将include的文件内容全部递归包含进来,形成一个大的文件,这个大文件是一个编译单元,也就是上面链接性所说的文件。其实,C++的宏定义的可使用范围,也是在这个大文件内。

链接性有三种:

  • 外部链接性:一个文件声明的名称可以在另一个文件中使用
  • 内部链接性:声明的名称只能在文件内使用
  • 无链接性:意味着只能在函数或代码块内使用

自动类型变量都没有链接性,而静态类型变量可以有三种链接性。那么如何定义这三种链接性的静态变量呢?

外部链接性

链接性为外部的变量也叫外部变量,也称全局变量。外部变量在函数外声明,不加static关键字。外部变量可以在所有文件使用。

对于函数来说,没有加inline和static关键字的函数,都具有外部链接性。

说到外部,可能会想到一个关键字:extern。这个关键字有什么用?实际上它是用来做引用声明。因为如果想使用其他文件中定义的外部变量,不能直接使用,而是要先进行引用声明,表示要引用这个外部变量,这里就需要用到关键字extern。例如:

// file1.cpp
int foo = 1;

// file2.cpp
extern int foo;
// extern int foo = 1; WRONG
cout << foo;

file1.cpp 在函数之外定义了全局变量foo,在file2.cpp中,用extern关键字声明之后,即可使用foo了。注意,extern语句中不能初始化foo,否则这里就变成定义而不是引用声明,导致重复定义全局变量foo,编译错误。

单定义规则

对于外部变量,每个使用它的文件都必须声明它。而C++又有“单定义规则”,即链接性为外部的函数和变量可以有多个声明,但只能有一个定义。这里再明确下这两个术语:“定义声明”,简称“定义”;“引用声明”,简称“声明”。

为了实现单定义规则,编译器要知道这一行代码是在声明还是在定义,那怎么区分声明还是定义呢?

对于函数来说,区分声明和定义很简单,有函数体则是定义,否则为声明。而变量则不同,前面所谓的变量声明,对于编译器来说都是定义,都分配了存储空间。如何声明一个变量而不分配存储空间呢?关键字extern就派上用场了,使用extern关键字且没有进行初始化,则为声明,不会分配存储空间,否则为定义。

C++初学者可能还不太明白为什么C++中都要把函数声明放在.h文件,把函数定义放在.cpp文件中。其实用单定义规则就很好解释了。函数如果没有加inline和static,即具有外部链接性,如果把定义放在.h文件中,这个.h文件会被多个.cpp文件引用,编译时会形成多个副本,相当于被定义了多次。

总之,.h文件中只能放声明,或者没有外部链接性的定义。

可能有小伙伴发现,有些定义在函数外的静态变量没有加static,也会放在.h文件中,为什么可以呢?其实const变量默认会添加static,链接性变为内部。而如果想要声明为外部变量,则需要加上external:

const int foo = 10;  // 链接性为内部
extern const int bar = 10; // 链接性为外部

内部链接性

前面说了,定义在函数外部的变量默认是全局变量,具有外部链接性。但如果加上static说明符,则变成内部链接性。对于函数也一样,加上static则变为内部链接性。

在函数外定义的变量,static的含义与局部变量中static的含义不同。前者表示链接性为内部,后者表示存储持续性为静态。这也可以称为关键字重载,即关键字在不同上下文中有不同含义。

具有内部链接性的变量或函数,可以在不同文件中有多个定义。内部链接性的变量也可以与同名外部变量同时存在,这时内部变量将隐藏外部变量。对函数来说也一样。

无链接性

定义在函数或代码块内的局部变量没有链接性,只能在局部使用。如果加上static修饰则为静态变量,虽然在程序运行期间会一直存在,但只有在代码块内才能使用。

总结

下面总结一下前面提到的不同存储持续性与链接性的变量。

  • 自动存储持续性,无链接性

在代码块内定义

  • 静态存储持续性,无链接性

在代码块内定义,用static关键字

  • 静态存储持续性,内部链接性

在代码块外定义,用static关键字 或const修饰

  • 静态存储持续性,外部链接性

在代码块外定义。引用声明则需要用extern

以上就是C++存储链接性原理示例详解的详细内容,更多关于C++存储链接性原理的资料请关注我们其它相关文章!

(0)

相关推荐

  • C++存储持续性生命周期原理解析

    目录 存储持续性(生命周期) C++中的存储持续性有4类 自动存储持续性 静态存储持续性 动态存储持续性 存储持续性(生命周期) 课堂上都讲过,变量有生命周期和作用域,类似的在C++中也有存储持续性与连接性的概念.什么是自动变量,什么是静态变量,什么是全局变量?本文就来解答一下. 存储持续性说的就是数据在内存中保留的时间. 先来看看下面这段代码是否有问题? *int getInt() { int a = 1; return &a; } 我们知道函数调用和返回在内存中对应入栈出栈的过程.这个函数将

  • C++使用链表存储实现通讯录功能管理

    本文实例为大家分享了C++使用链表存储实现通讯录功能管理的具体代码,供大家参考,具体内容如下 简介 这是第二周老师给的一个小项目要求实现基本通讯录功能,有数据的增删改查,包含插入时间的能力. 代码详情 头文件 #include <iostream> #include <string> #include<malloc.h> //system功能调用  #include <windows.h> //使用本地系统API获取插入时间  #include <ss

  • C++ 自由存储区是否等价于堆你知道吗

    目录 free store" VS "heap" 问题的来源 结论 free store" VS "heap" 当我问你C++的内存布局时,你大概会回答: "在C++中,内存区分为5个区,分别是堆.栈.自由存储区.全局/静态存储区.常量存储区". 如果我接着问你自由存储区与堆有什么区别,你或许这样回答: "malloc在堆上分配的内存块,使用free释放内存,而new所申请的内存则是在自由存储区上,使用delete来

  • C++浮点型的存储方式详解

    目录 浮点型及其存储方式 一.IEEE浮点标准 二.存储方式 IEEE 754对有效数字M和指数E的规定. 重点: 根据指数域不同取值分为一下三种情况: 总结 浮点型及其存储方式 有些时候需要变量能存储带小数点的数,或者能存储极大数或极小数.这类数可以用浮点(因小数点是"浮动的"而得名)格式进行存储.C语言提供了3种浮点类型,对应三种不同的浮点格式. 当精度要求不严格时(小数点后少于六位),float类型是很适合的类型.double提供更高的精度, 对绝大多数程序来说够用了.longd

  • C++浅析数据在内存中如何存储

    目录 一.数据类型 二.原码反码补码 三.大小端 整型提升 一.数据类型 数据类型有7种: char            字符型     short          短整型    int               整型   long            长整型   long long    更长整型   float            单精度浮点数    double        双精度浮点数 二.原码反码补码 计算机中的整数有三种2进制表示方法,即原码.反码和补码. 三种表示方法均

  • C++存储链接性原理详解

    目录 链接性 外部链接性 单定义规则 内部链接性 无链接性 总结 链接性 链接性是指名称在不同文件之间能否共享,而作用域是指名称在文件内部哪些范围可见. 这里的文件并非开发时创建的文件,而是将文件的 include 内容全部递归包含进来之后,形成的大文件. 这一点也是与Go等一些语言不同.在Go语言中,文件是按包(package)组织,所有依赖的包都需要直接或间接import进来.也就是从main开始递归import得到的就是我们依赖的所有文件. 但C++不同.C++源文件主要分.h 和 .cp

  • c#中String类型的存储原理详解

    在我们正式了解c#中的String类型前,先来判断一下下面代码的结果吧~ String str1 = "123"; String str2 = str1; str2 = "321"; Console.WriteLine(str1); 上面代码的最终输出结果是123,如果有浅学过引用类型的同学一定会问:str2不是在存储的是str1的引用么?那么str2不是和str1指向堆中同一块内存空间么?为什么在引用了str2使其改变数据后再打印出str1最终还是打印出来123?

  • Python深度强化学习之DQN算法原理详解

    目录 1 DQN算法简介 2 DQN算法原理 2.1 经验回放 2.2 目标网络 3 DQN算法伪代码 DQN算法是DeepMind团队提出的一种深度强化学习算法,在许多电动游戏中达到人类玩家甚至超越人类玩家的水准,本文就带领大家了解一下这个算法,论文的链接见下方. 论文:Human-level control through deep reinforcement learning | Nature 代码:后续会将代码上传到Github上... 1 DQN算法简介 Q-learning算法采用一

  • Spring @Transactional工作原理详解

    本文将深入研究Spring的事务管理.主要介绍@Transactional在底层是如何工作的.之后的文章将介绍: propagation(事务传播)和isolation(隔离性)等属性的使用 事务使用的陷阱有哪些以及如何避免 JPA和事务管理 很重要的一点是JPA本身并不提供任何类型的声明式事务管理.如果在依赖注入容器之外使用JPA,事务处理必须由开发人员编程实现. UserTransaction utx = entityManager.getTransaction(); try{ utx.be

  • Web网络安全分析XSS漏洞原理详解

    目录 XSS基础 XSS漏洞介绍 XSS漏洞原理 反射型XSS 存储型XSS DOM型XSS XSS基础 XSS漏洞介绍 跨站脚本(Cross-Site Scripting,简称为XSS或跨站脚本或跨站脚本攻击)是一种针对网站应用程序的安全漏洞攻击技术,是代码注入的一种.它允许恶意用户将代码注入网页,其他用户在浏览网页时就会收到影响.恶意用户利用XSS代码攻击成功后,可能得到很高的权限(如执行一些操作).私密网页内容.会话和cookie等各种内容. XSS攻击可以分为三种:反射型.存储型和DOM

  • 计算机网络编程MQTT协议基础原理详解

    目录 什么是 MQTT 协议 MQTT 基础 发布 - 订阅模式 可拓展性 消息过滤 基于主题的过滤 基于内容的过滤 基于类型的过滤 MQTT 与消息队列的区别 MQTT 重要概念 MQTT client MQTT broker MQTT Connection 消息报文 CONNECT CONNACK 消息类型 发布 订阅 确认消息 退订 确认退订 聊聊 Topic 通配符 单级通配符 多级通配符 之前有位读者给我留言说想要了解一下什么是 MQTT 协议,顺便还把我夸了一把,有点不好意思啦. 那

  • MySql事务及ACID实现原理详解

    目录 逻辑架构和存储引擎 自动提交 特殊操作 ACID 特性 原子性 持久性 隔离性 脏读.不可重复读和幻读 事务隔离级别 MVCC 一致性 逻辑架构和存储引擎 自动提交 MySQL 中默认采用的是自动提交(autocommit)模式,如下所示: 在自动提交模式下,如果没有 start transaction 显式地开始一个事务,那么每个 sql 语句都会被当做一个事务执行提交操作. 通过如下方式,可以关闭 autocommit;需要注意的是,autocommit 参数是针对连接的,在一个连接中

  • mysql中的mvcc 原理详解

    目录 简介 前言 一.mysql 数据写入磁盘流程 二.redo log 1.redolog 的整体流程 2.为什么需要 redo log 三.undo log 1.undo log 特点 2.undo log 类型 3.undo log 生成过程 4.undo log 回滚过程 5.undo log的删除 四.mvcc 1.什么是MVCC 2.MVCC组成 3.快照读与当前读 快照读 当前读 五.mvcc操作演示 1.READ COMMITTED 隔离级别 2.REPEATABLE READ 

  • Apache Doris Join 优化原理详解

    目录 背景 & 目标 Doris 数据划分 Partition Bucket Join 方式 总览 Broadcast / Shuffle Join Bucket Shuffle Join Plan Rule Colocate Join Runtime Filter 优化 Join Reorder 优化 Join 调优建议 背景 & 目标 掌握 Apache Doris Join 优化手段及其实现原理 为代码阅读提供理论基础 Doris 数据划分 不同的 Join 方式非常依赖于对 Dor

  • 通过HashMap原理详解entrySet中的疑问

    目录 HashMap底层变量 put()方法: 2. get(Object key)方法: 3. remove(Object key)方法: 4.entrySet()方法: EntrySet类代码 HashMap底层变量 HashMap的底层的一些变量: transient Node<K,V>[] table; //存储数据的Node数组 transient Set<java.util.Map.Entry<K,V>> entrySet; transient int si

随机推荐