博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
再谈谈只针对C++编译器/链接器的extern “C“------C与C++的相互调用
阅读量:4141 次
发布时间:2019-05-25

本文共 3241 字,大约阅读时间需要 10 分钟。

      说明: 我用的编译器是VC++6.0, 支持.c和.cpp文件的编译。

 

      很多朋友对在变量或者函数前面加extern比较熟悉了, 但对于extern "C"则有点措手不及, 有时。为什么需要extern "C"呢? 我认为这都是c++惹的祸, 如果没有C++这个恐龙, 也就没有所谓的C与C++的相互调用, 那也就不用纠结什么extern "C"了。 

 

      在本文中, 我们来聊一下extern "C"的用法, 无论是笔试面试, 还是真正的项目开发, 你几乎总会遇到extern "C".   在笔试面试的时候, 没搞懂这个东西, 会被面试官鄙视啊, 丢掉offer. 在实际项目中, 则会经常出现编译错误, 纠结半天, 束手无策。

 

       一. C++如何调用C

 

       还是一贯的简约实战风格。 我们先来看一个最简单的C程序:

 

// 注意: 该代码是C程序, 请放在.c文件中, 这样确保是C编译器#include 
int main(){ fun(); return 0;}

 

       放在.c文件中, 这个程序可以通过编译, 但会提示:

warning C4013: 'fun' undefined; assuming extern returning int

      

       好, 我们再链接一下, 意料之中地出错了, 如下:

error LNK2001: unresolved external symbol _fun

 

       C编译器在抱怨了, 编译器说, 靠, 我去找_fun这个符号, 居然找不到。 究其原因: fun函数没有定义, 所以编译器查找的时候就没有_fun这个符号, 所以现在编译看不到, 所以就链接错误。(知识点补充: 如果C语言定义了fun函数, 那么C编译器会把它编译成_fun, 供别的模块链接)

 

      我们继续看代码:

 

// 注意: 该代码是C++程序, 请放在.cpp文件中, 这样确保是C++编译器#include 
int main(){ fun(); return 0;}

      放在.cpp文件中, 发现编译不过, 可见, C++编译器要严格很多啊。 好, 那我们想一个办法来暂时骗过C++编译器, 如下:

 

 

// 注意: 该代码是C++程序, 请放在.cpp文件中, 这样确保是C++编译器#include 
extern void fun(); // 暂时骗过C++编译器int main(){ fun(); return 0;}

      再用C++编译器来编译一下, 嗯, 通过了。 看来暂时确实被骗了。 我们再链接一下, 发现如下错误:

 

error LNK2001: unresolved external symbol "void __cdecl fun(void)" (?fun@@YAXXZ)

 

       这个时候, C++链接器开始要飚脏话了: 我靠, 你骗过了编译器, 你还想骗我?  之所以C++连接器会不爽, 是因为它去找名为“?fun@@YAXXZ”的函数后, 找不到。 为什么找不到呢? 因为压根就没有对应的定义。(知识点补充: 如果C++语言定义了fun函数, 那么C++编译器会把它编译成“?fun@@YAXXZ” 供别的模块链接, 这个名称好奇怪啊? 主要是基于C++重载函数考虑的。 当然, 不同编译器厂商的实现并不一致)

 

       于是, 问题就来了, 如果在.c文件中定义了一个fun函数, C编译器会把它编译成_fun, 此时, 如果是C++去调用fun函数, C++编译器自然会去找类似于“?fun@@YAXXZ”这样的符号, 找呀找, 找呀找, 最后找不到, 于是, 脾气来了, 干脆就给你报一个error LNK2001: unresolved external symbol "void __cdecl fun(void)" (?fun@@YAXXZ)错误, 为难一下不谙世事的程序猿, 并用微软式的傲慢略带调侃地说: 谁让你不懂extern "C"的。

 

       好了, 为了不被微软C++编译器鄙视, 我们来学一下extern "C", 提前给微软的C++编译器打个招呼, 对它说: 你链接fun函数的时候, 不要再用C++的规范啦, 要用C的规范, 也就是说, 你不要再去找什么“?fun@@YAXXZ”了, 你应该直接找_fun.   我们来看看程序:

 

