本转帖是对上一篇C语言inline的详细讲解的补充. 

原文链接: http://wangcong.org/blog/archives/2021

GCC inline : http://gcc.gnu.org/onlinedocs/gcc/Inline.html

(本文是《C语言编程艺术》的一部分,转载请注明出处,勿用于商业用途。)

大家一定对C语言 inline 关键字不陌生,甚至经常用到 static inline 的函数。可能感到陌生的是 extern inline。C11 标准在6.7.4 p7 中对此进行了描述,不过比较生涩难读。简单地讲,static line 和 extern inline 的区别,从名字上也能看得出来,就是编译单元之外可见性的问题。把一个普通函数给内联掉之后,一个显著的区别就是外部可见性没了,怎么能同时既能内联又能保留外部的可见性呢?为了解决这个问题就有了extern inline。

static inline 是说这个函数就这么一个,而且没有外部可见性,在所有的编译单元中大家必须共用这么一个定义,这也是为什么 static inline 通常要放到头文件中的原因;而 extern inline 就不一样了,它是说这个函数虽然只有一个定义,但是既有内联的版本,也有非内联的版本,如果你能看得到它的定义。即在同一个编译单元中,那么你就可以用它的内联版本;看不到的话,你就可以用非内联版本,即和其它普通函数一模一样。

而如果既不带 static 也不带 extern 的话,含义又不同了:前面的 extern inline 只需要定义一次,只是不同的地方看到的版本不同,有的地方看到的是内联的版本,而有的地方看到的只是个 external reference。而仅用 inline 的话,就变成了要定义两次,带 inline 关键字的这个定义就是内联版本,在这个编译单元中都用这个版本,而在外部,还是使用普通的非内联定义。换句话说,带 inline 的定义遮盖住了外部的定义。既然有两个不同的版本,那么也就需要保持这两个版本的定义相同,否则就会有问题。

以上是标准C的定义,而 GNU89 的定义就不同了,基本上是把"extern inline"和"inline"的含义给交换了,“static line” 的含义都是相同的。看下面的标准C的例子:

PLAIN TEXT
C:

  1. //a.c
  2. //C99
  3. extern inline int max(int a, int b)
  4. {
  5.     return a> b ? a : b;
  6. }
  7.  
  8. int a = 10;
  9. int b = 20;
  10. int foo(void)
  11. {
  12.     return max(a, b);
  13. }
  14.  
  15. extern int bar(void);
  16.  
  17. int main(void)
  18. {
  19.     return foo() + bar();
  20. }
  21.  
  22. //b.c
  23. //C99
  24. extern int max(int a, int b);
  25.  
  26. int bar(void)
  27. {
  28.     int a = 10;
  29.     int b = 20;
  30.     return max(a, b);
  31. }

我们这么编译:
% gcc -std=c99 -O2 -Wall -W a.c b.c -o c

而我们如果把它编译成 GNU89 的话,就会报错:
% gcc -std=gnu89 -O2 -Wall -W a.c b.c -o c
/tmp/ccAJTzwY.o: In function `bar':
b.c:(.text+0xb): undefined reference to `max'
collect2: ld returned 1 exit status

很明显,因为 GNU89 中的 extern inline 需要两个定义,明显我们少了一个。修改后的代码如下:

PLAIN TEXT
C:

  1. //a.c
  2. //GNU89
  3. extern inline int max(int a, int b)
  4. {
  5.     return a> b ? a : b;
  6. }
  7.  
  8. int a = 10;
  9. int b = 20;
  10. int foo(void)
  11. {
  12.     return max(a, b);
  13. }
  14.  
  15. extern int bar(void);
  16.  
  17. int main(void)
  18. {
  19.     return foo() + bar();
  20. }
  21.  
  22. // b.c
  23. //GNU89
  24. int max(int a, int b)
  25. {
  26.     return a> b ? a : b;
  27. }
  28.  
  29. int bar(void)
  30. {
  31.     int a = 10;
  32.     int b = 20;
  33.     return max(a, b);
  34. }

