inline关键字仅仅是对编译器的建议,编译器有权力决定一个函数是否在调用处嵌入。因为内联函数要在调用处展开,编译器必须能在每一个调用处能看到该函数的定义,因此最好将函数实现放在头文件中(而且实现在类定义中的成员函数即便不加inline关键字也会自动成为内联函数)。在实现文件中该函数之前要加上inline关键字的方式是有问题的:如果调用的obj文件在函数定义之前生成,那么该处就无法嵌入内联函数了。如果普通函数需要成为内联函数,在定义时加上inline关键字。

  1. 包含了递归、循环等结构的函数一般不会被内联。
  2. 虚拟函数一般不会内联,但是如果编译器能在编译时确定具体的调用函数,那么仍然会就地展开该函数。
  3. 如果通过函数指针调用内联函数,那么该函数将不会内联而是通过call进行调用。
  4. 构造和析构函数一般会生成大量代码,因此一般也不适合内联。
  5. 如果内联函数调用了其他函数也不会被内联。

如果想要阻止某函数被内联,可以在函数体前加上 __attribute__((noinline)) 。

 
 

有时函数是否原地展开与编译时指定的优先级有关,下面就是一个例子:

inline int foo(int x) {

return x+42;

}

 
 

int main(int argc, char* argv[])

{

printf("%d\n", foo(42));

 

return 0;

}

 
 

使用如下命令行得到汇编代码,g++ -S -Wall -DDEBUG -D_GNU_SOURCE -std=c++11 -I/usr/include test.cpp:

 
 

.section        .text._Z3fooi,"axG",@progbits,_Z3fooi,comdat

.weak        _Z3fooi

.type        _Z3fooi, @function

_Z3fooi:

//rbp寄存器的值入栈,保存main函数的栈基地址

pushq        %rbp

//将rsp寄存器的值写入到rbp寄存器,将main函数的栈顶指针赋予foo函数的栈底指针

movq        %rsp, %rbp

//此时edi寄存器中的值是42,放入foo栈底向上4byte处

movl        %edi, -4(%rbp)

//将42放入eax寄存器

movl        -4(%rbp), %eax

//立即数21与eax寄存器中的值(42)相加

addl        $21, %eax

//恢复main函数的栈底

popq        %rbp

//返回

ret

.size        _Z3fooi, .-_Z3fooi

.section        .rodata

.LC0:        

.string        "%d\n"

.text

.globl        main

.type        main, @function

main:

//rbp寄存器的值入栈,保存_start函数的栈基地址

pushq        %rbp

//将rsp寄存器的值写入到rbp寄存器,将_start函数的栈顶指针赋予main函数的栈底指针

movq        %rsp, %rbp

//rsp寄存器的值减去立即数16,即main函数的栈大小为16

subq        $16, %rsp

//将edi寄存器中的值放入rbp寄存器中的值减4的位置(栈底向上4byte)

movl        %edi, -4(%rbp)

//将rsi寄存器中的值放入rbp寄存器中的值减16的位置(栈底向上16byte)

movq        %rsi, -16(%rbp)

//立即数42放入edi寄存器

movl        $42, %edi

//调用函数foo

call        _Z3fooi

//eax寄存器的值(63)放入esi寄存器

movl        %eax, %esi

//LC0段地址放入edi寄存器

movl        $.LC0, %edi

movl        $0, %eax

call        printf

movl        $0, %eax

leave

ret

.size        main, .-main

 
 

使用如下命令行生成汇编代码:g++ -S -O -Wall -DDEBUG -D_GNU_SOURCE -std=c++11 -I/usr/include test.cpp(增加了一个优化选项)

.section        .rodata.str1.1,"aMS",@progbits,1

.LC0:

.string        "%d\n"

.globl        main

.type        main, @function

main:

subq        $8, %rsp

movl        $63, %esi

movl        $.LC0, %edi

call        printf

movl        $0, %eax

addq        $8, %rsp

ret

.size        main, .-main

可见foo函数并未被调用,而是直接将立即数63放入esi寄存器作为printf函数的参数。

