由于平时很少用到__attribute__定义函数或者变量的符号属性,所以很难想象C语言可以向C++一样进行函数或者变量的重载。

首先,复习一下有关强符号与弱符号的概念和编译器对强弱符号的处理规则:

1.不同变量与函数所在段

变量类型我们可以分为1)未初始化的,已初始化的;2)全局,局部;3)静态,非静态。

变量类型 所在段
初始化非静态全局变量 .data
初始化静态全局变量 .data
初始化静态局部变量 .data
初始化绝非静态局部变量 运行时动态分配
未初始化非静态全局变量 .bss
未初始化静态全局变量 .bss
未初始化静态局部变量 .bss
未初始化非静态局部变量 运行时动态分配

局部变量无需事先分配到段中,原因是他是用的是进程的栈空间,查看反汇编代码就可以发现,一般函数进入后第一件事情,就是push,把局部变量先放到栈里面,所以局部变量的相对地址在编译后就已经加到指令代码里面了。

假设有如下代码:

int global_init_var = 1;
int global_uninit_var;
static int static_init_var = 2;
static int static_uninit_var; int main(void){
int local_init_var = 3;
int local_uninit_var;
static int static_local_init_var = 4;
static int static_local_uninit_var; global_uninit_var = global_init_var+10;
static_uninit_var = static_init_var+10;
local_uninit_var = local_init_var+10;
static_local_uninit_var = static_local_init_var+10; printf("%d,%d,%d,%d\n", global_uninit_var, static_uninit_var, local_uninit_var, static_local_uninit_var);
return 0;
}

全局未初始化变量其实在汇编完成后被分配为COMMON特殊段内,在链接时候根据程序是否调用此变量决定是否将其置于.bss段中。

2.强符号与弱符号

声明为全局变量和非静态函数,编译器都将其符号视为强符号,而使用__attribute__((weak))可将符号指定为弱符号。

编译器在遇到强、弱符号时,遵循下面的规则进行处理:

1)遇到多个强符号,出错并提示有符号重复定义;

2)遇到一个强符号,多个弱符号,则选用强符号,弱符号被忽略,此时其他文件对其原若符号的操作将产生误操作(弱符号地址指向了强符号地址)

3)多个弱符号,与链接器有关,一般选用占用空间最大的符号。

看一个有趣的代码:

在main.c和foo.c中分别写入foo_only函数

void foo_only(void){
printf("foo_only() is in %s", __FILE__);
}

此时,编译gcc -o test foo.c  main.c就会提示符号被重复定义,这是因为foo_only在两个文件中均为强符号。

当我在main.c和foo.c中的任意一个文件内加上void foo_only(void) __attribute__((weak))

那么链接器就会自动链接默认的强符号。

3.C语言重载方法

如果在使用第三方的库或者自己提供库给别人使用时,对库中某些模块不满意,那么应该提供客户一个自给自足的机会。此时,weak属性就非常有用。

实验代码:

foo1.c

foo2.c

运行gcc -o symbol_weak_strong foo1.c foo2.c

1)外部有则引用,没有就用自己的(外部文件函数为强符号,本文件函数为弱符号)

现在我不知道外部文件foo2.c是否有我所使用的函数,那么我先自己在文件foo1.c中定义一个,请注意foo2函数。

在含main的文件将foo2声明为弱符号,而外部文件的函数在无显示声明为弱符号时,均为强符号,因此,

编译器将选择foo2.c中的foo2函数。

2)自己有的就用自己的,没有就引用外部的(外部文件函数声明为弱函数)

如果我对自己的代码水平有足够信心,那么只要我写了,我就用自己的。那么就要求外部文件将函数符号声明为weak,下面的声明语句位于foo.c。

此时,在foo1.c中的函数foo1作为强符号使用,外部文件的foo2.c中的foo1就显式声明为弱符号。

还可以在foo1.c主文件内显式声明外部文件的函数为弱符号:extern void foo1(void) __attribute__((weak)); 这个被称为弱引用。

3)如果不小心两个函数都声明为弱符号,那么编译器怎么选择?

说法一:按照函数占用空间最大的函数作为引用对象;

说法二:按照编译链接顺序进行引用。

由于与编译器有关,所以再次不作详细讨论。

4. C语言的“伪函数重载“与C++的重载区别

C++为了避免C语言那样,不同人开发不同模块代码中,使用了相同的函数或变量名,增加了名称空间和符号修饰来避免多模块之间的符号冲突问题。

(C程序员会尽量避免使用全局变量或者规范命名方法来避免符号冲突,但无法解决这一问题)

GCC对于C++名称修饰方法如下(摘自《程序员的自我修养》):

所有的符号都以”_Z"开头,对于嵌套的名字(在名称空间或在类里面的),后面紧跟“N",然后是各个名称空间和类的名字,每个名字前是名字字符串的长度,再以”E"结尾,如果是函数,则E后面紧跟参数列表的类型简写。比如

namespace N{

class C{

int var;

void func(float);

}

}

作用域为N::C::func的函数经过名称修饰后就是_ZN1N1C4funcEf。

作用域为N::C::var的函数经过名称修饰后就是_ZN1N1C3varE。

使用c++filt可以解析被修饰过的名称。

C++做符号修饰是在汇编阶段完成的;而上述所说的C声明弱符号是作用在链接阶段。

