GCC和C99 中的inline

内联函数是代码被插入到调用者代码处的函数。如同 #define 宏(但并不等同,原因见下文),内联函数通过避免被调用的开销来提高执行效率,尤其是它能够通过调用(“过程化集成”)被编译器优化。

    gcc对C语言的inline做了自己的扩展,其行为与C99标准中的inline有较大的不同。

1.1. static inline
    GCC的static inline定义很容易理解:你可以把它认为是一个static的函数,加上了inline的属性。这个函数大部分表现和普通的static函数一样,只不过在调用这种函数的时候,gcc会在其调用处将其汇编码展开编译而不为这个函数生成独立的汇编码。除了以下几种情况外:
    函数的地址被使用的时候。如通过函数指针对函数进行了间接调用。这种情况下就不得不为static inline函数生成独立的汇编码,否则它没有自己的地址。
    其他一些无法展开的情况,比如函数本身有递归调用自身的行为等。
    static inline函数和static函数一样,其定义的范围是local的,即可以在程序内有多个同名的定义(只要不位于同一个文件内即可)。
    注意:gcc的static inline的表现行为和C99标准的static inline是一致的。所以这种定义可以放心使用而没有兼容性问题。
    要点:gcc的static inline相对于static函数来说只是在调用时建议编译器进行内联展开;gcc不会特意为static inline函数生成独立的汇编码,除非出现了必须生成不可的情况(如通过函数指针调用和递归调用)
gcc的static inline函数仅能作用于文件范围内
1.2. inline
    相对于C99的inline来说,GCC的inline更容易理解:可以认为它是一个普通全局函数加上了inline的属性。即在其定义所在文件内,它的表现和static inline一致:在能展开的时候会被内联展开编译。但是为了能够在文件外调用它,gcc一定会为它生成一份独立的汇编码,以便在外部进行调用。即从文件外部看来,它和一个普通的extern的函数无异。举个例子:
        foo.c:
       
        inline foo() {
                          ...;   <- 编译器会像非inline函数一样为foo()生成独立的汇编码
        }
        void func1() {
                         foo(); <- 同文件内foo()可能被编译器内联展开编译而不是直接call上面生成的汇编码
        }
    而在另一个文件里调用foo()的时候,则直接call的是上面文件内生成的汇编码:
        bar.c:
            extern foo(); <- 声明foo(),注意不能在声明内带inline关键字
            void func2() {
                             foo();    <- 这里就是直接call在foo.c内为foo()函数生成的汇编码了
                          } 
    虽然gcc的inline函数的行为很好理解,但是它和C99的inline是有很大差别的。

    要点: 
    gcc的inline函数相对于普通extern函数来说只是在同一个文件内调用时建议编译器进行内联展开; 
    gcc一定会为inline函数生成一份独立的汇编码,以便其在本文件之外被调用。在别的文件内看来,这个inline函数和普通的extern函数无异;
    c的inline函数是全局性的:在文件内可以作为一个内联函数被内联展开,而在文件外可以调用它

1.3. extern inline
    GCC的static inline和inline都很好理解:看起来都像是对普通函数添加了可内联的属性。但是这个extern inline就千万不能想当然地理解成就是一个extern的函数+inline属性了。实际上gcc的extern inline十分古怪:一个extern inline的函数只会被内联进去,而绝对不会生成独立的汇编码!即使是通过指针应用或者是递归调用也不会让编译器为它生成汇编码,在这种时候对此函数的调用会被处理成一个外部引用。另外,extern inline的函数允许和外部函数重名,即在存在一个外部定义的全局库函数的情况下,再定义一个同名的extern inline函数也是合法的。以下用例子具体说明一下extern inline的特点:
        foo.c:
        extern inline
        int foo(int a)
        {
            return (-a);
        }
        void func1()
       {
           ...;
           a = foo(a);   ①
           p_foo = foo;  ②
           b = p_foo(b); ③
        }
    在这个文件内,gcc不会生成foo函数的汇编码。在func1中的调用点①,编译器会将上面定义的foo函数在这里内联展开编译,其表现类似于普通inline函数。因为这样的调用是能够进行内联处理的。而在②处,引用了foo函数的地址。但是注意:编译器是绝对不会为extern inline函数生成独立汇编码的!所以在这种非要个函数地址不可的情况下,编译器不得不将其处理为外部引用,在链接的时候链接到外部的foo函数去(填写外部函数的地址)。这时如果外部没有再定义全局的foo函数的话就会在链接时产生foo函数未定义的错误。
    假设在另一个文件里面也定义了一个全局函数foo:
        foo2.c:
        int foo(int a)
        {
            return (a);
        }
    那么在上面那个例子里面,后面一个对foo函数地址的引用就会在链接时被指到这个foo2.c中定义的foo函数去。也就是说:①调用foo函数的结果是a=-a,因为其内联了foo.c内的foo函数;而③调用的结果则是b=b,因为其实际上调用的是foo2.c里面的foo函数!
    extern inline的用法很奇怪也很少见,但是还是有其实用价值的。第一:它可以表现得像宏一样,可以在文件内用extern inline版本的定义取代外部定义的库函数(前提是文件内对其的调用不能出现无法内联的情况);第二:它可以让一个库函数在能够被内联的时候尽量被内联使用。举个例子:
    在一个库函数的c文件内,定义一个普通版本的库函数libfunc:
        lib.c:
        void libfunc()
        {
            ...;
        }
    然后再在其头文件内,定义(注意不是声明!)一个实现相同的exterin inline的版本:
        lib.h:
        extern inline libfunc()
       {
            ...;
       }
    那么在别的文件要使用这个库函数的时候,只要include了lib.h,在能内联展开的地方,编译器都会使用头文件内extern inline的版本来展开。而在无法展开的时候(函数指针引用等情况),编译器就会引用lib.c中的那个独立编译的普通版本。即看起来似乎是个可以在外部被内联的函数一样,所以这应该是gcc的extern inline意义的由来。 但是注意这样的使用是有代价的:c文件中的全局函数的实现必须和头文件内extern inline版本的实现完全相同。否则就会出现前面所举例子中直接内联和间接调用时函数表现不一致的问题。
    gcc的extern inline函数的用法相当奇怪,使用的范围也非常狭窄:几乎没有什么情况会需要用它。 C99中,也没有关于extern inline这样的描述,所以不建议大家使用extern inline,除非你明确理解了这种用法的意义并且有充足的理由使用它!
    要点:

    gcc绝对不会为extern inline的函数生成独立汇编码
    extern inline函数允许和全局函数重名,可以在文件范围内替代外部定义的全局函数
    extern inline函数的应用范围十分狭窄,而且行为比较奇怪,不建议使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值