glibc 中就用到了 GNU89 的extern inline 特性,在 ctype.h 中,tolower()/toupper() 的定义是:

PLAIN TEXT
C:

  1. # ifdef __USE_EXTERN_INLINES
  2. __extern_inline int
  3. __NTH (tolower (int __c))
  4. {
  5.   return __c>= -128 && __c <256 ? (*__ctype_tolower_loc ())[__c] : __c;
  6. }
  7.  
  8. __extern_inline int
  9. __NTH (toupper (int __c))
  10. {
  11.   return __c>= -128 && __c <256 ? (*__ctype_toupper_loc ())[__c] : __c;
  12. }
  13. # endif

而在 ctype/ctype.c 中却是:

PLAIN TEXT
C:

  1. #define __ctype_tolower \
  2.   ((int32_t *) _NL_CURRENT (LC_CTYPE, _NL_CTYPE_TOLOWER) + 128)
  3. #define __ctype_toupper \
  4.   ((int32_t *) _NL_CURRENT (LC_CTYPE, _NL_CTYPE_TOUPPER) + 128)
  5.  
  6. int
  7. tolower (int c)
  8. {
  9.   return c>= -128 && c <256 ? __ctype_tolower[c] : c;
  10. }
  11.  
  12. int
  13. toupper (int c)
  14. {
  15.   return c>= -128 && c <256 ? __ctype_toupper[c] : c;
  16. }

顺便说一句,gcc 提供了-fgnu89-inline 和 -fno-gnu89-inline 选项可在编译时控制上述 inline 的行为。

Stackoverflow 上有人做了一个很好的总结,我翻译了一下:

GNU89:

"inline": 函数可能会被内联掉,非内联的版本总是会生成,而且外部可见,因此内联的定义在本编译单元中只能有一次,其它地方看到的是非内联版本。

"static inline":不会生成外部可见的非内联版本,可能会生成一个 static 的非内联版本。它当然可以被定义多次,因为外部看不到它。

"extern inline":不会生成一个非内联的版本,但是可以调用一个非内联版本(因此你必须在其它编译单元中定义它)。只能有一个定义的规则当然也适用,非内联版本和内联版本的代码必须是一样的。

C99 (or GNU99):

"inline":和 GNU89 的 "extern inline" 一样,没有外部可见的函数生成,但是外部可见的函数必须存在,因为有可能会用到它。

"extern inline":和 GNU89 的 "inline" 一样, 会生成外部可见的代码,最多一个编译单元可以使用它。

"static inline":和 GNU89 的 "static inline" 一样,这是唯一一个在 GNU89 和 C99之间可移植的。

最后,我们可以看大神 Linus Torvalds 如何描述 extern inline:

"static inline" means "we have to have this function, if you use it, but don't inline it, then make a static version of it in this compilation unit". "extern inline" means "I actually _have_ an extern for this function, but if you want to inline it, here's the inline-version".

参考资料:

http://www.greenend.org.uk/rjk/tech/inline.html

http://gcc.gnu.org/ml/gcc/2006-11/msg00006.html

http://stackoverflow.com/questions/6312597/is-inline-without-static-or-extern-ever-useful-in-c99

http://www.cnblogs.com/cnmaizi/archive/2011/01/19/1939686.html

