C++的内联函数

目录

前言

内联函数

为什么声明和定义分离

为什么声明和定义分离后不出错 

为什么内联函数不支持声明和定义分离 

为什么内联函数支持声明和定义不分离

坚持声明和定义不分离的解决方法

static修饰函数

inline修饰函数

结论

声明和定义不分离的应用场景


前言

        在C语言中可能会遇到频繁调用100次函数的情况,这就意味这需要频繁的创建和销毁栈帧一百次,无疑这会增加程序运行时的开销,对此C语言使用了宏来解决这一问题。但是宏是一个很容易犯错的概念,其中主要有以下几个方面:

  • 宏不是函数,宏函数不需要参数参数
#define ADD(a,b) ((a)+(b))  //right

#define ADD(int a,int b) ((a)+(b)) //wrong
  • 宏定义的结尾不需要分号
#define ADD(a,b) ((a)+(b))  //right

#define ADD(a,b) ((a)+(b));//wrong
  • 要用括号控制优先级(运算符的优先级问题)
#define ADD(a,b) ((a)+(b))  //right

//不加括号导致出错:
ADD(x | y,x & y);  ——> (x | y + x & y),原本想要计算x|y与x&y之和,但是由于+的优先级高所以就变成了计算x | (y + x) & y

此外,宏也存在一些缺点:

  • 语法复杂,坑很多,不容易控制(上面说过了)
  • 不能调试
  • 没有类型检查

关于宏的具体内容请查看:C语言关于预处理命令的理解

内联函数

问题描述:由于宏在处理频繁调用函数的问题中可能会出现各种各样的问题,C++摒弃了宏而采用inline关键字来解决该问题

内联展开:当使用 inline 关键字修饰函数时,编译器会尝试在每个调用该函数的地方直接将函数的代码插入到调用位置,而不是像普通函数那样通过跳转到另一个内存地址执行完后再返回

展开详解:当编译器遇到一个被声明为 inline 的函数并进行调用时,在生成最终的可执行程序时,并不会像普通函数一样创建一个新的栈帧、跳转到对应代码段执行完后再返回。相反,编译器会尽可能地将整个被标记为 inline 的函数体复制粘贴至每次调用该 inline 函数处。

优点:有助于减少因频繁调用小型简单操作而产生的额外开销(如参数传递、栈帧创建等),从而提高程序运行效率

无inline关键字修饰函数:

#include <iostream>
using namespace std;

int Add(int a, int b)
{
	return a + b;
}

int main()
{
	int ret1 = Add(1, 2) * 3;

	int x = 1, y = 2;
	int ret2 = Add(x | y, x & y);
	return 0;
}

有inline关键字修饰函数:

#include <iostream>
using namespace std;

inline int Add(int a, int b)
{
	return a + b;
}

int main()
{
	int ret1 = Add(1, 2) * 3;

	int x = 1, y = 2;
	int ret2 = Add(x | y, x & y);
	return 0;
}

1和2分别放入寄存器,接着将寄存器会将 eax 中的值与 3 相乘,并将结果放回到 eax 寄存器

注意事项:

1、并非所有被声明为 inline 的函数都一定会被内联展开,在实际情况中编译器可能基于复杂性和其他因素做出选择性忽略某些内联请求:

  • 在release模式下,可以直接查看编译器生成的汇编代码中是否存在call Add
  • 在debug模式下,需要对编译器进行设置否则不会展开(debug模式下,编译器默认不会对代码进行优化)

2、内联是一种以空间(编译好的可执行程序的大小)换时间的做法,如果所有函数都是内联函数,这可能会使目标文件变大

        对于一个100行且需要调用1w次的func函数,如果采用内联的方法,可执行程序的代码增100*1w行,最终的可执行程序代码量剧增,代码的编译速度降低影响用户体验,如果不采用内联的办法,每次调用时都会跳转到该段代码运行,这就导致只有一份 func 函数代码存在于程序中,可执行程序实际上只会增加一百行的代码:

3、inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议是将函数规模较小(即函数不是很大)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性,《C++prime》第五版关于inline的建议如下:

4、内联 不建议声明和定义分离,否则链接错误(展开后没有函数地址 ,链接会找不到)

为什么声明和定义分离

        我们现在有一个Stack.h文件和Test.cpp文件和一个空的Stack.cpp文件,当我们直接在Stack.h中声明和定义一个函数,程序可以正常运行:

Stack.h文件