【C语言用法】C语言的函数“重载”的更多相关文章

  1. 从两个角度理解为什么 JS 中没有函数重载

    函数重载是指在同一作用域内,可以有一组具有相同函数名,不同参数列表(参数个数.类型.顺序)的函数,这组函数被称为重载函数.重载函数通常用来声明一组功能相似的函数,这样做减少了函数名的数量,避免了名字空 ...

  2. ActionScript语言函数重载

    更新:你见过JavaScript支持重载吗,规范就是这么定义的.如果不是研究Java和Flex对象的Serialization,我也不会注意它. 距离写这篇文章已有8年了,时光匆匆啊,今天整理资料时看 ...

  3. C++函数重载实现的原理以及为什么在C++中使用用C语言编译的函数时,要在函数名称前面加上extern "C"声明

    C++相对于C语言而言支持函数重载是其极大的一个特点,相信在使用C语言的时候大家如果要写一个实现两个整型数据相加的函数还要写一个浮点型数据相加的函数,那么这两个函数的名字绝对不可以一样,这样无疑在我们 ...

  4. C语言对文件的操作函数用法详解2

    fopen(打开文件) 相关函数 open,fclose 表头文件 #include<stdio.h> 定义函数 FILE * fopen(const char * path,const  ...

  5. C语言对文件的操作函数用法详解1

    在ANSIC中,对文件的操作分为两种方式,即: 流式文件操作 I/O文件操作 一.流式文件操作 这种方式的文件操作有一个重要的结构FILE,FILE在stdio.h中定义如下: typedef str ...

  6. C++对C语言的拓展(5)—— 函数重载和函数指针结合

    1.函数指针的介绍 函数指针指向某种特定类型,函数的类型由其参数及返回类型共同决定,与函数名无关.举例如下: int add(int nLeft,int nRight);//函数定义 该函数类型为in ...

  7. C++对C语言的拓展(4)—— 函数重载

    函数重载(Function Overload):用同一个函数名定义不同的函数,当函数名和不同的参数搭配时函数的含义不同. 1.重载规则 (1)函数名相同: (2)参数个数不同,参数的类型不同,参数顺序 ...

  8. 【C语言】05-printf和scanf函数

    一.printf函数 这是在stdio.h中声明的一个函数,因此使用前必须加入#include <stdio.h>,使用它可以向标准输出设备(比如屏幕)输出数据 1.用法 1> pr ...

  9. 【C语言】03-printf和scanf函数

    一.printf函数 这是在stdio.h中声明的一个函数,因此使用前必须加入#include <stdio.h>,使用它可以向标准输出设备(比如屏幕)输出数据 1.用法 1> pr ...

  10. C语言入门:05.scanf函数

    一.变量的内存分析 1.字节和地址 为了更好地理解变量在内存中的存储细节,先来认识一下内存中的“字节”和“地址”. (1)内存以“字节为单位”

随机推荐

  1. 修改Fedora 20 启动项

    在Fedora 20里面,Fedora 使用了systemd作为系统与服务的管理工具,这个守护进程是系统开机后第一个开启的进程,pid 为1.systemd扮演着初始化系统的角色,主要用于开启与维护系 ...

  2. DBA 经典面试题(5)

    国外公司的Oracle DBA试题 Oracle DBA Interview Questions 1. How many memory layers are in the shared pool? 2 ...

  3. linux 虚拟机设置IP访问外网

    1 设置网络为桥接模式:(Vmware为例,安装过程中也可以设置) 选中当前的操作系统,点击虚拟机-->设置-->硬件-->网络设备器,勾选桥接模式 2 修改网络配置在命令行界面输入 ...

  4. VS快捷编码方式

    概念: 代码段是将预先定义好的可重用代码块快速插入到代码文件中,代码段提高了开发效率,增强了代码的可重用性:既节约了时间,又实现了不同开发人员间代码的共享.同时也可保证同一项目中代码风格的统一.   ...

  5. robots.txt网站爬虫文件设置

    目录: 什么是robots.txt robots.txt使用误区 robots.txt使用技巧 什么是robots.txt? robots.txt是搜索引擎中访问网站的时候要查看的第一个文件.Robo ...

  6. linux在文件打包和压缩

    1. 打包和压缩文件 linux现在经常使用gzip和bzip2要压缩的文件.tar压缩文件. 经常使用的扩展: *.gz   gzip压缩文件 *.bz2  bzip2压缩的文件 *.tar   t ...

  7. git错误:fatal: Not a git repository (or any of the parent directories): .git

    git错误:fatal: Not a git repository (or any of the parent directories): .git 我用git add file添加文件时出现这样错误 ...

  8. Git 提供篇

    1. Git自动补全 假使你使用命令行工具运行Git命令,那么每次手动输入各种命令是一件很令人厌烦的事情.为了解决这个问题,你可以启用Git的自动补全功能,完成这项工作仅需要几分钟. 为了得到这个脚本 ...

  9. 线程:CyclicBarrier同步工具类

    一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点.比如公司组织活动出去玩,需要在公司门口一起搭车去.每个人从家里出发去公司门口,到达的时间肯定先后不一样,所以公司的车要一直等待,等所有人 ...

  10. asp.net repeater控件操作

    Repeater控件和DataList控件,可以用来一次显示一组数据项.比如,可以用它们显示一个数据表中的所有行. Repeater控件完全由模板驱动,提供了最大的灵活性,可以任意设置它的输出格式. ...