跳过正文

C++ inline & static

·2 分钟
作者
Nanmonk

inline
#

inline cppreference

内联展开
#

以下面这段代码为例:

const char* num_check(int v) {
    return (v % 2 > 0) ? "奇" : "偶";
}

int main() {
    for (int i = 0; i <= 99; i++)
        printf("%02d %s\n", i, num_check(i));
}

未开启优化时编译,可以看到 num_check 是正常的函数调用(call num_check(int)):

.LC0:
        .string "\345\245\207"
.LC1:
        .string "\345\201\266"
num_check(int):
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     edx, DWORD PTR [rbp-4]
        mov     eax, edx
        sar     eax, 31
        shr     eax, 31
        add     edx, eax
        and     edx, 1
        sub     edx, eax
        mov     eax, edx
        test    eax, eax
        jle     .L2
        mov     eax, OFFSET FLAT:.LC0
        jmp     .L4
.L2:
        mov     eax, OFFSET FLAT:.LC1
.L4:
        pop     rbp
        ret
.LC2:
        .string "%02d %s\n"
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 0
        jmp     .L6
.L7:
        mov     eax, DWORD PTR [rbp-4]
        mov     edi, eax
        call    num_check(int)
        mov     rdx, rax
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC2
        mov     eax, 0
        call    printf
        add     DWORD PTR [rbp-4], 1
.L6:
        cmp     DWORD PTR [rbp-4], 99
        jle     .L7
        mov     eax, 0
        leave
        ret

开启 -O1 后编译器会自动将函数内联展开,消除函数调用开销:

如果需要在 -O0 下(例如调试构建中的热路径)也强制内联,可以使用 [[gnu::always_inline]]__attribute__((always_inline))

[[gnu::always_inline]] inline const char* num_check(int v) {
    return (v % 2 > 0) ? "奇" : "偶";
}
笔记

通常情况下让编译器决定(开 -O1 及以上)是更好的选择。always_inline 适用于必须控制内联行为的特殊场景,滥用可能导致代码体积膨胀。

内联 const 变量默认拥有外部链接
#

普通的 const 变量在命名空间作用域下默认是内部链接(相当于隐式 static),而加了 inline 之后则拥有外部链接,可以在多个翻译单元中共享同一份定义:

// constants.h
inline const int kMaxSize = 1024; // 外部链接,多个 .cpp 包含也不会重复定义
const int kOther = 42;            // 内部链接,每个 .cpp 各有一份副本

这让头文件中定义全局常量成为可能,而不需要 extern 声明 + .cpp 定义的分离写法。

类成员函数与模板函数默认 inline
#

  • 类中直接定义的成员函数默认是 inline 的(即允许多重定义)
struct S {
    int get() const { return 42; } // 默认 inline
};
  • 模板函数直接定义时也是默认 inline
template<typename T>
T square(T x) { return x * x; } // 默认 inline

这也是为什么模板定义通常放在头文件里——多个翻译单元包含同一份定义不会触发 ODR 违规。

static
#

static 修饰命名空间作用域的函数或变量时,让其仅在当前翻译单元(Translation Unit)可见,即内部链接。

// a.cpp
static void foo() {}  // 仅 a.cpp 可见

// b.cpp
static void foo() {}  // 与 a.cpp 的 foo 互不干扰,不违反 ODR

这与 inline 的"允许多重定义"不同:static 是"每个翻译单元各有一份独立定义",inline 是"多个翻译单元共享同一份定义"。

ODR cppreference

头文件保护
#

笔记

头文件可以使用 #pragma once 代替传统的 include guard,虽然被大多数主流编译器支持,但它不属于 C++ 标准,移植性上需留意。