__cplusplus和extern “C“

命运对每个人都是一样的,不一样的是各自的努力和付出不同,付出的越多,努力的越多,得到的回报也越多,在你累的时候请看一下身边比你成功却还比你更努力的人,这样,你就会更有动力。

导读:本篇文章讲解 __cplusplus和extern “C“,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

__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++的语法来解释。注意当源文件为cpp文件时,MSVC编译器也会加入这个预定义宏。上面说的gcc/g++是GNU编译集合下的。

说明下在MSVC下也可以识别__cplusplus

在这里插入图片描述

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

代码说明:#ifdef __cplusplus 如果当前文件中已经定义了__cplusplus(说明编译当前文件的环境为C++环境)了,就添加 extern “C”{ ,然后是 #endif,结束条件预编译指令。下面的也是一个判断#ifdef __cplusplus,如果已经定义就 添加},然后,#endif结束条件预编译指令。前面我们说过了 __cplusplus有定义,这个源文件会按照C++编译代码,但是我们又想让一部分指定的代码安装C语言的风格进行编译。所以就得这样处理了。这里加 extern “C” { } 就是想让它,按照C风格来编译,为啥非得外部再套一层 __cplusplus,避免当前文件的编译环境就是C环境,代码里还有extern “C” { }的情况,这种感觉很奇怪,为了是代码更灵活,当当前编译环境为C环境时,就只显示do something here部分,不再显示extern “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);
};
  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);

}

在这里插入图片描述
这里是在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表示对应的实现不在当前文件内,需要在链接阶段,找到具体实现。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/142622.html

(0)

相关推荐

发表回复

登录后才能评论
半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!