汉扬编程 编程大纲 C 语言中的冷门知识点:extern inline 函数

C 语言中的冷门知识点:extern inline 函数

gcc 的编译原则讲这个知识点之前,我们先回顾下编译器的原则。编译器工作时,以 .c 文件为单位逐个编译 .o 文件,每个 .c 文件的编译是独立的,如果 当前 .c 文件中要用到外部函数,那么就在编译时预留一个符号。等到所有 .o 文件生成后,链接时才给这些符号指定地址(链接脚本决定地址),所以这个 . c 文件编译时只会看到外部函数的声明而无法知道它的函数体。而内联函数声明时,加关键字 inline 修饰。调用到它的地方直接将汇编代码展开,而不需要通过符号(函数名)地址跳转。

extern inline 函数gcc 的 inline、static inline 都很好理解:就是对普通函数添加了可内联的属性,每个函数还是有自己的汇编代码段,只是其他函数调用内联函数时,不是通过符号地址跳转,而是直接展开内联函数代码段。

但是 extern inline 就千万不能想当然地理解成就是一个 extern 的函数 + inline属性了。实际上 gcc的 extern inline 行为十分古怪:

1) 一个 extern inline 的函数只会被内联进去,而绝对不会生成独立的汇编代码段!即使是通过指针应用或者是递归调用也不会让编译器为它生成汇编代码,在这种时候对此函数的调用会被处理成一个外部引用。

2)另外,extern inline 的函数允许和外部函数重名,即在存在一个外部定义的全局库函数的情况下,再定义一个同名的 extern inline 函数也是合法的。我们来看下面一个例子:

// file1.c了如下函数:extern inline int foo(int a) { return -a;};void func(){ int a = foo(10); // void *p = foo; // ② int b = p(20); // ③}首先,这个文件内,gcc 不会生成 foo 函数的汇编码。

其次,在 func 中的调用点 ①,编译器会将上面定义的 foo 函数内联展开编译,其表现类似于普通inline函数。因为这样的调用是能够进行内联处理的。

而在 ② 处,引用了 foo 函数的地址。但是!编译器是绝对不会为 extern inline 函数生成独立汇编码的!所以在这种非要个函数地址不可的情况下,编译器不得不将其处理为外部引用,在链接的时候链接到外部的 foo 函数去(填写外部函数的地址)。这时如果外部没有再定义全局的 foo 函数,就会在链接时产生 foo 函数未定义的错误。

此时,假如在另一个文件里面也定义了一个全局函数 foo:

// file2.c 里定义了如下函数:int foo (int a){ return a;}那么上述例子中,后面一个对 foo 函数地址的引用就会在链接时被指到这个 file2.c 中定义的 foo 函数去。也就是说:① 调用foo函数的结果是a = -10,因为其内联了 file1.c 内的 foo 函数;而③调用的结果则是 b = 10,因为其实际上调用的是 file2.c 里面的 foo 函数!

extern inline 函数的价值extern inline 的用法奇怪且少见,但是还是有其实用价值的。我们来看下面的例子。

在一个库函数的c文件内,定义一个普通版本的库函数 libfunc:

// lib.cvoid libfunc(){ /* … */}然后再在其头文件内,定义(注意不是声明!)一个实现相同的exterin inline的版本:

// lib.hextern inline libfunc(){ /* … */}如果在别的文件要使用函数 libfunc 时,只要 include 了 lib.h,在能内联展开的地方,编译器都会使用头文件内extern inline的版本来展开。而在无法展开的时候(函数指针引用等情况),编译器就会引用lib.c中的那个独立编译的普通版本。即看起来似乎是个可以在外部被内联的函数一样,所以这应该是 gcc 的 extern inline 意义的由来。 但是注意这样的使用是有代价的:c 文件中的全局函数的实现必须和头文件内 extern inline 版本的实现完全相同。否则就会出现前面所举例子中直接内联和间接调用时函数表现不一致的问题。

总结gcc 的 extern inline 函数的用法相当奇怪,使用的范围也非常狭窄:几乎没有什么情况会需要用它。 c99中也没有关于 extern inline 这样的描述,所以不建议大家使用 extern inline,除非你明确理解了这种用法的意义并且有充足的理由使用它!

之所以会讲这个知识点,是因为 intel 的 SSE、AVX 等指令集中的函数(在头文件 emmintrin.h 中)都定义成了 extern inline 形式,如果你想取这些函数的地址,那么,不好意思,你将会看到“符号未定义”的报错,然后就掉坑里了,却还不知道怎么回事。

本文来自网络,不代表汉扬编程立场,转载请注明出处:http://www.hyzlch.com/mianfei/6571.html

C语言__FILE__、__LINE__等预定义跟踪调试

C语言中选择合适数据类型的一些经验准则

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

返回顶部