C++中哪些函数不能声明为inline?的更多相关文章

  1. C++中哪些函数不能声明为virtual?

    首先要明确,virtual是用于支持类多态的关键字,所以出现在类声明之外的地方都是错误的.由此可以断定下文的1. 普通函数(即非类成员函数)不能是virtual的,否则不能通过编译,virtual只能 ...

  2. C++ DLL中导出函数的声明的方法

    定义: TESTDLLEXPORT_API int fnTestDllExport(void); TESTDLLEXPORT_API int fnTestCall(void); TESTDLLEXPO ...

  3. 在C++中调用DLL中的函数 (3)

    1.dll的优点 代码复用是提高软件开发效率的重要途径.一般而言,只要某部分代码具有通用性,就可将它构造成相对独立的功能模块并在之后的项目中重复使用.比较常见的例子是各种应用程序框架,ATL.MFC等 ...

  4. 《APUE》中的函数整理

    第1章 unix基础知识 1. char *strerror(int errnum) 该函数将errnum(就是errno值)映射为一个出错信息字符串,返回该字符串指针.声明在string.h文件中. ...

  5. DLL中导出函数的两种方式(dllexport与.def文件)

    DLL中导出函数的声明有两种方式: 一种方式是:在函数声明中加上__declspec(dllexport): 另外一种方式是:采用模块定义(.def)文件声明,(.def)文件为链接器提供了有关被链接 ...

  6. 在C++中调用DLL中的函数(3)

    1.dll的优点 代码复用是提高软件开发效率的重要途径.一般而言,只要某部分代码具有通用性,就可将它构造成相对独立的功能模块并在之后的项目中重复使用.比较常见的例子是各种应用程序框架,ATL.MFC等 ...

  7. (转)DLL中导出函数的两种方式(dllexport与.def文件)

    DLL中导出函数的两种方式(dllexport与.def文件)http://www.cnblogs.com/enterBeijingThreetimes/archive/2010/08/04/1792 ...

  8. 【转】DLL中导出函数的两种方式(dllexport与.def文件)

    DLL中导出函数的两种方式(dllexport与.def文件) DLL中导出函数的声明有两种方式: 一种方式是:在函数声明中加上__declspec(dllexport):另外一种方式是:采用模块定义 ...

  9. 为什么模板函数的声明和实现都放在.h文件中

    当你不使用这个模板函数或模板类,编译器并不实例化它,当你使用时,编译器需要实例化它,因为编译器是一次只能处理一个编译单元,也就是一次处理一个cpp文件,所以实例化时需要看到该模板的完整定义.所以都放在 ...

随机推荐

  1. 【学习笔记】第八章 python3核心技术与实践--条件与循环

    [第七章]思考题答案,仅供参考:

  2. 使用Bookinfo应用测试Kuma服务网格

    最近,开源API管理平台Kong服务供应商近日放出了新的开源项目Kuma.本文尝试将 bookinfo 应用部署在 Kuma 网格中,以便帮助大家更好的理解 Kuma 项目.   Kuma是能用于管理 ...

  3. 数据库高级:SQL-CREATE-TABLE语句

    作者:松软科技(www.sysoft.net.cn) 发布时间:2019/3/17 9:34:51 CREATE TABLE 语句 CREATE TABLE 语句用于创建数据库中的表. SQL CRE ...

  4. 同步FIFO design and IP level verification

    一.前言 应聘IC前端相关岗位时,FIFO是最常考也是最基本的题目.FIFO经常用于数据缓存.位宽转换.异步时钟域处理.随着芯片规模的快速增长,灵活的system verilog成为设计/验证人员的基 ...

  5. 跟我学SpringCloud | 第十九章:Spring Cloud 组件 Docker 化

    前面的文章<跟我学SpringCloud | 第十八篇:微服务 Docker 化之基础环境>我们介绍了基础环境系统和 JRE 的容器化,这一节我们介绍 Spring Cloud 组件的容器 ...

  6. Java 集合转换(数组、List、Set、Map相互转换)

    package com.example.test; import java.util.ArrayList; import java.util.Arrays; import java.util.Hash ...

  7. Cannot find class: com.mysql.jdbc.Driver错误及解决办法。

    在刚刚开始搭建Mybatis源码解析,一步一步从浅入深 简单示例的时候,我使用的是mysql 5.1.12版本的驱动包.运行时出现如下错误: Cause: java.sql.SQLException: ...

  8. js之捕捉冒泡和事件委托

     以下为转载内容 一.事件流(捕获,冒泡)   事件流:指从页面中接收事件的顺序,有冒泡流和捕获流. 当页面中发生某种事件(比如鼠标点击,鼠标滑过等)时,毫无疑问子元素和父元素都会接收到该事件,可具体 ...

  9. vuex(vue状态管理)

    vuex(vue状态管理) 1.先安装vuex npm install vuex --save   2.在项目的src目录下创建store目录,并且新建index.js文件,然后创建vuex实例,引入 ...

  10. vue-router路由元信息及keep-alive组件级缓存

    路由元信息?(黑人问号脸???)是不是这么官方的解释很多人都会一脸懵?那么我们说meta,是不是很多人恍然大悟,因为在项目中用到或者看到过呢? 是的,路由元信息就是我们定义路由时配置的meta字段:那 ...