当前位置:  开发笔记 > 编程语言 > 正文

我为什么要使用内联代码?

如何解决《我为什么要使用内联代码?》经验,为你挑选了4个好方法。

我是一名C/C++开发人员,这里有几个问题总让我感到困惑.

"常规"代码和内联代码之间有很大区别吗?

哪个是主要区别?

内联代码只是宏的"形式"吗?

在选择内联代码时必须做出哪些权衡?

谢谢



1> Konrad Rudol..:

性能

正如之前的答案中所建议的那样,使用inline关键字可以通过内联函数调用使代码更快,通常以增加可执行文件为代价."内联函数调用"只是意味着在相应地填充参数后,用函数的实际代码将调用替换为目标函数.

但是,当设置为高优化时,现代编译器非常擅长自动内联函数调用而无需用户提示.实际上,编译器通常擅长确定内联速度增益的调用.

inline为了性能增益明确声明函数(几乎?)总是不必要的!

此外,如果请求适合,编译器可以并且将 忽略inline请求.如果对函数的调用不可能内联(即使用非平凡的递归或函数指针),而且如果函数太大而无法获得有意义的性能增益,则编译器将执行此操作.

一个定义规则

但是,使用inline关键字声明内联函数会产生其他影响,实际上可能需要满足一个定义规则(ODR):C++标准中的此规则声明给定符号可以多次声明,但可能只定义一次.如果链接编辑器(=链接器)遇到几个相同的符号定义,则会生成错误.

这个问题的一个解决方案是通过声明它来确保编译单元不通过给出内部链接来导出给定符号static.

但是,标记函数通常更好inline.这告诉链接器将编译单元中此函数的所有定义合并为一个定义,一个地址和共享函数静态变量.

例如,请考虑以下程序:

// header.hpp
#ifndef HEADER_HPP
#define HEADER_HPP

#include 
#include 
#include 

using vec = std::vector;

/*inline*/ double mean(vec const& sample) {
    return std::accumulate(begin(sample), end(sample), 0.0) / sample.size();
}

#endif // !defined(HEADER_HPP)
// test.cpp
#include "header.hpp"

#include 
#include 

void print_mean(vec const& sample) {
    std::cout << "Sample with x? = " << mean(sample) << '\n';
}
// main.cpp
#include "header.hpp"

void print_mean(vec const&); // Forward declaration.

int main() {
    vec x{4, 3, 5, 4, 5, 5, 6, 3, 8, 6, 8, 3, 1, 7};
    print_mean(x);
}

请注意,这两个.cpp文件都包含头文件,因此包含函数定义mean.虽然文件是使用包含防止双重包含的保护来保存的,但这将导致相同函数的两个定义,尽管在不同的编译单元中.

现在,如果您尝试链接这两个编译单元 - 例如使用以下命令:

??? g++ -std=c++11 -pedantic main.cpp test.cpp

你会收到一个错误,说"重复符号__Z4meanRKNSt3__16vectorIdNS_9allocatorIdEEEE"(这是我们函数的错误名称mean).

但是,如果取消注释inline函数定义前面的修饰符,则代码会正确编译和链接.

函数模板是一种特殊情况:它们始终是内联的,无论它们是否以这种方式声明.这并不意味着编译器会内联调用它们,但它们不会违反ODR.对于在类或结构中定义的成员函数也是如此.


Dang,我一直在寻找像这样的修复...我有一个功能定义+实现的标题,但没有内联.虽然有标题保护,但仍有多个定义...终于找到修复:)

2> Luc Touraill..:

"常规"代码和内联代码之间有很大区别吗?

是的,不是.不,因为内联函数或方法具有与常规函数或方法完全相同的特性,最重要的一个是它们都是类型安全的.是的,因为编译器生成的汇编代码会有所不同; 使用常规函数,每个调用将被转换为几个步骤:在堆栈上推送参数,跳转到函数,弹出参数等,而对内联函数的调用将被其实际代码替换,如宏.

内联代码只是宏的"形式"吗?

!宏是简单的文本替换,可能导致严重的错误.请考虑以下代码:

#define unsafe(i) ( (i) >= 0 ? (i) : -(i) )

[...]
unsafe(x++); // x is incremented twice!
unsafe(f()); // f() is called twice!
[...]

使用内联函数,您可以确保在实际执行函数之前评估参数.它们也将被类型检查,并最终转换为匹配形式参数类型.

在选择内联代码时必须做出哪些权衡?

通常,使用内联函数时程序执行应该更快,但使用更大的二进制代码.有关更多信息,请阅读GoTW#33.


值得一提的是,程序员只能提示编译器使用内联代码是由编译器实际做出的选择(即使在类中定义了方法).只有在分析表明这样做的优势时,编译器才会内联

3> Sander..:

内联代码本质上就像宏一样,但它是真正的实际代码,可以进行优化.非常小的函数通常适用于内联,因为与该方法所做的少量实际工作相比,设置函数调用所需的工作(将参数加载到适当的寄存器中)是昂贵的.使用内联,不需要设置函数调用,因为代码直接"粘贴"到任何使用它的方法中.

内联增加了代码大小,这是它的主要缺点.如果代码太大而无法容纳到CPU缓存中,则可能会出现严重的减速.在极少数情况下,您只需要担心这种情况,因为在很多地方您不太可能使用方法,增加的代码会导致问题.

总而言之,内联对于加速多次调用的小方法是理想的,但在很多地方却没有(不过100个地方仍然很好 - 你需要进入极端的例子来获得任何重要的代码膨胀).

编辑:正如其他人所指出的,内联只是对编译器的建议.它可以自由地忽略你,如果它认为你正在制作愚蠢的请求,如内联一个巨大的25行方法.



4> Adam Davis..:

"常规"代码和内联代码之间有很大区别吗?

是 - 内联代码不涉及函数调用,并将寄存器变量保存到堆栈.它每次被"调用"时都会使用程序空间.总的来说,执行起来需要的时间更少,因为处理器中没有分支,节省了状态,清除了缓存等.

内联代码只是宏的"形式"吗?

宏和内联代码共享相似之处.最大的区别在于内联代码是专门格式化的函数,因此编译器和未来的维护者有更多的选择.具体来说,如果你告诉编译器优化代码空间,或者未来的维护者最终扩展它并在代码中的许多地方使用它,它很容易变成一个函数.

在选择内联代码时必须做出哪些权衡?

宏:代码空间使用率高,执行速度快,如果'功能'很长,很难维护

功能:代码空间使用率低,执行速度慢,易于维护

内联函数:高代码空间使用,快速执行,易于维护

应该注意的是,保存和跳转到函数的寄存器确实占用了代码空间,因此对于非常小的函数,内联可以占用比函数更少的空间.

-亚当

推荐阅读
Chloemw
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有