gcc内置函数

来源:互联网 发布:英翻汉软件 编辑:程序博客网 时间:2024/05/19 22:24

一 概要

  • 首先内置函数并不是c标准定义的,而是由编译器自定义的。gcc实现了一套自己的内置函数:

https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#Other-Builtins

其中需要引起注意的一段话是:

GCC built-in functions are always expanded inline and thus do not have corresponding entry points and their address cannot be obtained. Attempting to use them in an expression other than a function call results in a compile-time error

可见所有的内置函数都是inline的。

  • 内置函数分为两类:
一类与标准c库函数对应,例如strcpy()有对应的__builtin_strcpy()内建函数;另一类是对库函数的拓展。
  • 内置函数名称:
通常以__builtin_作为函数名前缀,老的版本还可能以__sync作为前缀。
  • 内置函数使用方法:
不需要函数声明,也不需要头文件包含,直接调用即可。

  • 内置函数开关

-fbuiltin:

这是默认选项,用于通过名字来识别内建函数

-fno-builtin:

除非利用前缀 __builtin_ 进行引用,否则不识别所有内建函数。例如,为了获得内建版本,应该调用 __builtin_strcpy() 而不是名为 strcpy() 的函数。

-fno-builtin-xxx(例如:-fno-builtin-fgets):

想使用除fgets() 之外的所有内建函数


二 实现原理

  • 内置函数是如何实现的呢?

这个问题看一下gcc的源代码就知道了,或者干脆看一下patch文件:

https://gcc.gnu.org/ml/gcc-patches/2009-06/msg00419.html

打上patch后重新编译gcc,通过这个过程不难发现,其实gcc内置函数跟关键字很相似,在调用内置函数处根据对应的上下文产生不同的代码。

  • 内置函数是如何链接的?
通过上面的分析可知,内置函数都是inline的,在内置函数调用处产生相应代码,所以内置函数不存在链接信息。

  • 内置函数都可以通过库函数去实现么?
不可否认部分内置函数可以自己用函数去实现,但并不是所有的都可以,因为很多内置函数函数会影响gcc生成代码。


三 常用built-in函数介绍

  • __builtin_constant_p(x)
检查是否为编译器常量,也即在编译时其值是否可以确定, 如果值在compile time能够确定,则返回1,否则返回0。
  • __builtin_expect (long exp, long c)
阅读过linux内核代码的人应该对此比较熟悉:

# define likely(x) __builtin_expect(!!(x), 1)
# define unlikely(x) __builtin_expect(!!(x), 0)

likely和unlikely在内核代码中被大量使用,用来引导gcc进行条件分支预测。在一条指令执行时,由于流水线的作用,CPU可以完成下一条指令的取指,这样可以提高CPU的利用率。在执行一条条件分支指令时,CPU也会预取下一条执行,但是如果条件分支跳转到了其他指令,那CPU预取的下一条指令就没用了,这样就降低了流水线的效率。__builtin_expect(!!(x), 1)返回值!!(x),并使指令尽量顺序执行。

  • __builtin_return_address(n)
当前函数的第n级调用者的地址,用的最多的就是__builtin_return_address(0),即获得当前函数的调用者的地址。注意,该函数实现是体系结构相关的,有些体系结构只实现了n=0的返回结果。

  • __builtin_object_size(const void * ptr, int type)
返回ptr所指向的内存区域的大小,type影响整个函数的行为,最低两位:

type is an integer constant from 0 to 3. If the least significant bit is clear, objects are whole variables, if it is set, a closest surrounding subobject is considered the object a pointer points to. The second bit determines if maximum or minimum of remaining bytes is computed.

最低位为0,返回整个object的大小,为1,返回subobject的大小,下面例子能很好的说明:

struct V { char buf1[10]; int b; char buf2[10]; } var;char *p = &var.buf1[1], *q = &var.b;/* Here the object p points to is var.  */assert (__builtin_object_size (p, 0) == sizeof (var) - 1);/* The subobject p points to is var.buf1.  */assert (__builtin_object_size (p, 1) == sizeof (var.buf1) - 1)
如果ptr能够同时指向多个对象,并且都在编译器可确定,则第二位为1返回最大的object大小,为0返回最小的object大小。

但是如果ptr指向的对象在编译期是不确定的,则:

If there are any side-effects in them, it returns (size_t) -1 for type 0 or 1 and (size_t) 0 for type 2 or 3.

关于__builtin_object_size()函数的应用,下面的例子:

#undef memcpy#define bos0(dest) __builtin_object_size (dest, 0)#define memcpy(dest, src, n) \  __builtin___memcpy_chk (dest, src, n, bos0 (dest))char *volatile p;char buf[10];/* It is unknown what object p points to, so this is optimized   into plain memcpy - no checking is possible.  */memcpy (p, "abcde", n);/* Destination is known and length too.  It is known at compile   time there will be no overflow.  */memcpy (&buf[5], "abcde", 5);/* Destination is known, but the length is not known at compile time.   This will result in __memcpy_chk call that can check for overflow   at run time.  */memcpy (&buf[5], "abcde", n);/* Destination is known and it is known at compile time there will   be overflow.  There will be a warning and __memcpy_chk call that   will abort the program at run time.  */memcpy (&buf[6], "abcde", 5);

另外,开源软件中比较常用的应用:-D_FORTIFY_SOURCE=1执行编译期内存越界检查,不影响程序的行为,-D_FORTIFY_SOURCE=2执行更严格的内存检查,可能会导致编译或运行错误。


四 优缺点

优点:方便编程,优化程序运行效率,增强安全检查;

缺点:程序移植性差,gcc之外的编译器可能编译不过,低版本的gcc同样可能出现编译错误。

原创粉丝点击