// 注意: 该代码是C++程序, 请放在.cpp文件中, 这样确保是C++编译器#include 
extern "C" void fun(); // 暂时骗过C++编译器, 并对链接器说, 你要按照C规范链接, 去找_fun, 而不是"?fun@@YAXXZ"int main(){ fun(); return 0;}

      我们看到, 编译ok, 但链接的时候仍然有错, 如下:

 

error LNK2001: unresolved external symbol _fun

 

      看到没, C++编译器果然很听话, 确实是去找_fun, 但依然找不懂, 因为我们根本就没有定义fun函数。 好, 继续看代码:

 

// 注意: 该代码是C程序, 请放在.c文件中, 这样确保是C编译器#include 
void fun(){ printf("ok\n");}

 

// 注意: 该代码是C++程序, 请放在.cpp文件中, 这样确保是C++编译器#include 
extern "C" void fun(); // 暂时骗过编译器, 并对链接器说, 你要按照C规范链接, 去找_fun, 而不是"?fun@@YAXXZ"int main(){ fun(); return 0;}

       结果, 编译ok, 链接ok, 运行ok.

 

 

       综上所述: extern "C"的作用之一是:在C++调用C的时候, 告诉C++编译器, 编译的时候, 不要因为看不到fun而伤心;链接的时候,不要用默认的C++规范查找, 而要采用C规范去查找。

 

 

 

      二. C如何调用C++

 

      下面, 我们讨论一下C如何调用C++, 看代码:

 

// 注意: 该代码是C++程序, 请放在.cpp文件中, 这样确保是C++编译器#include 
extern "C" void fun() // 按C编译器的方式去编译,要生成_fun, 而不是"?fun@@YAXXZ"{ printf("ok\n");}

 

// 注意: 该代码是C程序, 请放在.c文件中, 这样确保是C编译器#include 
extern void fun(); // C编译器不严格,本可以去掉本句,但为了移植性, 最好不要去掉int main(){ fun(); // C编译器会去找_fun return 0;}

     

      编译ok, 连接ok, 运行ok.   我们注意到, 在上述C代码中, 有了extern void fun(); 此处不是extern "C" void fun(); 如果采用后者, 则C编译器会直接报错, 如下:

 

error C2059: syntax error : 'string'

 

      可见, C编译器根本就不认识extern "C",  也没有必要去认识它, 因为C编译器本来就是按照_fun的方式去查到的。

 

 

      综上所述: extern "C"的作用之二是:在C调用C++的时候, 告诉C++编译器, 编译的时候, 不要按照C++默认的规范去编译, 而应该按照C规范去编译。

 

      至于extern "C"的具体用法, 都离不开上面两个根本的东西, 万变不离其宗。

 

 

      最后, 千言万语汇成一句话: extern "C"针对不是C编译器/链接器, 而是C++编译器/链接器------>在C++编译器编译的时候, 按C规范编译, 在C++链接器链接的时候, 按C规范查找/链接。

 

 

 

 

 

转载地址:http://kygvi.baihongyu.com/

你可能感兴趣的文章
JSP的内置对象及方法
查看>>
android中SharedPreferences的简单例子
查看>>
android中使用TextView来显示某个网址的内容,使用<ScrollView>来生成下拉列表框
查看>>
andorid里关于wifi的分析
查看>>
Spring MVC和Struts2的比较
查看>>
Hibernate和IBatis对比
查看>>
Spring MVC 教程,快速入门,深入分析
查看>>
Android 的source (需安装 git repo)
查看>>
Commit our mod to our own repo server
查看>>
LOCAL_PRELINK_MODULE和prelink-linux-arm.map
查看>>
Simple Guide to use the gdb tool in Android environment
查看>>
Netconsole to capture the log
查看>>
Build GingerBread on 32 bit machine.
查看>>
How to make SD Card world wide writable
查看>>
Detecting Memory Leaks in Kernel
查看>>
Linux initial RAM disk (initrd) overview
查看>>
Timestamping Linux kernel printk output in dmesg for fun and profit
查看>>
There's Much More than Intel/AMD Inside
查看>>
apache和tomcat整合
查看>>
java虚拟机错误问题
查看>>