关于 extern inline相关推荐

  1. C/C++中inline/static inline/extern inline的区别及使用

    引入内联函数的目的是为了解决程序中函数调用的效率问题,也是用内联函数取代带参宏定义(函数传参比宏更加方便易用) inline关键字用来定义一个类的内联函数. 在类体中和类体外定义成员函数是有区别的:在 ...

  2. c语言函数声明中,static inline和extern inline的区别

    c语言函数声明中,static inline和extern inline的区别 "extern"关键字对于普通函数没有作用 "extern"和"sta ...

  3. 语言基础(9):static, extern 和 inline

    C++关键字 static.extern.inline 单独的含义和用法都不难,但组合起来的含义常常比较含糊,本文主要分析 static inline 和 extern inline.引用 Linux ...

  4. c语言inline详解

    本文介绍了GCC和C99标准中inline使用上的不同之处.inline属性在使用的时候,要注意以下两点:inline关键字在GCC参考文档中仅有对其使用在函数定义(Definition)上的描述,而 ...

  5. C语言中内联函数的作用 inline

    C语言中内联函数的作用 inline C语言中内联函数到底有什么作用? 试想一下,每当我们在假设就在主函数中调用另外一个函数的时候,那么这个函数就要入栈或者出栈,比如说下面的一个例子: 点击(此处)折 ...

  6. C语言的inline

    2019独角兽企业重金招聘Python工程师标准>>> C语言的inline 转以前我用Docbook写的一篇关于C语言inline关键字使用的文章.唉,要是能用docbook直接写 ...

  7. 容易被误解的inline

    C++中的inline是个容易被误解的关键字,即使是专家也常会犯错. 不信?请打开<Thinking in C++ 2rd Edition Volumn  1>,找到Chapter 9  ...

  8. C语言inline详细讲解

    本文介绍了GCC和C99标准中inline使用上的不同之处.inline属性在使用的时候,要注意以下两点:inline关键字在GCC参考文档中仅有对其使用在函数定义(Definition)上的描述,而 ...

  9. C++ inline weak symbol and so on

    关于inline这个关键字,听到强调得最多的是,它只是一种对于编译器的建议,而非强制执行的限定. 但事实上,即使这个优化最终由于函数太过复杂的原因没有达成,加上inline关键字(还有在类定义中直接定 ...

  10. c语言inline有什么作用,C语言inline的用法

    inline这个关键词,是从C99开始出现的.它要解决的问题很明确,对于那些短小精干频繁调用的函数,如果是inline的,编译的时候,函数调用位置会被替换成函数的代码块,省掉了函数调用的压栈出栈等操作 ...

最新文章

  1. c++和python先学哪个?
  2. c++读取文件–结束条件的判断
  3. [react-router] React-Router怎么获取URL的参数?
  4. 重学java基础第二十二课:IDEA安装
  5. Android SDK 2.2 开发环境安装
  6. SQLite中利用事务处理优化DB操作
  7. 《Linux内核原理与设计》第十一周作业 ShellShock攻击实验
  8. 关于JVM内存的N个问题
  9. Python爬取王者荣耀皮肤
  10. Mysql报too many connections详解
  11. 使用Golang实现的快速排序
  12. 简易JTAG线缆原理
  13. mybatis注解开发-动态SQL
  14. 简易计算器--带小数点--单片机课程设计
  15. 电力电子仿真软件Plecs数据类型
  16. SAI颈部正面的画法
  17. 【安卓开发】开源Notepad记事本APP项目(完整代码+说明文档)
  18. 智能世界的罗马是怎样建成的?
  19. light-bot小游戏
  20. html画流程图插件,基于SVG的流程图插件Flowchart.js

热门文章

  1. 计算机基础知识试题和答案6,计算机基础知识试题及答案选择题(九)
  2. http抓包实践--(五)-常用的操作
  3. js基础-20-js对象赋值时的key值问题
  4. dd 转换和拷贝复制文件
  5. matlab实现2dpsk调制与解调,(完整版)matlab设计2DPSK信号调制与解调
  6. python运算符手写笔记_Python基础学习笔记之运算符
  7. python显示文字框_python如何使用文本框
  8. vue element ui_vue+element-ui实现表格里嵌套表格
  9. 内屏损坏强制usb调试_反渗透膜工作压力及调试
  10. 煤矿调度计算机使用管理制度,煤矿调度文档管理制度(参考).doc