最近在学Lua,关于Lua编译的地方,由于Lua是用C语言写的,在C++中使用C语言的函数,需要使用extern “C“编译才能过,之前貌似没有接触过这方面的知识,写个blog纪念一下,顺便整理一下extern关键字的作用。
一.extern “C”的作用
这是我第一次见,因为之前一直没有过C++去调用
C的函数。关于C++和C语言混合编程其实还有一些地方要注意的。比如,如果编译时出现下面的错误:
error:预编译头文件来自编译器的早期版本,或者预编译头为 C++ 而在 C 中使用它(或相反)
这是因为C语言和C++的预编译头不同,如果C语言文件较少,可以将C语言文件的属性->预编译头改为不使用预编译头,就可以解决问题啦。
而如果这时候直接在cpp文件中使用C语言的函数,则会出现下面的错误:
>C++Test.obj : error LNK2019: 无法解析的外部符号 "int __cdecl CTest(int,int)" (?CTest@@YAHHH@Z),该符号在函数 _wmain 中被引用。
错误发生在link阶段,连接器没有找到int_cdecl CTest(int int)这个函数,而我们明明写了CTest这个函数。原因是C++和C的编译方式不同,C++支持函数重载,所以在编译的时候,函数名称后面通常会加上一大堆奇葩的东东,一般是记录重载的信息的,具体被编译成什么样需要看编译器的脾气。而C语言不支持重载,所以编译得比较”纯净“,而我们按照C++的编译方式去编译了一个C语言的函数,就会造成上面的错误。
在编译的时候,如果要包含C语言的东东,在C语言的头文件上下加上extern”C”就可以解决这个问题。
注:在C语言中不支持extern “C”这个关键字组合,所以要加上这一句话的话需要在C++引入C语言的头文件的部分来添加这个。如果在C语言中添加的话,会出现下面的错误:
error C2059: 语法错误:“字符串”
看一个C++混合编译C语言函数的例子:
C语言头文件:
#ifndef __CFILE_H_
#define __CFILE_H_
int CTest(int a, int b);
#endif
C语言实现文件:
#include "CFile.h"
int CTest(int a, int b)
{
return a + b;
}
C++文件:
// C++Test.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
using namespace std;
//使用extern "C"关键字,声明这个东东是C语言的,不要用C++的方式来
// __cplusplus是cpp文件内置的宏,作用为如果是cpp文件,才进行相关编译。这样写更好地控制了编译流。
#ifdef __cplusplus
extern "C"{
#endif
#include "CFile.h"
#ifdef __cplusplus
};
#endif
int _tmain(int argc, _TCHAR* argv[])
{
int a = 1;
int b = 2;
cout<<CTest(a, b)<<endl;
system("pause");
return 0;
}
这样写的话,再编译就可以通过啦!
运行结果如下:
3
请按任意键继续. . .
关于extern “C”要注意的地方:
1.在使用C语言写的lib或者dll时,包含的.h头需要写在extern “C”{//头文件}之中。
2.在C语言的文件中不要这么写,因为C语言不支持extern ”C“关键字。
3.在cpp文件中使用ifdef __cplusplus控制编译流,如果是cpp文件,才进行相关的编译操作。
关于extern “C”的解释有两篇文章讲的很详细:
http://www.cnblogs.com/rollenholt/archive/2012/03/20/2409046.html
http://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777431.html
二.extern的作用
extern关键字主要是声明这个变量已经在其他文件中声明过了(声明全局变量或者函数),如果编译过程中遇到带有extern的变量,就会去其他模块中寻找它的定义。但是注意,它只是声明而不是定义,也就是说,如果想要使用这个变量,只需要包含这个变量定义所在的位置的头文件即可。在编译阶段,虽然本模块找不到该函数或者变量,但是不会报错,会在连接时从定义的模块中找到该变量或者函数。
总之,extern就是用来声明这个东西已经在其他文件中声明过了。不管是变量还是函数,都可以使用extern函数这样声明。不过函数不使用extern和使用extern关键字声明没有太大的区别。而对于变量,如果全局变量定义和使用不在一个文件中,就会发生未定义的错误。而如果在这个文件中重新定义一个同名的变量,又会发生重定义的错误。所以,这种情况就需要使用extern关键字在本文件中声明即可。
关于extern和static: 两者其实是水火不容,因为extern的作用为让变量或者函数跨文件,而static的作用是让变量或者函数的作用域保留在本文件中,所以二者不能同时使用。 一般定义static全局变量都是在实现文件中,而不会再跑到头文件中去声明一份。
三.关于全局变量
貌似本人之前经常干这种把定义和声明放在一起的蠢事,直接在头文件定义并且声明了全局变量,这样的确比较方便,但是这样做非常不好,原因如下: 1.代码体积增大 2.如果这个头文件被多个文件包含的话,会造成重定义的错误。 3.在连接阶段,有多个定义会造成找不到函数体的错误。
所以我们要注意一点:
只在头文件中做声明,定义放在实现文件中。 好的方法是:在头文件中进行声明,使用extern,在实现文件中进行定义。 一个例子: StaticTest.h文件
#ifndef __STATIC_TEST_H_
#define __STATIC_TEST_H_
//声明变量(加上extern关键字,说明在此处只是声明,定义在其他文件中)
extern int num;
//声明函数
void TestFunc();
#endif
StaticTest.cpp文件
#include "stdafx.h"
#include <iostream>
#include "StaticTest.h"
using namespace std;
//定义变量
int num = 10;
//定义函数
void TestFunc()
{
cout<<num<<endl;
}
main函数文件
// C++Test.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
//引用了StaticTest.h文件,将num的声明包含进来,所以在此文件中可以直接使用num变量和TestFunc函数了
#include "StaticTest.h"
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
TestFunc();
cout<<num<<endl;
system("pause");
return 0;
}
我们在使用其他文件中定义的全局变量的时候,有下面几种情况: 1.在其他cpp文件中定义的时候,我们要在本文件中使用的话,只需要在本文件中再使用extern关键字声明一下这个变量即可。 2.这个全局变量在.cpp文件中定义了,但是在其对应的.h文件中已经使用extern关键字进行了声明,那么,我们直接包含这个.h文件就可以使用这个变量啦。 3.最糟糕的一种情况,这个变量的定义和声明都在头文件中,那么我们直接包含这个头文件就可以使用这个变量了,但是不推荐这么做。