00. 目录

文章目录

  • 00. 目录
  • 01. weak属性
  • 02. 变量强符号和弱符号
  • 03. 函数强符号和弱符号
  • 04. 弱符号的作用
  • 05. alias属性
  • 06. 附录

01. weak属性

GNU C 通过 attribute 声明weak属性,可以将一个强符号转换为弱符号。

用法:

void  __attribute__((weak))  func(void);
int  num  __attribte__((weak);

编译器在编译源程序时,无论你是变量名、函数名,在它眼里,都是一个符号而已,用来表征一个地址。编译器会将这些符号集中,存放到一个叫符号表的 section 中。

在一个软件工程项目中,可能有多个源文件,由不同工程师开发。有时候可能会遇到这种情况:A 工程师在他负责的 A.c 源文件中定义了一个全局变量 num,而 B 工程师也在他负责的 B.c 源文件中定义了一个同名全局变量 num。那么当我们在程序中打印变量 num 的值时,是该打印哪个值呢?

是时候表演真正的技术了。这时候,就需要用编译链接的原理知识来分析这个问题了。编译链接的基本过程其实很简单,主要分为三个阶段。

编译阶段:编译器以源文件为单位,将每一个源文件编译为一个 .o 后缀的目标文件。每一个目标文件由代码段、数据段、符号表等组成。

链接阶段:链接器将各个目标文件组装成一个大目标文件。链接器将各个目标文件中的代码段组装在一起,组成一个大的代码段;各个数据段组装在一起,组成一个大的数据段;各个符号表也会集中在一起,组成一个大的符号表。最后再将合并后的代码段、数据段、符号表等组合成一个大的目标文件。

重定位:因为各个目标文件重新组装,各个目标文件中的变量、函数的地址都发生了变化,所以要重新修正这些函数、变量的地址,这个过程称为重定位。重定位结束后,就生成了可以在机器上运行的可执行程序。

上面举例的工程项目,在编译过程中的链接阶段,可能就会出现问题:A.c 和 B.c 文件中都定义了一个同名变量 num,那链接器到底该用哪一个呢?解决该问题就涉及强符号和弱符号问题。

02. 变量强符号和弱符号

在一个程序中,无论是变量名,还是函数名,在编译器的眼里,就是一个符号而已。符号可以分为强符号和弱符号。

  • 强符号:函数名、初始化的全局变量名;
  • 弱符号:未初始化的全局变量名。

在一个工程项目中,对于相同的全局变量名、函数名,我们一般可以归结为下面三种场景。

  • 强符号+强符号
  • 强符号+弱符号
  • 弱符号+弱符号

强符号和弱符号在解决程序编译链接过程中,出现的多个同名变量、函数的冲突问题非常有用。一般我们遵循下面三个规则。

  • 一山不容二虎
  • 强弱可以共处
  • 体积大者胜出

为了方便,这是我编的顺口溜。主要意思就是:在一个项目中,不能同时存在两个强符号,比如你在一个多文件的工程中定义两个同名的函数,或初始化的全局变量,那么链接器在链接时就会报重定义的错误。但一个工程中允许强符号和弱符号同时存在。比如你可以同时定义一个初始化的全局变量和一个未初始化的全局变量,这种写法在编译时是可以编译通过的。编译器对于这种同名符号冲突,在作符号决议时,一般会选用强符号,丢掉弱符号。还有一种情况就是,一个工程中,同名的符号都是弱符号,那编译器该选择哪个呢?谁的体积大,即谁在内存中存储空间大,就选谁。

程序示例

#include <stdio.h>int a = 1;
int b;void fun(void)
{printf("fun a = %d\n", a);printf("fun b = %d\n", b);
}int a;
int b = 2;int main(void)
{printf("main a = %d\n", a);printf("main b = %d\n", b);fun();return 0;
}

执行结果

deng@itcast:~/tmp$ gcc test.c
deng@itcast:~/tmp$ ./a.out
main a = 1
main b = 2
fun a = 1
fun b = 2

我们在 main.c 和 func.c 中分别定义了两个同名全局变量 a 和 b,但是一个是强符号,一个是弱符号。链接器在链接过程中,看到冲突的同名符号,会选择强符号,所以你会看到,无论是 main 函数,还是 func 函数,打印的都是强符号的值。

一般来讲,不建议在一个工程中定义多个不同类型的弱符号,编译的时候可能会出现各种各样的问题,这里就不举例了。在一个工程中,也不能同时定义两个同名的强符号,即初始化的全局变量或函数,否则就会报重定义错误。但是我们可以使用 GNU C 扩展的 weak 属性,将一个强符号转换为弱符号。

程序示例

# test.c
#include <stdio.h>int a = 4;void fun(void);int main(void)
{printf("main a = %d\n", a);fun();return 0;
}# fun.c
#include <stdio.h>int a __attribute__((weak)) = 1;void fun(void)
{printf("fun a = %d\n", a);
}

执行结果

deng@itcast:~/tmp$ gcc test.c  fun.c
deng@itcast:~/tmp$ ./a.out
main a = 4
fun a = 4
deng@itcast:~/tmp$

通过 weak 属性声明,将 func.c 中的全局变量 a,转换为一个弱符号,然后在 main.c 里同样定义一个全局变量 a,并初始化 a 为4。链接器在链接时会选择 main.c 中的这个强符号,所以在两个文件中,打印变量 a 的值都是4。

03. 函数强符号和弱符号

链接器对于同名变量冲突的处理遵循上面的强弱规则,对于函数同名冲突,同样也遵循相同的规则。函数名本身就是一个强符号,在一个工程中定义两个同名的函数,编译时肯定会报重定义错误。但我们可以通过 weak 属性声明,将其中一个函数转换为弱符号。

程序示例

//fun.c#include <stdio.h>int a __attribute__((weak)) = 1;void __attribute__((weak)) fun(void)
{printf("fun a = %d\n", a);
}// test.c
#include <stdio.h>int a = 4;void fun(void)
{printf("test.c fun....\n");
}int main(void)
{printf("main a = %d\n", a);fun();return 0;
}

执行结果

deng@itcast:~/tmp$ gcc fun.c test.c
deng@itcast:~/tmp$ ./a.out
main a = 4
test.c fun....

在这个程序示例中,我们在 main.c 中重新定义了一个同名的 func 函数,然后将 func.c 文件中的 func() 函数,通过 weak 属性声明转换为一个弱符号。链接器在链接时会选择 main.c 中的强符号,所以我们在 main 函数中调用 func() 时,实际上调用的是 main.c 文件里的 func() 函数。

04. 弱符号的作用

在一个源文件中引用一个变量或函数,当我们只声明,而没有定义时,一般编译是可以通过的。这是因为编译是以文件为单位的,编译器会将一个个源文件首先编译为 .o 目标文件。编译器只要能看到函数或变量的声明,会认为这个变量或函数的定义可能会在其它的文件中,所以不会报错。甚至如果你没有包含头文件,连个声明也没有,编译器也不会报错,顶多就是给你一个警告信息。但链接阶段是要报错的,链接器在各个目标文件、库中都找不到这个变量或函数的定义,一般就会报未定义错误。

当函数被声明为一个弱符号时,会有一个奇特的地方:当链接器找不到这个函数的定义时,也不会报错。编译器会将这个函数名,即弱符号,设置为0或一个特殊的值。只有当程序运行时,调用到这个函数,跳转到0地址或一个特殊的地址才会报错。

程序示例

#include <stdio.h>int a = 4;void __attribute__((weak)) fun(void);int main(void)
{printf("main a = %d\n", a);fun();return 0;
}

执行结果

deng@itcast:~/tmp$ gcc test.c
deng@itcast:~/tmp$ ./a.out
main a = 4
段错误 (核心已转储)

在这个示例程序中,我们没有定义 func() 函数,仅仅是在 main.c 里作了一个声明,并将其声明为一个弱符号。编译这个工程,你会发现是可以编译通过的,只是到了程序运行时才会出错。

为了防止函数运行出错,我们可以在运行这个函数之前,先做一个判断,即看这个函数名的地址是不是0,然后再决定是否调用、运行。这样就可以避免段错误了,示例代码如下。

程序示例

#include <stdio.h>int a = 4;void __attribute__((weak)) fun(void);int main(void)
{printf("main a = %d\n", a);if (NULL != fun){fun();}return 0;
}

执行结果

deng@itcast:~/tmp$ gcc test.c
deng@itcast:~/tmp$ ./a.out
main a = 4

函数名的本质就是一个地址,在调用 func 之前,我们先判断其是否为0,为0的话就不调用了,直接跳过。你会发现,通过这样的设计,即使这个 func() 函数没有定义,我们整个工程也能正常的编译、链接和运行!

弱符号的这个特性,在库函数中应用很广泛。比如你在开发一个库,基础的功能已经实现,有些高级的功能还没实现,那你可以将这些函数通过 weak 属性声明,转换为一个弱符号。通过这样设置,即使函数还没有定义,我们在应用程序中只要做一个非0的判断就可以了,并不影响我们程序的运行。等以后你发布新的库版本,实现了这些高级功能,应用程序也不需要任何修改,直接运行就可以调用这些高级功能。

弱符号还有一个好处,如果我们对库函数的实现不满意,我们可以自定义与库函数同名的函数,实现更好的功能。比如我们 C 标准库中定义的 gets() 函数,就存在漏洞,常常成为黑客堆栈溢出攻击的靶子。

#include <stdio.h>int main(void)
{char a[10];gets(a);puts(a);return 0;
}

C 标准定义的库函数 gets() 主要用于输入字符串,它的一个 Bug 就是使用回车符来判断用户输入结束标志。这样的设计很容易造成堆栈溢出。比如上面的程序,我们定义一个长度为10的字符数组用来存储用户输入的字符串,当我们输入一个长度大于10的字符串时,就会发生内存错误。

接着我们定义一个跟 gets() 相同类型的同名函数,并在 main 函数中直接调用,代码如下。

#include <stdio.h>char *gets(char *str)
{printf("hello 123456789\n");return NULL;
}int main(void)
{char a[10];gets(a);return 0;
}

执行结果

deng@itcast:~/tmp$ gcc test.c
deng@itcast:~/tmp$ ./a.out
hello 123456789
deng@itcast:~/tmp$

通过运行结果,我们可以看到,虽然我们定义了跟 C 标准库函数同名的 gets() 函数,但编译是可以通过的。程序运行时调用 gets() 函数时,就会跳转到我们自定义的 gets() 函数中运行。

05. alias属性

GNU C 扩展了一个 alias 属性,这个属性很简单,主要用来给函数定义一个别名。

程序示例

#include <stdio.h>void _fun(void)
{printf("_fun\n");
}void fun() __attribute__((alias("_fun")));int main(void)
{char a[10];fun();return 0;
}

执行结果

deng@itcast:~/tmp$ gcc test.c
deng@itcast:~/tmp$ ./a.out
_fun

通过 alias 属性声明,我们就可以给 _fun() 函数定义一个别名 fun(),以后我们想调用 _fun() 函数,可以直接通过 fun() 调用即可。

在 Linux 内核中,你会发现 alias 有时会和 weak 属性一起使用。比如有些函数随着内核版本升级,函数接口发生了变化,我们可以通过 alias 属性给这个旧接口名字做下封装,起一个新接口的名字。

//f.c
void __f(void)
{printf("__f()\n");
}
void f() __attribute__((weak,alias("__f")));
​
//main.c
void __attribute__((weak)) f(void);
void f(void)
{printf("f()\n");
}
​
int main(void)
{f();return 0;
}

当我们在 main.c 中新定义了 f() 函数时,在 main 函数中调用 f() 函数,会直接调用 main.c 中新定义的函数;当 f() 函数没有新定义时,就会调用 __f() 函数。

06. 附录

参考:C语言嵌入式Linux高级编程

【嵌入式】C语言高级编程-强符号和弱符号(09)相关推荐

  1. 浅谈一下嵌入式中的强符号和弱符号

    __attribute__  是一个编译器指令,其实是 GNU C 的一种机制,本质是一个编译器的指令,在声明的时候可以提供一些属性,在编译阶段起作用,来做多样化的错误检查和高级优化. 用于在 C.C ...

  2. 鼠标绘图 c语言,c语言高级编程技术教程 图形显示方式与鼠标输入.doc

    c语言高级编程技术教程 图形显示方式与鼠标输入 c语言高级编程技术教程 图形显示方式和鼠标输入 图形显示方式和鼠标输入 问题的提出编写程序,使用鼠标进行如下操作:按住鼠标器的任意键并移动,十字光 标将 ...

  3. c语言高级程序设计第五版PDF,C语言高级编程.pdf

    C语言高级编程 概述 由几个测试程序说开去 预编译与宏 高级预编译介绍 宏的高级用法 变量 变量分类详细解析 我的变量去哪儿了? 大小端对变量的影响 内存与指针 常见内存使用错误大观 指针,又是指针! ...

  4. C语言中的强符号与弱符号

    注意,强符号和弱符号都是针对定义来说的,不是针对符号的引用. 一.概述 在C语言中,函数和初始化的全局变量(包括显示初始化为0)是强符号,未初始化的全局变量是弱符号. 对于它们,下列三条规则使用: ① ...

  5. C语言中的强符号与弱符号(关于变量声明与定义的深入讨论)

    看到一篇介绍C语言强符号与弱符号的文章非常好,转载过来加深印象. 原文地址:http://blog.csdn.net/astrotycoon/article/details/8008629 ===== ...

  6. matlab高级教程教材,MATLAB语言高级编程 PDF_IT教程网

    资源名称:MATLAB语言高级编程 PDF 本书共分8章,主要介绍了matlab的概述.matlab安装与工作桌面:matlab的编程基础,包括matlab的变量.matlab的运算符.矩阵的创建及运 ...

  7. Go 学习推荐 —(Go by example 中文版、Go 构建 Web 应用、Go 学习笔记、Golang常见错误、Go 语言四十二章经、Go 语言高级编程)

    Go by example 中文版 Go 构建 Web 应用 Go 学习笔记:无痕 Go 标准库中文文档 Golang开发新手常犯的50个错误 50 Shades of Go: Traps, Gotc ...

  8. 高级编程中C语言属于,c语言高级编程

    c语言高级编程 C高级编程 责任编辑:admin 更新日期:2005-8-6 深入了解C语言(函数的参数传递和函数使用参数的方法) tangl_99(原作) 关键字 C语言,汇编,代码生成,编译器 C ...

  9. 新手小心:c语言的强符号和弱符号

    声明:下面的实例全部在linux下尝试,window下未尝试.有兴趣者可以试一下.文章针c初学者. c语言的强符号和弱符号是c初学者经常容易犯错的地方.而且很多时候,特别是多人配合开发的程序,它引起的 ...

最新文章

  1. vue-cli3+typescript初体验
  2. java的正则表达式 CPU_小心踩雷!一个小小的正则表达式竟把CPU拖垮......
  3. mapreduce排序算法_MapReduce算法–二级排序
  4. 向高手学习--第3列数据是第2列从头到当前行的值之和
  5. AlertDialog用法。
  6. code review手记2
  7. System Center 2016组件将发生什么变化?
  8. 無題(後改為總有那麼一句話)
  9. 【数字信号处理】--数字信号分析与处理基础知识
  10. 【MongoDB】索引属性 之 唯一索引
  11. php 图形库 锯齿,PHP imageantialias - 是否使用抗锯齿(antialias)功能 - 有码中国
  12. my97DatePicker选择年、季度、月、周、日(转)
  13. SQL求同比增长率(系列1)
  14. java 视频边下边播,android 边下边播放mp3完美实现(有缓冲和播放进度效果)
  15. apscheduler使用中的时区问题
  16. 【正点原子FPGA连载】 第二十章 LCD触摸屏实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0
  17. U-Boot参数设置 .
  18. JAVA的Stream
  19. linux创建删除用户及vim简单操作
  20. 如何通过通达信股票交易DLL接口来实现条件选股

热门文章

  1. poj 3275 Ranking the Cows 搜索
  2. 在ASP.net 中对GridView中数据的删除、编辑等操作:
  3. java怎么获取字符串位置,Java:在字符串中获取匹配位置的方法?
  4. 6-3 逆序数据建立链表
  5. oracle数据转成sqlserver,oracle数据库转换到Sqlserver的几点经验
  6. 【Java NIO的深入研究6】JAVA NIO之Scatter/Gather
  7. 重新实践《轻量级DJANGO》这本书
  8. iOS 9.0以后支持http协议
  9. 面向侧面的程序设计AOP-------《二》本质
  10. lightoj 1004 dp:数字三角形