转自:http://www.cnblogs.com/monotone/archive/2012/11/16/2773772.html

  

  参考资料:

  

《程序员的自我修养》3.5.3以及3.5.4小节。

符号修饰的由来


20世纪70年代以前,编译器编译代码时产生的目标文件中,符号名与相应的变量和函数的名字是一样的,随着编程语言的发展,例如C语言,如果一个C 语言程序要使用这些库的话,其自身就不能使用这些库中已经声明了的函数和变量的名字作为符号名,否则将会跟现有的目标文件发生名称冲突。

为了防止这类符号名冲突,各平台下的编程语言规定了各自的符号生成语法。如C在UNIX下在函数名和变量前加下划线作为符号名。这种给函数名增加特定符号来使其符号名唯一的方式就是符号修饰。

这种简单的符号修饰没有从根本上解决符号冲突的问题,比如同一种编程语言编写的目标文件之间还有可能产生符号冲突,当程序很大时,不同的部门之间也有可能会产生符号冲突,于是C++这类语言开始加上了命名空间(namespace)来解决多模块符号冲突问题。

为了支持C++拥有类、继承、虚机制、重载、命名空间等这些特性,人们发明了符号修饰修饰(或者称为符号改编),被修饰之前的函数名称以及其返回值和参数等信息,被称为函数签名。不同的编译器采用不同的名字修饰方法,因为导致由不同的编译器编译产生目标文件无法正常相互链接。

extern “C”


C++为了与C兼容,在符号的管理上,C++有一个用来声明或定一个C的符号的“extern ”C"”关键字用法:

extern "C" int add(int a, int b);
extern "C" int a;
// 或者
//extern "C"
//{
// int add(int a, int b);
// int a;
//};

注意:extern “C”中的C必须大写。

C++编译器会将在extern “C”的大括号内(或者后面的)代码当作C语言代码来处理。所以很明显,上面的代码就是为了将add函数和变量a声明成C的方式,因为有可能他们的定义是放在.c文件中的。

举例:

// c_code.h file in c_project project
#pragma once
int add(int a, int b);
// c_code.c file in c_project project
#include "c_code.h"
int add(int a, int b)
{
return a + b;
}

如果我们有一个main.c,代码如下

#include "c_code.h"
#include "stdio.h"
#include <conio.h>
int main(void)
{
printf("%d + %d = %d\n", , , add(,)); _getch();
return ;
}

编译链接运行都没错。那么现在我们把main.c文件重命名成main.cpp,代码不变,会发生链接错误: error LNK2019: unresolved external symbol "int __cdecl add(int,int)" (?add@@YAHHH@Z) referenced in function _main。这里的链接错误就是指没有找到符号“?add@@YAHHH@Z”。这个符号就是"int __cdecl add(int,int)"修饰后的符号名。

原因是C的符号修饰方式和C++的符号修饰方式不同,导致同样的声明会产生不同的符号名。这里main.cpp是一个cpp文件,会使用cpp的方式进行编译链接,而add的定义却是在.c文件里面,因而会链接不上。

接下来尝试使用extern “C”,让add函数在main.cpp中编译链接时,使用C的符号修饰方式,这里有两种方法:

extern "C" int add(int a, int b);        // 在这里声明add函数,不使用c_code.h头文件
int main(void)
{
printf("%d + %d = %d\n", , , add(,)); _getch();
return ;
}
extern "C"            // 显示的将c_code.h所有声明都使用C方式修饰
{
#include "c_code.h"
}
#include "stdio.h"
#include <conio.h>
int main(void)
{
printf("%d + %d = %d\n", , , add(,)); _getch();
return ;
}

以上两种方式其实是一样的,这里因为c_code.h中只有一个函数,所以使用第一种方法也很简单,但是如果头文件中有很多函数声明,使用第二种方法就简单多了。

由于C++是对C语言的扩展,我们常常会需要使用C的库函数,这些库函数的定义都是用.c文件实现的,那么为了避免每次我们在使用库函数的时候,都去用extern “C”关键字修饰其头文件,这些标准库头文件中往往都包含了如下代码来解决这个问题。

// c_code.h file in c_project project
#pragma once #ifdef __cplusplus
extern "C"{
#endif int add(int a, int b); #ifdef __cplusplus
}
#endif

意思是,如果编译的时候发现__cplusplus宏已经定义,则给后面的函数声明都加上extern “C”,以用C方式修饰,否则不处理。而__cplusplus宏是C++编译器在编译C++程序时默认定义的宏(其实我们也可以自己在.cpp文件的顶 部定义一个宏),显然,在.cpp文件中包含这种头文件的时候,就会将这些个函数声明成用C方式修饰,而如果在.c文件中包含时,就不会加上extern “C”修饰。这就解释了为什么之前main.c能直接编译通过,而main.cpp不能的原因。

后记


其实这篇博文主要就为了介绍extern “C”的用法,在网上能搜到很多用法介绍,我也看了好几篇,但是就是感觉有种不是很透彻的感觉,于是就去找了这本参考书,看完之后就比较明了了。所以说,有些知识看起来很简单,但是如果不是很透彻理解的话,还是太容易忘记。

