关于C和CPP中同名函数的思考

首先看一段代码:

***************************文件名:fun_c.c***********************
int fun(int a);
int fun(int a ,int b);
void fun(int a);
int fun(int a)
{
  printf("This is int fun(int a)\n");
}

int main()
{
    fun(1);
    return 0;
}

使用gcc编译:

fun_c.c:5: error: conflicting types for ‘fun’
fun_c.c:4: error: previous declaration of ‘fun’ was here
fun_c.c: In function ‘main’:
fun_c.c:14: error: too few arguments to function ‘fun’

使用g++编译:

fun_c.c:6: error: new declaration ‘void fun(int)’
fun_c.c:4: error: ambiguates old declaration ‘int fun(int)’
fun_c.c: In function ‘int fun(int)’:
fun_c.c:7: error: new declaration ‘int fun(int)’
fun_c.c:6: error: ambiguates old declaration ‘void fun(int)’

首先解释一下gcc和g++编译报错原因:

  1. gcc编译器默认将代码当做C语言去编译,认为函数名相同的函数为同一个函数,以上代码中声明了三个函数名相同的函数,所以gcc编译器报fun重复定义。
  2. g++编译器默认将代码当做CPP语言去编译,认为 int fun(int a); 和 void fun(int a); 两个函数是同一个函数。
    那为什么CPP只报这两个函数重定义呢?
    原因是:CPP拥有重载的特性,在同一个作用域中,函数名相同,参数表不同的函数,构成重载关系。 重载与函数的返回类型无关,与参数名也无关,而只与参数的个数、类型和顺序有关。CPP会将构成重载关系的函数解析成不同函数。

现在,我们不经要问:为什么CPP要引入重载?CPP是怎样将构成重载关系或不同作用域的函数解析成不同函数的呢?

1.为什么CPP要引入重载?

刚开始,编译器编译源代码生成目标文件时,符号名和函数名是一致的,但是随着后来程序越来越大,编写的目标文件不可避免的会出现符号冲突的问题。比如,当程序很大时,不同模块由不同部门开发,如果他们之间命名不规范,很有可能出现符号冲突的问题。于是呢,CPP等后来设计语言就开始引入了重载和命名空间来解决这个问题。

2.CPP是怎样将构成重载关系或不同作用域的函数解析成不同函数的呢?

首先,看一段代码:

int fun(int);
int fun(int,int);

class Cfun_class1{
    int fun(int);
    class Cfun_class2{
        int fun(int);
    };
};
namespace N {
    int fun(int);
    class Cfun_class3{
        int fun(int);
    };
}

以上代码中有6个同名函数fun,但是他们的参数类型和参数个数以及所在的namespace不同。CPP利用函数签名来识别不同的函数。函数签名包括函数名,参数类型,所在的类和namespace。以上6个函数的函数签名分别是:

函数签名
int fun(int)
int fun(int,int)
int::Cfun_class1:: fun(int)
int::Cfun_class1::Cfun_class2:: fun(int)
int::N:: fun(int)
int::N::Cfun_class3:: fun(int)

编译器在将CPP源代码编译成目标文件时,会利用某种名称修饰方法将函数签名编码成一个符号名。此外,以上的签名和修饰的方法不仅用在了函数上,CPP中全局变量和静态变量也用到了同样的方法。

通过以上的阐述,我们了解到C和CPP的编译链接规约是不同的,也就是说编译器会将C和CPP中国函数名编码成不同的符号名。这里我们想一个问题,如果一个项目中,即有C文件又有CPP文件,该怎么编译?这就涉及到了extern "C"

3.extern "C"

先看一段代码:

cHeader.h

#ifndef C_HEADER
#define C_HEADER

void print_fun(int i);

#endif C_HEADER

cHeader.c

#include <stdio.h>
#include "cHeader.h"
void print(int i)
{
    printf("cHeader %d\n",i);
}

main.c

#include "cHeader.h"

int main(int argc,char** argv)
{
    print(3);
    return 0;
}

编译链接:

gcc -c cHeader.c  -o cHeader.o
ar cqs libCheader.a cHeader.o
g++ -o mian main.cpp -L/root/Desktop -lCheader

结果:

/tmp/ccUgVIT7.o: In function `main':
main.cpp:(.text+0x19): undefined reference to `print(int)'
collect2: ld returned 1 exit status

编译后报错:未定义print函数。这就是因为编译器对CPP和C的编译规约不同,编译器认为print是一个CPP函数,将print编码成一个CPP符号,链接器拿着这个CPP符号在静态库中找不到对应的print函数,所以编译器认为print函数为定义。

为解决上述问题,CPP引入了extern "C"。将CHeader.h中代码改成如下代码,即可编译通过。

 extern "C"{
     void print(int i);
}

