文章目录
- __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是什么
- 指定gcc编译 .c文件,__cplusplus没有定义,编译器按照c编译代码
- 指定gcc编译 .cpp文件,__cplusplus有定义,编译器按照c++编译代码
- 指定g++编译 .c文件,__cplusplus有定义,编译器按照c++编译代码
- 指定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” {…},下面是百度百科上的一段说明
使用场景的示例
准备四个文件
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 once
class my_handle_client
{
public:
void do_something(const char* name);
};
- 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);
}
这里是在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里
具体怎么设置,这里不清楚。这里说下造成编译报错的原因。原因就在各个目标文件之间进行链接的时候。因为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表示对应的实现不在当前文件内,需要在链接阶段,找到具体实现。