#pragma once
#include <iostream>
using namespace std;

int Add(int a, int b)
{
	cout << "int Add(int a,int b)" << endl;
	return a + b;
}

Test.cpp文件

#include "Stack.h"

int main()
{
	Add(1, 2);
	return 0;
}

但是当我们在空的Stack.cpp文件中也包含Stack.h文件,就会出现一个重定义的链接错误:

Stack.cpp文件

#include "Stack.h"

        这是因为Stack.cpp和Test.cpp两个文件都包含了Add函数,且在编译的阶段检查时不满足函数重载的条件(两个文件中的Add函数的返回值类型、参数类型、顺序一致),当两个.cpp文件经过编译、链接和汇编形成的.o文件进行链接时,链接器就会检测出函数重定义的错误

#pragma once解决的问题是一个源文件同时包含多个一样的头文件

#include "Stack.h"
#include "Stack.h" //wrong

int main()
{
    int i = 0;
    return 0;
}

结论:如果将函数或变量的实现直接写在头文件中(声明和定义不分离),并且多个源文件包含了该头文件,在链接时就会出现多重定义错误

为什么声明和定义分离后不出错 

        采用函数声明与定义分离,即Stack.h文件中只有Add函数的声明,Stack.cpp文件中只有函数的定义,Test.cpp文件中只有调用函数,当程序进行编译时,.h文件会分别在两个.cpp文件中展开,Stack.cpp文件中既有函数的声明和定义,并且Add的函数名会被符号表统计,Test.cpp文件中只有函数的声明和调用,Test.cpp在链接前调用函数会生成类似call Add(?)的汇编指令(Test.cpp文件中没有函数的定义),所以链接器在链接阶段会去符号表依据函数名寻找该函数的地址,当找到该函数的地址后会将两个文件进行链接,此时汇编指令就变成了call Add(函数地址)

  1. 编译阶段

    • 头文件(Stack.h)被包含到两个源文件(Stack.cpp 和 Test.cpp)中
    • Stack.cpp 中包含了 Add 函数的实现部分
    • Test.cpp 中只包含了对 Add 函数的调用
  2. 链接阶段

    • 在编译过程中生成目标代码时,在 Test.cpp 文件里对 Add 函数进行调用时会生成类似 call Add(?) 的汇编指令。因为此时并没有找到该函数具体实现所在地址,所以使用一个占位符“?”来表示需要填充该地址。
  3. 符号解析和链接

    • 链接器会根据符号表查找具体实现位置,并将所有目标代码整合成可执行程序。
    • 链接器遇到 call Add(?) 这样的指令时,在符号表找到相应函数名,并替换为正确地址后完成最终连接

为什么内联函数不支持声明和定义分离 

原因:被inline修饰的函数在编译阶段不会被统计进符号表中(前提是明确规定该函数可以展开)

函数声明和定义分离:      

        如果将inline修饰的函数放入Stack.cpp文件中且包含.h文件,Stack.h文件中只有函数的声明,Test.cpp文件中只有函数的调用且包含.h文件,在编译后会出现如下情况:

为什么内联函数支持声明和定义不分离

原因:被inline修饰的函数在编译阶段不会被统计进符号表中(前提是明确规定该函数可以展开)

函数声明和定义未分离:        
        如果将inline修饰的函数放入Stack.h文件中,Stack.cpp文件只有包含.h文件的#include "Stack.h"一行代码,Test.cpp文件中有函数的调用且包含.h文件,在编译后会出现如下情况:

坚持声明和定义不分离的解决方法

static修饰函数

基本概念:使用static修饰的函数,则该函数只能被当前文件访问,无法被其他文件调用

底层原理:编译阶段被static修饰的函数不会被统计进符号表中(其它的用不到)

#pragma once
#include <iostream>
using namespace std;

static int Add(int a, int b)
{
	cout << "int Add(int a,int b)" << endl;
	return a + b;
}

        当Stack.cpp文件只有#include ”Stack.h“,Test.cpp文件有函数调用和#include ”Stack.h“时,在编译后会产生如下情况:

inline修饰函数

不做解释

结论

  • 要么在写文件的时候直接选择函数声明和定义分离的方式
  • 如果实在想要用函数声明和定义不分离,那么大函数用static修饰,小函数用inline修饰

必须声明和定义不分离的应用场景

