GCC编译器函数属性
gcc编译器函数属性声明概括请看往期博客:
http://t.csdn.cn/a9bCBhttp://t.csdn.cn/a9bCB
alias("target")
函数属性声明——alias("target"),起到的作用是:给函数起一个别名
alias 属性导致函数声明将会作为另一个符号(函数)的别名被“触发”,该符号(函数)之前必须使用相同的类型进行声明,对于函数的参数,也必须具有相同的大小和对齐方式。
声明与目标类型不同的别名是未定义的,并且会在编译阶段被诊断为错误。
例如,以下声明:
void __func(){
printf("I'm __func()\n");
return;
}
void func() __attribute__((alias("__func")));
定义了‘func’作为‘__func’的别名。此时调用func和直接调用__func的效果是一致的:
#include <stdio.h>
void __func(){
printf("I'm __func()\n");
return;
}
void func() __attribute__((alias("__func")));
int main(){
func();
__func();
return 0;
}
gcc编译运行,输出内容如下:
I'm __func()
I'm __func()
可以看出,调用func()实际上就是调用了__func()。
那么,实际开发过程中,alias (“target“) 的经典应用场景是什么呢?
版本兼容
现在,你已经开发完成一套动态库a,a库内包括三个函数接口:void alice(void)、void bob(void)、void celia(void),该库版本号被你命名为1.0.0。a库源代码如下:(省略若干细节,专注重点)
a.h:
#ifndef A_H
#define A_H
void alice(void);
void bob(void);
void celia(void);
#endif
a.c:
#include "a.h"
void alice(void){
printf("I'm alice\n");
return;
}
void bob(void){
printf("I'm bob\n");
return;
}
void celia(void){
printf("I'm celia\n");
return;
}
很久之后,根据项目的一贯命名规则,你发现该库中的函数接口命名需要修改,需要将首字母大写,即:void Alice(void)、void Bob(void)、void Celia(void)。
首先可以想到的解决方案是,将源代码中函数名修改,重新编译动态库a,然后替换交付。
但是,由于你的客户Davis公司已经基于a库的1.0.0版本编写了他们的程序Elbert 1.0.0和Fredy 1.0.0,并且已经发布,其他用户已经下载使用中。Elbert 1.0.0和Fredy 1.0.0两者使用同一个动态库a,在某个特定的Davis公司的安装目录下(至于为什么不是每个软件都各自维护一个动态库,这是考虑到实际情况,有些库的体积是相当大的,而且分开不好维护)。按照刚才的解决思路,那么Davis公司需要根据你库的修改重新修改他们的程序,然后重新编译发行,然后向用户发布更新1.0.1版本。
现在某些用户有这样的需求:我想升级Elbert 1.0.0至Elbert 1.0.1版本,但由于Fredy 1.0.0我用的特别顺手,不想升级。此需求带来的问题是升级Elbert会将a库替换为1.0.1版本,届时Fredy 1.0.0无法正常使用a库,必然崩溃。
车到山前必有路,让Elbert 1.0.1依赖的库名称改一下,不叫a库了,改叫a.1.0.1库,之后的库后面都加个版本。这样做带来的后果是,Davis公司某个存放动态库的路径下,若干年后出现了这样的情况:
a a.1.0.1 a.1.0.2 a.1.0.3 a.1.0.4 a.1.0.5 .............
另一个后果是,你所在公司将会受到极大谴责,被批判随意命名库,库名随意更换,基于a库进行开发时,需要时刻注意版本,根据版本选择库名称。
痛定思痛,考虑更佳的解决方案——alias("target")
a.h:
#ifndef A_H
#define A_H
void alice(void);
void bob(void);
void celia(void);
#endif
a.c:
#include <stdio.h>
#include "a.h"
void __alice(void){
printf("I'm alice\n");
return;
}
void alice(void) __attribute__((alias("__alice")));
void __bob(void){
printf("I'm bob\n");
return;
}
void bob(void) __attribute__((alias("__bob")));
void __celia(void){
printf("I'm celia\n");
return;
}
void celia(void) __attribute__((alias("__celia")));
如果a库的1.0.0版本是这种方式实现的,那么想要达到更改函数首字母为大写这个需求,需要修改源代码中相关部分,然后重新编译,发布1.0.1版本。
a.h:(1.0.1)
//1.0.0
void alice(void);
void bob(void);
void celia(void);
//1.0.1
void Alice(void);
void Bob(void);
void Celia(void);
a.c:(1.0.1)
#include <stdio.h>
#include "a.h"
void __alice(void){
printf("I'm alice\n");
return;
}
void alice(void) __attribute__((alias("__alice")));
void Alice(void) __attribute__((alias("__alice")));
void __bob(void){
printf("I'm bob\n");
return;
}
void bob(void) __attribute__((alias("__bob")));
void Bob(void) __attribute__((alias("__bob")));
void __celia(void){
printf("I'm celia\n");
return;
}
void celia(void) __attribute__((alias("__celia")));
void Celia(void) __attribute__((alias("__celia")));
现在,a库的1.0.1版本完全可以兼容1.0.0版本,编译好新版本库后,发给Davis公司,Davis公司无需对Elbert 1.0.0和Fredy 1.0.0重新编译,只需要替换打包过程中的a库即可,重新打包a库到Elbert和Fredy的安装程序中,即可得到对应的1.0.1版本了。
再回到某些用户的需求上,如果只升级了Elbert至1.0.1,Fredy保持1.0.0版本,两者共用a库1.0.1版本,a库中既包含老的小写字母的接口,也包含大写字母开头的接口,完全可以兼容两者。
其他场景
另一个常见的应用场景,是和 weak 相结合,给函数起一个别名的同时,声明为弱符号