关于C语言中弱符号与弱引用的实际应用问题

 更新时间:2021年09月19日 11:12:20   作者:有态度的程序员  
在编码过程中,我们经常遇到符号重定义的错误问题,本文通过实例代码展示给大家介绍了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语言弱符号与弱引用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 使用c++实现OpenCV图像横向&纵向拼接

    使用c++实现OpenCV图像横向&纵向拼接

    这篇文章主要介绍了使用c++实现OpenCV图像横向&纵向拼接,文中有图像拼接函数,可以实现如“长图拼接王”这类小程序的类似功能,大家可以将该函数封装在软件中自由使用
    2021-08-08
  • 举例理解C语言二维数组的指针指向问题

    举例理解C语言二维数组的指针指向问题

    这篇文章主要介绍了C语言二维数组的指针指向问题,文中不建议用二级指针来访问二维数组,需要的朋友可以参考下
    2015-12-12
  • 基于OpenCV 差分法实现绿叶识别

    基于OpenCV 差分法实现绿叶识别

    物体识别是图像处理学在现实生活中较多的应用之一,本文提供了一种相对简单的思路来实现绿叶识别,适合初学图像处理的新人研究参考。感兴趣的同学可以关注一下
    2021-11-11
  • C语言中杨氏矩阵与杨辉三角的实现方法

    C语言中杨氏矩阵与杨辉三角的实现方法

    这篇文章主要给大家介绍了关于C语言中杨氏矩阵与杨辉三角的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-05-05
  • C++中套接字库sockpp的使用详解

    C++中套接字库sockpp的使用详解

    sockpp是一个开源、简单、现代的C++套接字库,这篇文章主要为大家详细介绍一下套接字库sockpp的使用,文中的示例代码讲解详细,感兴趣的小伙伴可以学习一下
    2023-11-11
  • 详解c++ 继承

    详解c++ 继承

    这篇文章主要介绍了c++ 继承的相关资料,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-07-07
  • C++面向对象语言自制多级菜单功能实现代码

    C++面向对象语言自制多级菜单功能实现代码

    菜单类主要负责菜单的创建、修改、删除,是包含菜单结构组织和响应函数的模型,用户拥有充分的自主性,可根据需要自定义菜单显示和响应函数,这篇文章主要介绍了C++面向对象语言自制多级菜单,需要的朋友可以参考下
    2024-06-06
  • C++ Boost Spirit进阶教程

    C++ Boost Spirit进阶教程

    Boost是为C++语言标准库提供扩展的一些C++程序库的总称。Boost库是一个可移植、提供源代码的C++库,作为标准库的后备,是C++标准化进程的开发引擎之一,是为C++语言标准库提供扩展的一些C++程序库的总称
    2022-11-11
  • C/C++函数调用的几种方式总结

    C/C++函数调用的几种方式总结

    本篇文章主要是对C/C++函数调用的几种方式进行了详细的总结介绍,需要的朋友可以过来参考下,希望对大家有所帮助
    2013-12-12
  • VC定制个性化的MessageBox解决方法

    VC定制个性化的MessageBox解决方法

    这篇文章主要介绍了VC定制个性化的MessageBox解决方法,有助于进一步的了解windows应用程序的消息机制及运行原理,需要的朋友可以参考下
    2014-07-07

最新评论