由于平时很少用到__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. UESTC_方老师和农场 2015 UESTC Training for Graph Theory<Problem L>

    L - 方老师和农场 Time Limit: 3000/1000MS (Java/Others)     Memory Limit: 65535/65535KB (Java/Others) Submi ...

  2. 在O(1)时间内删除单链表结点

    // 在O(1)时间内删除单链表结点 /* 思考: 很显然链表是一个节点地址不连续的存储结构 删除节点一般很容易会想到是修改p节点的前一个节点的next为p->next 然而除非是双向链表,否则 ...

  3. 《Algorithms 4th Edition》读书笔记——2.4 优先队列(priority queue)-Ⅵ

    · 学后心得体会与部分习题实现 心得体会: 曾经只是了解了优先队列的基本性质,并会调用C++ STL库中的priority_queue以及 java.util.PriorityQueue<E&g ...

  4. 第04讲- Android项目目录结构分析

    学习内容: 1.        认识R类(R.java)的作用 R.java是在建立项目时自动生成的,这个文件是只读模式,不能更改.R类中包含很多静态类,且静态类的名字都与res中的一个名字对应,即R ...

  5. PPT去掉图片白色背景

    双击图片,点击菜单栏“删除背景”,用矩形框选中想要的区域,然后将鼠标焦点移到图片外,单击鼠标即可.

  6. NTP配置实践

    前言 NTP(Network Time Protocol,网络时间协议)是用来使网络中的各个计算机时间同步的一种协议.不管是平时使用的私人计算机还是在工作中搭建的服务器集群.时间的统一性和准确性是十分 ...

  7. 下拉框Html.DropDownList 和DropDownListFor 的经常用法

    一.非强类型: Controller: ViewData["AreId"] = from a in rp.GetArea()                             ...

  8. Android开发之去掉标题栏的三种方法,推荐第三种

    Android:去掉标题栏的三种方法和全屏的三种方法 第一种:一般入门的时候常常使用的一种方法 onCreate函数中增加下面代码: requestWindowFeature(Window.FEATU ...

  9. OJ2.0userInfo页面Modify逻辑bug修复,search功能逻辑实现

    这周的主要任务:userInfo页面Modify逻辑bug修复,search功能逻辑实现. (一)Modify逻辑bug修复: 这里存在的bug就是在我们不重置password的时候依照前面的逻辑是不 ...

  10. NPOI之使用EXCEL模板创建报表

    因为项目中要用到服务器端创建EXCEL模板 无法直接调用EXCEL 查了下发现NPOI很方便很简单就实现了 其中走了点弯路 第一次弄的时候发现输出的值是文本不是数字型无法直接计算公式 然后又发现打开报 ...