Clang 是一个 C++ 编写,基于 LLVM 的 C/C++、Objective-C 语言的轻量级编译器,在 2013.04 开始,已经全面支持 C++11 标准。
pragma
#pragma
宏定义在本质上是声明,常用的功能就是注释,尤其是给 Code 分段注释;另外,还支持处理编译器警告。
#pragma clang diagnostic push
//----- 方法弃用告警
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
//----- 不兼容指针类型
#pragma clang diagnostic ignored "-Wincompatible-pointer-types"
//----- 未使用变量
#pragma clang diagnostic ignored "-Wunused-variable"
//----- 无返回值
#pragma clang diagnostic ignored "-Wmissing-noreturn"
//... ...
#pragma clang diagnostic pop
attribute
gcc 会通过 __attribute__((XXX))
做一些特殊的检查,这里简单介绍一些常见的使用方法。
format
__attribute__((format))
该属性用于自实现的字符串格式化参数添加类似 printf()
的格式化参数的校验,判断需要格式化的参数与入参是否相同。
format (archetype, string-index, first-to-check)
__attribute__((format(printf,m,n)))
__attribute__((format(scanf,m,n)))
m : 第m个参数为格式化字符串(从1开始);
n : 变长参数(也即"...")的第一个参数排在总参数的第几个;
如下是使用示例。
void myprint(const char *format,...) __attribute__((format(printf,1,2)));
void myprint(int l,const char *format,...) __attribute__((format(printf,2,3)));
如下是一个简单的使用示例。
#include <stdio.h>
extern void myprint(const char *format,...) __attribute__((format(printf,1,2)));
int myprint(char *fmt, ...)
{
int result;
va_list args;
fputs("foobar: ", stderr);
va_start(args, fmt);
result = vfprintf(stderr, fmt, args);
va_end(args);
return result;
}
int main(int argc, char **argv)
{
myprint("i=%d\n",6);
myprint("i=%s\n",6);
myprint("i=%s\n","abc");
myprint("%s,%d,%d\n",1,2);
return 0;
}
编译时添加 -Wall
就会打印 Warning 信息,如果去除,实际上不会显示任何信息,通常可以提前发现常见的问题。
constructor/destructor
可以设置程序在开始执行或停止时调用指定的函数。
__attribute__((constructor))
在 main()
之前执行,__attribute__((destructor))
在 main()
执行结束之后执行。
#include <stdio.h>
#include <stdlib.h>
static __attribute__((constructor)) void before()
{
printf("Hello World\n");
}
static __attribute__((destructor)) void after()
{
printf("Bye World!\n");
}
int main(int args,char ** argv)
{
printf("Live...\n");
return EXIT_SUCCESS;
}
如果有多个函数,可以指定优先级,其中 0~100 (含100)系统保留。在 main 之前顺序为有小到大,退出时顺序为由大到小。
#include <stdio.h>
#include <stdlib.h>
static __attribute__((constructor(102))) void before102()
{
printf("Hello World 102\n");
}
static __attribute__((destructor(102))) void after102()
{
printf("Bye World! 102\n");
}
static __attribute__((constructor(101))) void before101()
{
printf("Hello World 101\n");
}
static __attribute__((destructor(101))) void after101()
{
printf("Bye World! 101\n");
}
int main(int args,char ** argv)
{
printf("Live...\n");
return EXIT_SUCCESS;
}
在使用时也可以先声明然再定义
#include <stdio.h>
#include <stdlib.h>
void before() __attribute__((constructor));
void after() __attribute__((destructor));
void before()
{
printf("Hello World\n");
}
void after()
{
printf("Bye World!\n");
}
int main(int args,char ** argv)
{
printf("Live...\n");
return EXIT_SUCCESS;
}
hot/cold
也就是 __attribute__((hot))
__attribute__((cold))
用于分支预测的一些处理。
其中 hot 表示该函数会被经常调用到,在编译链接时要对其优化,或说是将它和其它同样热 (hot) 的函数放到一块,这样有利于缓存的存取。
而 cold 表示该函数比较冷门,这样在分支预测机制里就不会对该函数进行预取,或说是将它和其它同样冷门 (cold) 的函数放到一块,这样它就很可能不会被放到缓存中来,而让更热门的指令放到缓存中。
__attribute__((visibility))
程序调用某个函数 A,而 A 函数存在于两个动态链接库 liba.so 和 libb.so 中,并且程序执行需要链接这两个库,此时程序调用的 A 函数到底是来自于 a 还是 b 呢?
这取决于链接时的顺序,首先链接的库会更新符号表,比如先链接 liba.so,这时候通过 liba.so 的导出符号表就可以找到函数 A 的定义,并加入到符号表中,而不会再查找 libb.so 。
也就是说,这里的调用严重的依赖于链接库加载的顺序,可能会导致混乱。
gcc 的扩展中有如下属性 __attribute__ ((visibility("hidden")))
可以用于抑制将一个函数的名称被导出,对连接该库的程序文件来说,该函数是不可见的,使用的方法如下:
1. 创建一个c源文件
#include<stdio.h>
#include<stdlib.h>
__attribute ((visibility("default"))) void not_hidden()
{
printf("exported symbol\n");
}
void is_hidden()
{
printf("hidden one\n");
}
想要做的是,第一个函数符号可以被导出,第二个被隐藏。
2. 生成动态库
先编译成一个动态库,使用到属性 -fvisibility
。
----- 编译
$ gcc -shared -o libvis.so -fvisibility=hidden foobar.c
----- 查看符号链接
# readelf -s libvis.so |grep hidden
7: 0000040c 20 FUNC GLOBAL DEFAULT 11 not_hidden
48: 00000420 20 FUNC LOCAL HIDDEN 11 is_hidden
51: 0000040c 20 FUNC GLOBAL DEFAULT 11 not_hidden
可以看到,属性确实有作用了。
3. 编译链接
现在试图链接程序。
int main()
{
not_hidden();
is_hidden();
return 0;
}
试图编译成一个可执行文件,链接到刚才生成的动态库。
$ gcc -o exe main.c -L ./ -lvis
/tmp/cckYTHcl.o: In function `main':
main.c:(.text+0x17): undefined reference to `is_hidden'
说明了 hidden 确实起到作用了。
__attribute__((sentinel))
该属性表示,此可变参数函数需要一个 NULL
作为最后一个参数,这个 NULL
参数一般被叫做 “哨兵参数”。例如,有如下程序:
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <malloc.h>
void foo(char *first, ...)
{
char *p = (char *)malloc(100), *q = first;
va_list args;
va_start(args, first);
while (q) {
strcat(p, q);
q = va_arg(args, char *);
}
va_end(args);
printf("%s\n", p);
free(p);
}
int main(void)
{
foo("Hello", "World");
return 0;
}
当通过 gcc main.c -Wall
进行编译时,会发现没有任何警告,不过很显然,调用 foo()
时最后一个参数应该是个 NULL
以表明 “可变参数就这么多”。
编译完成后,如果尝试运行则会打印一些乱码,显然是有问题的。
正常来说,应该通过如下方式调用 foo("Hello", "World", NULL);
,为此,就需要用到了上述的属性,用于表示最后一个参数需要为 NULL
。
void foo(char *first, ...) __attribute__((sentinel));
这样再不写哨兵参数,在编译时编译器就会发出警告了。
但是,对于同样使用可变参数的 printf()
来说,为什么就不需要哨兵属性,实际上,通过第一个参数就可以确定需要多少个参数,如下。
/*
* 第一个参数中规定了有两个待打印项,所以打印时会取 "string" 和 1,多写的 "another_string" 会被忽略。
* printf()在被调用时明确知道此次调用需要多少个参数,所以也就无需哨兵参数的帮忙。
*/
printf("%s %d\n", "string", 1, "another_string");