关于C语言中弱符号与弱引用的实际应用问题
作者:有态度的程序员
最近在学习《程序员的自我修养——链接、装载与库》时,get到了一个新的知识点:弱符号与弱引用。书中简短的介绍,让我了解到弱符号的含义以及使用方式。了解我的朋友,应该知道我喜欢将知识点与我们实际工作结合起来,在工作中利用起来,正所谓学以善用。根据我的理解,觉得利用弱符号的特性可以帮组我们在工作中编写出更加稳定,可复用,可组合的优秀代码。在此向大家分享。
符号重定义错误
在编码过程中,我们经常遇到符号重定义的错误。编译器会报如下错误:
multiple definition of `xxx';
这就是符号重复定义导致的,再往细里面说,是在同一作用域内符号冲突。我们知道变量是由作用域和生命周期概念的。比如:
例1: main.c int strong=0; int main() { printf("strong = %d\n",strong); return 0; } strong.c int strong=1;
gcc main.c strong.c -o main
则会报重定义错误。因为在main.c 和strong.c 文件中,整型变量strong是全局变量,它们的作用域都是跨文件的。若是在不同的作用域,即使相同变量名,也不会报错。编译器会有默认的优先级处理:总是更小作用域的变量覆盖更大作用域的变量,前提是这两个变量的作用域是包含或被包含的关系。比如:
例2: main.c int strong=0; int main() { printf("strong = %d\n",strong); return 0; } strong.c static int strong=1;
gcc main.c strong.c -o main
不再报错。此时main.c 中的strong 变量的作用域是跨文件,而strong.c中的strong变量的作用域仅限strong.c文件。因此不存在相同作用域中,符号重定义问题。并且结果输出为0;
同理,下面的代码会编译报错吗?输出为多少呢?
main.c int strong=0; int main() { int strong=2; printf("strong = %d\n",strong); return 0; } strong.c static int strong=1;
甚至于下面的代码也是合法的:
main.c int strong=0; int main() { int strong=2; if(1) { int strong=3; } { int strong = 4; printf("strong = %d\n",strong); } return 0; }
在C语言中,我们可以简单地认为花括号是文件内作用域的分隔符。
强符号与弱符号
编译器默认函数和已初始化的全局变量为强符号,而未初始化的全局变量为弱符号;同时开发者可以通过"attribute((weak))"来声明一个符号为弱符号;
gcc 在编译过程中,对于强弱符号遵循一定规则进行取舍:
- 当有多个为强符号时,报"redefinition of ‘xxx'"错误
- 当仅有一个为强符号时,选取强符号的值
- 当都为弱符号时,选择其中暂用空间较大的符号。(防止溢出越界等问题)—— 这个应该和编译器有关,我在本地环境中,是不允许多个类型不同的弱符号存在的。编译会出错。
很明显,例1 则是出现多个强符号,导致的redefinition 错误。例如下面code:
main.c int strong; int strong=2; int main() { printf("strong = %d\n",strong); return 0; }
编译并不会出错,并且输出为2;
强引用与弱引用
我们知道在编译成可执行文件时,若源文件引用了外部目标文件的符号,在链接过程中,需要找到对应的符号定义,若未找到对应符号(未定义),链接器会报符号位未定义错误,导致编译出错。这种被称为强引用。与相对应的时弱引用(开发者可通过attribute((weakref))声明),链接器在链接符号过程中,若发现符号为弱引用,即使没有找到符号定义,链接时也不会报错,但是会将该引用默认为0;
书中的代码如下:
main.c int strong; int strong=2; int main() { printf("strong = %d\n",strong); return 0; }
虽然没有定义foo(),但是我们可以将它编译成可执行文件,并且GCC 编译不会报链接错误,但是当我们运行时,会发生运行错误。实际上新版本的编译器上诉的代码会在链接时报错的,新版本的示例代码应该如下:(新版本的weakref 需要函数别名,且必须是static 修饰)
main.c static __attribute__((weakref("foo"))) void myfoo(void); void main(void) { if(myfoo) { myfoo(); } }
新版的弱符号引用如上所示。即表示若没有找到foo函数,编译不报错,但是会默认myfoo为NULL。若在其他库中定义了foo函数,对myfoo的引用就相当于对foo的引用。这种弱引用在库的使用上十分有用的。
小节
经过上面的描述,我们了解到了强符号,弱符号,强引用,弱引用的概念。我认为起码有两点特性可以在我们工作中使用:
- 强符号可以替换弱符号。
- 弱引用可以避免函数未定义的错误。
强符号替换弱符号
一些库中对外接口可以声明为弱符号。比如:
在math库中,我们发现add(int num1, int num2)
这个接口存在问题,那我们解决方式一般有以下几种:
1. 实现一个myadd(int num1,int num2)
接口,之后再将项目中的所有add
,替换为myadd
。这种方式可行,但是存在缺点:修改量大,并且后续人员不清楚背景,很有可能继续使用熟悉的add
接口。
2. 更新math库,从更本解决此问题。这种方式比较推荐。但是也并不是通用的,比如有些库并不是开源的,并且已经过了支持日期,也就不适用了。
此时,我们可以自己在项目中定义一个add(int num1,int num2)
接口,用强符号替换库中的弱符号,这样改动是比较小的。(这种情景需要了解接口的实现内容,可给调用者较高的重构权力)
巧用弱引用提高代码的健壮性
应用层的开发,离不开sdk的提供,一般sdk维护了,即使应用没有需求发生,往往也会为了配合sdk,进行简单的修改。以设备升级作为举例,若升级过程中,分为传包(pass),验签(verify),解密(decode),安装(install),上传日志(report)等步骤,并且这些核心接口都是以libsdk.so库的形式提供给应用工程师。那么正常情况下,应用逻辑大致如下:
用户业务流程 ... pass(); ... verify(); ... decode(); ... install(); ... report(); ...
但是这样的业务代码,我觉得是非常差的。比如新的项目中,不需要做解密包操作了,(理论上libsdk.so库中应该不具备decode接口了),这样就会导致应用程序编译失败。undefine 'decode'
。
因此我建议应用代码可以如下:
static __attribute__((weakref("pass"))) void mypass(void); static __attribute__((weakref("verify"))) void myverify(void); static __attribute__((weakref("decode"))) void mydecode(void); static __attribute__((weakref("install"))) void myinstall(void); static __attribute__((weakref("report"))) void myreport(void); 用户业务流程 ... if(mypass) mypass(); else printf("don't need pass\n"); ... if(myverify) myverify(); else printf("don't need verify\n"); ... if(mydecode) mydecode(); else printf("don't need decode\n"); ... if(myinstall) myinstall(); else printf("don't need install\n"); ... if(myreport) myreport(); else printf("don't need report\n"); ...
以上便是我理解的内容,希望能引起的你的共鸣,如果你有好的想法,也可以和我一起分享。。。
到此这篇关于C语言中弱符号与弱引用的实际应用的文章就介绍到这了,更多相关C语言弱符号与弱引用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!