友情提示: 写个博客对于我来说不容易,如果此文是我原创,烦请转载加个链接http://www.cnblogs.com/monotone/。谢谢。

符号修饰与函数签名、extern “C”(转载)的更多相关文章

  1. c++11 符号修饰与函数签名、函数指针、匿名函数、仿函数、std::function与std::bind

    一.符号修饰与函数签名 1.符号修饰 编译器将c++源代码编译成目标文件时,用函数签名的信息对函数名进行改编,形成修饰名.GCC的C++符号修饰方法如下: 1)所有符号都以_z开头 2)名字空间的名字 ...

  2. 转载----c++ static修饰的函数作用与意义

    static修饰的函数叫做静态函数,静态函数有两种,根据其出现的地方来分类: 如果这个静态函数出现在类里,那么它是一个静态成员函数: 静态成员函数的作用在于:调用这个函数不会访问或者修改任何对象(非s ...

  3. DLL模块例2:使用__declspec(dllexport)导出函数,extern "C"规范修饰名称,隐式连接调用dll中函数

    以下内容,我看了多篇文章,整合在一起,写的一个例子,关于dll工程的创建,请参考博客里另一篇文章:http://www.cnblogs.com/pingge/articles/3153571.html ...

  4. 【c++基础】static修饰的函数作用与意义

    static修饰的函数作用与意义 static修饰的函数叫做静态函数,静态函数有两种,根据其出现的地方来分类: 如果这个静态函数出现在类里,那么它是一个静态成员函数: 静态成员函数的作用在于:调用这个 ...

  5. Python: 拷贝函数签名

    使用场景有很多,比如C API在Python下很多都变成了(*args, **kwargs)的参数,这时候可能需要为其添加一个更严格签名来约束参数. 查了许多资料,能有效的拷贝函数签名貌似只能通过动态 ...

  6. const修饰虚函数

    [1]程序1 #include <iostream> using namespace std; class Base { public: ; }; class Test : public ...

  7. 浅谈python函数签名

    函数签名对象,表示调用函数的方式,即定义了函数的输入和输出. 在Python中,可以使用标准库inspect的一些方法或类,来操作或创建函数签名. 获取函数签名及参数 使用标准库的signature方 ...

  8. const 修饰成员函数 前后用法(effective c++ 03)

    目录 const在函数后面 const修饰成员函数的两个作用 const在函数前面 总结 const在函数后面 类的成员函数后面加 const,表明这个函数不会对这个类对象的数据成员(准确地说是非静态 ...

  9. swift Equatable 函数签名的测试

    struct Degoo:Equatable { var lex:String var pex:String static func == (left:Degoo, right:Degoo) -> ...

随机推荐

  1. Spring Boot 2(一):【重磅】Spring Boot 2.0权威发布

    就在今天Spring Boot2.0.0.RELEASE正式发布,今天早上在发布Spring Boot2.0的时候还出现一个小插曲,将Spring Boot2.0同步到Maven仓库的时候出现了错误, ...

  2. [1143] [CTSC2008]祭祀river(最大独立集 || 偏序集最大反链)

    传送门 网上说这是偏序集最大反链,然而我实在不理解. 所以我换了一个思路,先用floyd,根据点的连通性连边, 问题就转换成了找出最多的点,使任意两个点之间不连边,也就是最大独立集. ——代码 #in ...

  3. 序列终结者(bzoj 1521)

    Description 网上有许多题,就是给定一个序列,要你支持几种操作:A.B.C.D.一看另一道题,又是一个序列 要支持几种操作:D.C.B.A.尤其是我们这里的某人,出模拟试题,居然还出了一道这 ...

  4. hdu_1085_Holding Bin-Laden Captive!_201404261008

    Holding Bin-Laden Captive! Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Ja ...

  5. CAS--CompareAndSwap原理

    1.CAS(Compare-and-Swap),即比较并替换,是一种实现并发算法时常用到的技术,Java并发包中的很多类都使用了CAS技术. CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更 ...

  6. Memcached与Spring集成的方式(待实践)

    主要是基于这几种方式http://www.cnblogs.com/EasonJim/p/7624822.html去实现与Spring集成,而个人建议使用Xmemcached去集成好一些,因为现在官方还 ...

  7. 编程精粹--编写高质量C语言代码(3):自己设计并使用断言(二)

    接着上一遍文章<<编程精粹--编写高质量C语言代码(2):自己设计并使用断言(一)>>,继续学习怎样自己设计并使用断言,来更加easy,更加不费力地自己主动寻找出程序中的错误. ...

  8. BSON结构

    BSON结构 flyfish 2015-7-24 主要解释bsonspec.org站点上的两个样例 {"hello": "world"}  hello为key. ...

  9. 笔记本电脑 联想 Thinkpad E420 无法打开摄像头怎么办

    1 计算机管理-右击USB视频设备(应该显示为黄色问号,表示驱动安装不成功),点击浏览计算机以查找驱动程序软件 2 选择"从计算机的设备驱动程序列表中选择",然后选择Microso ...

  10. P-Called-Party-ID头域

    典型的proxy server在路由 INVITE 请求到目标时插入 P-Called-Party-ID 头域.该头域用 porxy 收到请求的 Request-URI 填写. UAS 从几个已注冊的 ...