简介
C++
语言的创建初衷是 “a better C”,但是这并不意味着 C++
中类似 C
语言的全局变量和函数所采用的编译和连接方式与 C
语言完全相同。作为一种欲与 C
兼容的语言, C++
保留了一部分过程式语言的特点(被世人称为"不彻底地面向对象"),因而它可以定义不属于任何类的全局变量和函数。但是, C++
毕竟是一种面向对象的程序设计语言,为了支持函数的重载, C++
对全局函数的处理方式与 C
有明显的不同。
本文将介绍如何通过 extern “C” 关键字在 C++
中支持 C
语言 和 在C
语言中如何支持 C++
。
某企业曾经给出如下的一道面试题
为什么标准头文件都有类似以下的结构?
//head.h
#ifndef HEAD_H
#define HEAD_H
#ifdef __cplusplus
extern "C" {
#endif
/*...*/
#ifdef __cplusplus
}
#endif
#endif /* HEAd_H */
问题分析
- 这个头文件head.h可能在项目中被多个源文件包含(#include “head.h”),而对于一个大型项目来说,这些冗余可能导致错误,因为一个头文件包含类定义或inline函数,在一个源文件中head.h可能会被#include两次(如,a.h头文件包含了head.h,而在b.c文件中#include a.h和head.h)——这就会出错(在同一个源文件中一个结构体、类等被定义了两次)。
- 从逻辑观点和减少编译时间上,都要求去除这些冗余。然而让程序员去分析和去掉这些冗余,不仅枯燥且不太实际,最重要的是有时候又需要这种冗余来保证各个模块的独立。
为了解决这个问题,上面代码中的
#ifndef HEAD_H
#define HEAD_H
/*……………………………*/
#endif /* HEAD_H */
就起作用了。如果定义了HEAD_H,#ifndef/#endif之间的内容就被忽略掉。因此,编译时第一次看到head.h头文件,它的内容会被读取且给定HEAD_H一个值。之后再次看到head.h头文件时,HEAD_H就已经定义了,head.h的内容就不会再次被读取了。
那么下面这段代码的作用又是什么呢?
#ifdef __cplusplus
extern "C" {
#endif
/*.......*/
#ifdef __cplusplus
}
#endif
我们将在后面对此进行详细说明。
关于 extern “C”
前面的题目中的 __cplusplus
宏,这是C++中已经定义的宏,是用来识别编译器的,也就是说,将当前代码编译的时候,是否将代码作为 C++
进行编译。
首先从字面上分析extern “C”,它由两部分组成:extern关键字、“C”。下面我就从这两个方面来解读extern "C"的含义。
首先,被它修饰的目标是 extern
的;其次,被它修饰的目标是 C
的。
extern关键字
被 extern “C” 限定的函数或变量是 extern
类型的。
extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数。
被extern修饰的函数,需要在编译阶段去链接该目标文件,并且与extern对应的关键字是 static,被static修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其一般是不可能被extern “C”修饰的。
**注意:**例如语句 extern int a;
仅仅是对变量的声明,其并不是在定义变量 a
,声明变量并未为 a
分配内存空间。定义语句形式为 int a;
变量 a
在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。
被 extern “C” 修饰的变量和函数是按照 C
语言方式编译和连接的。</