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