> 文章列表 > __cplusplus和extern “C“

__cplusplus和extern “C“

__cplusplus和extern “C“

文章目录

  • __cplusplus是什么
  • extern "C"
  • 使用场景的示例
  • 通过MinGW编译及查看下目标文件中的符号
    • 用gcc编译器添加 -c选项 使my_handle.c文件编译后生成my_handle.o文件,这里的 -o是 output的意思
    • nm命令 是GCC编译集合下最常用的查看目标文件中的符号的命令 -A选择可以展示目标文件名
    • 同理对my_handle_client.cpp用g++编译器编译
    • 在my_handle_client.cpp文件使用#include "my_handle.h",而不通过extern "C"{}对其进行包裹。
    • 符号类型的说明

__cplusplus是什么

  1. 指定gcc编译 .c文件,__cplusplus没有定义,编译器按照c编译代码
  2. 指定gcc编译 .cpp文件,__cplusplus有定义,编译器按照c++编译代码
  3. 指定g++编译 .c文件,__cplusplus有定义,编译器按照c++编译代码
  4. 指定g++编译 .cpp文件,__cplusplus有定义,编译器按照c++编译代码
    上面这四条都是正确的。
    __cplusplus是gcc编译器在编译.cpp文件或g++编译器在编译.c/.cpp文件时需要加入的宏定义;这个宏定义标志着编译器会把代码按C++的语法来解释。注意MSVC编译器不会加入这个预定义宏。只有类unix环境的GNU编译组件中才有。
#ifdef __cplusplus
extern "C"{ //告诉编译器,这部分代码按C语言的格式进行编译,而不是C++的
#endif
/*.................................* do something here*.................................*/
#ifdef __cplusplus
}
#endif

代码说明:#ifdef __cplusplus 如果当前文件中已经定义了__cplusplus了,就添加 extern “C”{ ,然后是 #endif,结束条件预编译指令。下面的也是一个判断#ifdef __cplusplus,如果已经定义就 添加},然后,#endif结束条件预编译指令。前面我们说过了 __cplusplus有定义,这个源文件会按照c++编译代码,但是我们又想让一部分指定的代码安装C语言的风格进行编译。所以就得这样处理了。
这里为啥不这样写:如下:

#ifdef __cplusplus             
extern "C"{ //告诉编译器,这部分代码按C语言的格式进行编译,而不是C++的
/*.................................* do something here*.................................*/
}
#endif

是因为如果 __cplusplus 没有定义 那么像上面这样写 do something here这段代码就被屏蔽了,所以 为正常执行 do something here 这里的C代码,我们要把extern “C”{和}用相同条件编译指令分别隔离开。

extern “C”

刚才我们说了添加 extern “C” {…}的作用。下面说下我们为什么要添加 extern “C” {…},下面是百度百科上的一段说明
__cplusplus和extern “C“

使用场景的示例

准备四个文件
1.my_handle.h

#ifndef __MY_HANDLE_H__
#define __MY_HANDLE_H__typedef unsigned int result_t;
typedef void* my_handle_t;my_handle_t create_handle(const char* name);
result_t operate_on_handle(my_handle_t handle);
void close_handle(my_handle_t handle);#endif  /*__MY_HANDLE_H__*/

2.my_handle.c 中引入my_handle.h

#include "my_handle.h"
my_handle_t create_handle(const char* name)
{return (my_handle_t)0;//即返回 NULL(空指针)
}
result_t operate_on_handle(my_handle_t handle)
{return 0;
}
void close_handle(my_handle_t handle)
{}

3.my_handle_client.h

#pragma onceclass my_handle_client
{
public:void do_something(const char* name);
};
  1. my_handle_client.cpp 引入 my_handle.h和my_handle_client.h
//extern "C" {
//	#include "my_handle.h"
//}
#include "my_handle.h"
#include "my_handle_client.h"
void my_handle_client::do_something(const char* name)
{my_handle_t handle = create_handle(name);(void) operate_on_handle(handle);close_handle(handle);}

__cplusplus和extern “C“
这里是在Windows下vs上演示的。下面我们说下报这个错的原因。
我们知道代码的编译流程,从 预编译 到 编译 到 汇编 最终得到 目标文件。window的msvc下生成的是.obj文件。而gcc/g++下生成的是.o文件 都是目标文件。我们写的头文件在预编译阶段 是被拷贝到引入此头文件源文件中的。所以.h文件不参与预编译阶段之后的操作。我们写的每个源文件都是一个编译单元。对应的生成一个同名的.o或.obj的目标文件。
下面说下目标文件里面都有啥,目标文件有函数名及静态存储区中各个变量的类型 及符号。一般情况下符号名和变量名一样。而函数名的符号名 分C/C++下的编译。C编译器下编译的源文件中函数名和符号名一致。而C++编译器下编译源文件 由于C++中存在函数重载机制,编译后目标文件中的符号名和源文件中的函数名相差甚远。
这里我本打算 在window下用msvc下(D:\\software\\visual_studio_2019\\IDE\\VC\\Tools\\MSVC\\14.29.30133\\bin\\Hostx64\\x86)提供的dumpbin命令查看下obj文件的内容。但是不支持。经过网上https://stackoverflow.com/questions/11849853/how-to-list-functions-present-in-object-file里__cplusplus和extern “C“
具体怎么设置,这里不清楚。这里说下造成编译报错的原因。原因就在各个目标文件之间进行链接的时候。因为my_handle_client.o文件中调用了三个外部文件中的函数。链接的时候要查找对应的函数实现。my_handle_client.o文件中这三个外部调用函数的符号分别变成了void * __cdecl create_handle(char const *) 和 unsigned int __cdecl operate_on_handle(void *) 和 void __cdecl close_handle(void *),因为把头文件my_handle.h里的代码,按c++编译器来编译了。但是 my_handle.o 是通过MSVC中的C编译器编译来的这三个函数的符号是create_handle,operate_on_handle,close_handle。my_handle_client.o目标文件根据它自己里面的这三个函数符号,去从其他 目标文件里遍历查找对应的函数符号,发现找不到。所以报了无法解析的外部符号的错误提示。所以这里的my_handle_client.cpp中的#include "my_handle.h"要指定通过C编译器来编译,即用extern “C” {}来包起来,这里为啥没加__cplusplus宏定义 是因为 我们的编译环境不是GCC,而是MSVC 。

