PA2.3必答-编译与链接-关于static与inline关键字

记录笔者在完成PA2.3必答题”去掉static,inline”时遇到的问题解答与思考.

本文章为一生一芯教学项目的个人理解,笔者并不能保证其正确,请注意学术诚信
笔者并非计算机科班,内容难免存在错误,如您发现,欢迎与我联系.

PA2 必答题:编译与链接
编译与链接 在nemu/include/cpu/ifetch.h中, 你会看到由static inline开头定义的inst_fetch()函数. 分别尝试去掉static, 去掉inline或去掉两者, 然后重新进行编译, 你可能会看到发生错误. 请分别解释为什么这些错误会发生/不发生? 你有办法证明你的想法吗?

现象

直接放结果:

  • 仅删去static,无事发生
  • 仅删去inline,无事发生
  • 删去static和inline,链接报错误multiple definition

这是为什么呢?

static和inline的作用

static关键字

  1. 对符号可见性的限制
    当用static修饰全局函数或全局变量时,其作用域被限制在当前编译单元(即当前源文件)。其他文件无法通过extern声明访问它。
  2. 对ELF符号表的影响
    在编译生成的ELF目标文件(.o)的符号表中:
  • static修饰的符号会被标记为LOCAL绑定类型。
  • 未用static修饰的全局符号会被标记为GLOBAL绑定类型。
    在链接时链接器仅处理GLOBAL符号,忽略LOCAL符号(视为各自文件的私有符号).
    具体来说,在链接时使用static修饰的符号为局部符号,在其他源文件中不可见,不检查多重定义,故多个源文件可定义同名的static变量.

inline关键字

GCC手册说:

By declaring a function inline, you can direct GCC to make calls to that function faster. One way GCC can achieve this is to integrate that function’s code into the code for its callers. This makes execution faster by eliminating the function-call overhead; in addition, if any of the actual argument values are constant, their known values may permit simplifications at compile time so that not all of the inline function’s code needs to be included. The effect on code size is less predictable; object code may be larger or smaller with function inlining, depending on the particular case.
通过内联声明函数,您可以指示 GCC 更快地调用该函数。GCC 实现此目的的一种方法是将该函数的代码集成到其调用者的代码中。这通过消除函数调用开销来加快执行速度;此外,如果任何实际参数值是常量,则它们的已知值可能允许在编译时进行简化,以便不需要包含内联函数的所有代码。对代码大小的影响更难预测;使用函数内联时,目标代码可能会更大或更小,具体取决于特定情况。

即通过inline关键字,GCC可以将函数内联进调用者的代码中.然而GCC手册又说:

When an inline function is not static, then the compiler must assume that there may be calls from other source files; since a global symbol can be defined only once in any program, the function must not be defined in the other source files, so the calls therein cannot be integrated. Therefore, a non-static inline function is always compiled on its own in the usual fashion.
当内联函数不是静态的时,编译器必须假定可能存在来自其他源文件的调用;由于一个全局符号在任何程序中只能定义一次,因此该函数不能在其他源文件中定义,因此其中的调用不能集成。因此,非静态内联函数总是以通常的方式自行编译。

当仅使用inline时,必须在某个源文件中使用extern inline显式提供该函数的外部定义,否则链接器无法找到符号.

If you specify both inline and extern in the function definition, then the definition is used only for inlining. In no case is the function compiled on its own, not even if you refer to its address explicitly. Such an address becomes an external reference, as if you had only declared the function, and had not defined it.
如果在函数定义中同时指定 inline 和 extern,则该定义仅用于内联。在任何情况下,函数都不会自行编译,即使您显式引用其地址也是如此。这样的地址成为外部引用,就好像您只声明了该函数而没有定义它一样。

我们有如下代码

1
2
// utils.h
inline void f(){ }
1
2
3
// file1.c
#include "utils.h"
void main(){ f() }

我们使用gcc file1.o -o out进行链接,出现错误 undefined reference to 'f',通过上文我们可以知道,我们在链接时使用了inline函数但却没有使用extern inline显式提供函数体,我们使用readelf看到f是UND,链接器报错.

utils.h中的inline改为static inline时,链接不报错.这是因为在此时我们使用了static inline,我们使用readelf看到f是LOCAL,其被认为是一个局部符号.

透过现象看本质

  • 当我们只保留static时,此时inst_fetch函数是静态函数,不内联,链接不会报错.
  • 当我们只保留inline时,此时可能所有调用被内联,链接有可能不报错.
    为了证明这点,我们在CFLAGS中加入-fno-inline强制禁止内联,出现错误undefined reference to 'inst_fetch',这是因为inline默认为局部符号,而在源文件不存在其定义.
    我们也可以通过elf的符号表证明这点.在没有-fno-inlinehostcall.oinst.o的符号表均不存在inst_fetch,即其被内联,而在有-fno-inline时两个文件inst_fetch符号均为UND.
  • 当static和inline都没有时,此时全局符号在头文件被定义,必然引起重复定义错误.

PA2.3必答-编译与链接-关于static与inline关键字
http://example.com/2025/02/27/PA2-3必答-编译与链接-关于static与inline关键字/
作者
lethe
发布于
2025年2月27日
许可协议