再谈谈只针对C++编译器/链接器的extern "C"------C与C++的相互调用

      说明: 我用的编译器是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规范查找/链接。









你可能感兴趣的:(S1:,C/C++,s2:,软件进阶)