extern "C" {#include "my_handle.h"
}
//#include "my_handle.h"
#include "my_handle_client.h"
void my_handle_client::do_something(const char* name)
{my_handle_t handle = create_handle(name);(void) operate_on_handle(handle);close_handle(handle);}

代码改成上面这样就可以找到响应的外部符号。

通过MinGW编译及查看下目标文件中的符号

由于我们说过通过msvc下的命令查看不了目标.obj文件的内容。这里由于之前安装过QT,QT Creator中自带MinGW功能。将D:\\software\\QT\\qt5.12.12\\Tools\\mingw730_32\\bin或这个D:\\software\\QT\\qt5.12.12\\Tools\\mingw730_64\\bin添加到系统环境变量下。就可以使用gcc或者g++以及其他MinGW中提供的命令。
需要通过gcc/g++对源进行重新编译。如果 直接用MinGW下的命令操作或查看MSVC环境中编译的.obj文件会识别不了文件。必须重新编译。

用gcc编译器添加 -c选项 使my_handle.c文件编译后生成my_handle.o文件,这里的 -o是 output的意思

D:\\vs_project\\sln_name_001\\sln_pro_001>gcc -c my_handle.c -o my_handle.o

nm命令 是GCC编译集合下最常用的查看目标文件中的符号的命令 -A选择可以展示目标文件名

D:\\vs_project\\sln_name_001\\sln_pro_001>nm -A my_handle.o
my_handle.o:00000000 b .bss
my_handle.o:00000000 d .data
my_handle.o:00000000 r .eh_frame
my_handle.o:00000000 r .rdata$zzz
my_handle.o:00000000 t .text
my_handle.o:00000014 T _close_handle
my_handle.o:00000000 T _create_handle
my_handle.o:0000000a T _operate_on_handle

#这里解释一下:
第一列是目标文件名(my_handle.o),
第二列 是符号的偏移或符号值(00000000),
第三列是符号类型(b )。
第四列是符号名(.bss)

同理对my_handle_client.cpp用g++编译器编译

D:\\vs_project\\sln_name_001\\sln_pro_001>g++ -c my_handle_client.cpp -o my_handle_client.o

D:\\vs_project\\sln_name_001\\sln_pro_001>nm -A my_handle_client.o
my_handle_client.o:00000000 b .bss
my_handle_client.o:00000000 d .data
my_handle_client.o:00000000 r .eh_frame
my_handle_client.o:00000000 r .rdata$zzz
my_handle_client.o:00000000 t .text
my_handle_client.o:00000000 T __ZN16my_handle_client12do_somethingEPKc
my_handle_client.o: U _close_handle
my_handle_client.o: U _create_handle
my_handle_client.o: U _operate_on_handle

#注由于我们在my_handle_client.cpp文件已经对#include "my_handle.h"通过extern “C”{}进行包裹。
#所以这里的通过从头文件my_handle.h中拷贝过来的代码,通过C编译器来编译。得到的函数符号和上面的函数符号一样。

在my_handle_client.cpp文件使用#include “my_handle.h”,而不通过extern “C”{}对其进行包裹。

D:\\vs_project\\sln_name_001\\sln_pro_001>g++ -c my_handle_client.cpp -o my_handle_client.o

D:\\vs_project\\sln_name_001\\sln_pro_001>nm -A my_handle_client.o
my_handle_client.o:00000000 b .bss
my_handle_client.o:00000000 d .data
my_handle_client.o:00000000 r .eh_frame
my_handle_client.o:00000000 r .rdata$zzz
my_handle_client.o:00000000 t .text
my_handle_client.o: U __Z12close_handlePv
my_handle_client.o: U __Z13create_handlePKc
my_handle_client.o: U __Z17operate_on_handlePv
my_handle_client.o:00000000 T __ZN16my_handle_client12do_somethingEPKc

可以看到这里的函数名已经面目全非了,链接的时候 ,就无法根据函数符号找到对应的具体函数实现了。

符号类型的说明

大写的T表示 此符号对应的函数或其他变量名的实现就在当前文件内。大写的U表示对应的实现不在当前文件内,需要在链接阶段,找到具体实现。