有时候声明和定义必须不分离的情况可能包括以下几点:

  1. 模板函数或类对于模板函数或类,通常需要将其声明和定义放在同一个文件中。因为编译器需要在实例化时能够看到完整的函数或类定义,以便生成相应的代码

  2. inline 函数:为处理频繁调用小型函数而导致的开销问题,而采用inline修饰函数时

  3. 静态成员变量:对于某些静态成员变量,在 C++ 中如果希望确保只有一个实例存在,则通常会将其声明和定义结合起来,并且还可能需要特殊处理以确保正确初始化

  4. 特定平台下链接问题:一些特定平台或库要求某些符号必须在同一源文件中进行声明和定义,并且不能被其他模块引用。这种情况下也需要将声明与实现结合起来

~over~

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

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

相关文章

Compiling from source on UNIX(cmake doxygen ant maven ccache)

前言 源码链接 cmake-3.18.0 https://cmake.org/files/v3.18/cmake-3.18.0.tar.gzdoxygen-1.10.0 https://www.doxygen.nl/files/doxygen-1.10.0.src.tar.gzapache-ant-1.10.8-bin https://archive.apache.org/dist/ant/binaries/apache-ant-1.10.8-bin.tar.gzapache-maven-3…

tomcat部署和优化(二)----- 轻松搭建博客、状态页优化、虚拟主机配置

一、tomcat 1、自建博客 [rootzzzcentos1 ~]#systemctl stop firewalld [rootzzzcentos1 ~]#setenforce 0 [rootzzzcentos1 ~]#cd /data/ [rootzzzcentos1 data]#rz -E rz waiting to receive. [rootzzzcentos1 data]#ls apache-tomcat-9.0.16 apache-tomcat-9.0…

数据挖掘:航空公司的客户价值分析

需求分析 理解并掌握聚类分析方法&#xff0c;掌握数据的标准化&#xff0c;掌握寻找最佳聚类数&#xff0c;掌握聚类的绘图&#xff0c;掌握聚类分析的应用场景。 系统实现 实验流程分析 借助航空公司数据&#xff0c;对客户进行分类对不同类别的客户进行特征分析&#xf…

对称加密与非对称加密

1、对称加密 对称加密&#xff0c;即采用对称的密码编码技术&#xff0c;他的特点是&#xff0c;加密和解密使用相同的秘钥。 常见的对称加密算法有DES、3DES、Blowfish、IDEA、RC4、RC5、RC6和AES。 优点&#xff1a;对称加密算法使用起来简单快捷&#xff0c;密钥较短&…

【Python】进阶学习:pandas--如何根据指定条件筛选数据

【Python】进阶学习&#xff1a;pandas–如何根据指定条件筛选数据 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448; 希望…

亿道信息轻工业三防EM-T195,零售、制造、仓储一网打尽

厚度仅10.5mm&#xff0c;重量仅0.65千克的EM-T195&#xff0c;其紧凑而纤薄的设计为以往加固型平板带来了全新的轻薄概念。尽管设计时尚、轻薄&#xff0c;但经过军用认证的强固性仍然能够承受所有具有挑战性的环境条件。随身携带无负担的轻便性加上抗震功能使其成为餐厅、酒店…

数据结构——Top-k问题

Top-k问题 方法一&#xff1a;堆排序&#xff08;升序&#xff09;&#xff08;时间复杂度O(N*logN)&#xff09;向上调整建堆&#xff08;时间复杂度&#xff1a;O(N * logN) &#xff09;向下调整建堆&#xff08;时间复杂度&#xff1a;O(N) &#xff09;堆排序代码 方法二&…

Linux信号【systemV】

目录 前言 正文&#xff1a; 1消息队列 1.1什么是消息队列&#xff1f; 1.2消息队列的数据结构 1.3消息队列的相关接口 1.3.1创建 1.3.2释放 1.3.3发送 1.3.4接收 1.4消息队列补充 2.信号量 2.1什么是信号量 2.2互斥相关概念 2.3信号量的数据结构 2.4…

【JSON2WEB】07 Amis可视化设计器CRUD增删改查

总算到重点中的核心内容&#xff0c;CRUD也就是增删改查&#xff0c;一个设计科学合理的管理信息系统&#xff0c;95%的就是CRUD&#xff0c;达不到这个比例要重新考虑一下你的数据库设计了。 1 新增页面 Step 1 启动amis-editor Setp 2 新增页面 名称和路径随便命名&#xf…

【谈一谈】我们所用的三种工厂模式优缺点