CPP编译器会把在extern "C"大括号内部的代码当做C代码来处理。这样编译器会将print函数编码成一个C符号,链接器就可以从静态库中找到对应的print函数。为进一步方便操作,CPP提供了宏__cplusplus ,CPP编译器会在编译CPP代码时默认这个宏,我们可以使用条件宏来判断当前的编译单元是不是CPP代码。具体代码如下:

#ifdef __cplusplus
extern "C"{
#endif
void print(int i);
#ifdef __cplusplus  
}
#endif

如果当前编译单元是CPP代码,那么void print(int i);会在 extern "C"里面被声明;如果是C代码,就直接声明。上面代码技巧几乎在所有的系统文件被用到。

4.弱引用和强引用

先看一段代码:

#include <stdio.h>
#include <stdlib.h>
void *malloc(unsigned long size)
{
     printf("I am void *malloc(unsigned long size).\n");
     return NULL;
}

 int main()
{
     char *buf = NULL;
   
     buf = (char *)malloc(10);
     if(NULL == buf)
               printf("failed.\n");
     else
     {
               printf("%p.\n", buf);
               free(buf);         
     }
     return 0;
}

编译:gcc -g -Wall -Werror test.c -o test 正确无错误输出
运行:./test
运行结果:I am void *malloc(unsigned long size). 正确

按照我们上面的说法C语言不支持同名函数,上面的函数应该报错才对。

这就涉及到了强引用和弱应用的概念。
强引用:若函数未定义,则链接时,链接器找不到函数位置报错;
而对于弱引用则不会报错,链接器默认函数地址为0。我们可以通过attribute((weak))来声明一个外部函数的应用为弱应用。下面,我们举一个例子来说明。

强引用实例:

int fun(int a);

int main()
{
    fun(1);
    return 0;
}

编译后报错: undefined reference to `fun',链接器找不到fun
弱引用实例:

 __attribute__((weak)) int fun(int a);

int main()
{
    fun(1);
    return 0;
}

编译不报错,运行报错:段错误。当main函数调用fun函数时,fun函数入口地址为0,发生了非法地址访问。改进:

 __attribute__((weak)) int fun(int a);
    
int main()
{
    if(fun) fun(1);
    return 0;
}

弱引用对于库来说十分重要。从上面的强弱引用的特点可看出:

  1. 当一个函数为弱引用时,不管这个函数有没有定义,链接时都不会报错,而且我们可以根据判断函数名是否为0来决定是否执行这个函数,这些函数的库就可以以模块、插件的形式和我们的引用组合一起,方便使用和卸载;
  2. 并且由于强引用可以覆盖弱引用可知,我们自己定义函数可以覆盖库中的函数。以下,我们给出一个例子予以说明。

fun_c.c

#include "weakref_test.h"

int fun(int a);
int fun(int a)
{
    printf("This is int fun(int a)\n");
}

int main()
{
    fun(1);
    return 0;
}

weakref_test.h

#include <stdio.h>
#include <stdlib.h>

__attribute__ ((weakref)) int fun(int a);

weakref_test.c

#include "weakref_test.h"

__attribute__ ((weakref)) int fun(int a)
{
    printf("This is __attribute__ ((weakref)) int fun(int a)\n");
}

编译后运行:This is int fun(int a)。

这个例子从说明了这一节开头抛出的问题,malloc在stdlib库中的定义为弱应用,“重写”的malloc为强引用,覆盖了stdlib库中的弱引用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,928评论 6 509
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,748评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,282评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,065评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,101评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,855评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,521评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,414评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,931评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,053评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,191评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,873评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,529评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,074评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,188评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,491评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,173评论 2 357

推荐阅读更多精彩内容

  • 喜欢的朋友可以关注收藏一下 本文实现了一个date类型,原版要求如下(括号为超出要求附加):为Date类实现如下成...
    Effortsto2017阅读 525评论 0 1
  • 概述:声明是将一个名称引入一个程序.定义提供了一个实体在程序中的唯一描述.声明在单个作用域内可以重复多次(类成员除...
    抓兔子的猫阅读 624评论 0 3
  • 1.面向对象的程序设计思想是什么? 答:把数据结构和对数据结构进行操作的方法封装形成一个个的对象。 2.什么是类?...
    少帅yangjie阅读 5,007评论 0 14
  • __block和__weak修饰符的区别其实是挺明显的:1.__block不管是ARC还是MRC模式下都可以使用,...
    LZM轮回阅读 3,323评论 0 6
  • 卿可知否, 千百年前的一次回首, 定格心中不变的永久。 烟花灿烂的桥头, 垂柳飞舞的雨后, 娉婷娇俏的佳人, 一眼...
    樱花子阅读 191评论 0 1