【谈一谈】我们所用的三种工厂模式优缺点 Hello!!大家好啊,好久也没有进行文章的更新了,原因嘛,最近的工作任务量有点大,导致摸鱼充电的时间大量减少,哈哈哈(你别说,这是借口嘛!) 不过,今天是星期六,难的能够在这里分享下最近在工作中,我用到的三种工厂模式(简工抽),有啥区别呢…

在线开源免费问卷调查系统

在线开源免费问卷调查系统 平台简介 本项目旨在提供一个简单易用的问卷调查平台&#xff0c;帮助用户创建、分享问卷&#xff0c;并收集、分析调查数据。我们希望能够为各行各业的调查需求提供一种高效、便捷的解决方案。 项目特点 用户友好&#xff1a;清晰直观的用户界面…

QT6 libModbus 用于ModbusTcp客户端读写服务端

虽然在以前的文章中多次描述过,那么本文使用开源库libModbus,可得到更好的性能&#xff0c;也可移植到各种平台。 性能&#xff1a;读1次和写1次约各用时2ms。 分别创建了读和写各1个连接指针&#xff0c;用于读100个寄存器和写100个寄存器&#xff0c;读写分离。 客户端&am…

5、DVWA代码审计(2)

一、csrf 1、csrf(low) 限制 复现 GET /vulnerabilities/csrf/?password_new123456&password_conf123456&ChangeChange HTTP/1.1 Host: ddd.com Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,…

手撸AI-3: Accelerate库分布式训练详解

一. 引言 Accelerate 是 Hugging Face 公司开发的一个 Python 库&#xff0c;旨在简化并优化在各种环境中进行深度学习训练的过程&#xff0c;包括单机、多 GPU、TPU 和各种分布式训练环境。这个库提供了一种通用的 API&#xff0c;可以方便地将原来只能在单个设备上运行的代码…

chromedriver,Chrome驱动的实时更新

发现自己的selenium项目跑不起来了 效验驱动版本 下载链接(可能需要魔法) https://registry.npmmirror.com/binary.html?pathchromedriver/ https://googlechromelabs.github.io/chrome-for-testing/ 找到驱动位置 1. 默认安装路径&#xff1a;Chrome驱动通常会默认安装在系…

智能驾驶规划控制理论学习02-基于搜索的路径规划方法

目录 一、路径搜索问题 二、图论基础 三、图搜索方法 1、广度优先搜索&#xff08;BFS&#xff09; bfs与dfs的区别 bfs的搜索过程 bfs的算法实现 2、迪杰斯特拉算法&#xff08;Dijkstra&#xff09; 核心思想 优先级队列 Dijkstra搜索过程 Dijkstra优缺点…

微服务day03-Nacos配置管理与Nacos集群搭建

一.Nacos配置管理 Nacos不仅可以作为注册中心&#xff0c;可以进行配置管理 1.1 统一配置管理 统一配置管理可以实现配置的热更新&#xff08;即不用重启当服务发生变更时也可以直接更新&#xff09; dataId格式&#xff1a;服务名-环境名.yaml&#xff0c;分组一般使用默认…

【比较mybatis、lazy、sqltoy、mybatis-flex操作数据】操作批量新增、分页查询(二)

orm框架使用性能比较 环境&#xff1a; idea jdk17 spring boot 3.0.7 mysql 8.0比较mybatis、lazy、sqltoy、mybatis-flex操作数据 测试条件常规对象 orm 框架是否支持xml是否支持 Lambda对比版本mybatis☑️☑️3.5.4sqltoy☑️☑️5.2.98lazy✖️☑️1.2.4-JDK17-SNAPS…

2024最新算法:鹦鹉优化算法(Parrot optimizer,PO)求解23个基准函数(提供MATLAB代码)

一、鹦鹉优化算法 鹦鹉优化算法&#xff08;Parrot optimizer&#xff0c;PO&#xff09;由Junbo Lian等人于2024年提出的一种高效的元启发式算法&#xff0c;该算法从驯养的鹦鹉中观察到的觅食、停留、交流和对陌生人行为的恐惧中汲取灵感。这些行为被封装在四个不同的公式中…

leetcode:37.解数独

题目理解&#xff1a;本题中棋盘的每一个位置都要放一个数字&#xff08;而N皇后是一行只放一个皇后&#xff09;&#xff0c;并检查数字是否合法&#xff0c;解数独的树形结构要比N皇后更宽更深。 代码实现&#xff1a;