__cplusplus和extern “C“

2023-12-02 19:30
文章标签 extern cplusplus

本文主要是介绍__cplusplus和extern “C“,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • __cplusplus是什么
  • 说明下在MSVC下也可以识别__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++的语法来解释。注意当源文件为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 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);}

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

这篇关于__cplusplus和extern “C“的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/446524

相关文章

extern之于变量、之于函数

背景:前几天一个同学问阿楞:“我在##.cpp文件里面已经定义了一个data,那么我想在main函数里面用到这个data的值,该怎么办?”,其实阿楞很菜,也不知道,后来经过谷歌度娘一番教导,方才初窥extern的门道。                                     如若有误,烦请更正! 1.extern可置于变量或函数前,表示变量或者函数的定义在别的文件中,

头文件中extern “C”的理解

C语言的. h头文件常会看到如下做法: #ifdef __FUNC_H_ #define _FUNC_H_ #ifdef __cplusplus__ extern "C" { #endif #include<stm32f10x.h> int foo(char ,int ); #ifdef __cplusplus__ } #endif #endif 开始看别人的代码中类似这样

宏__cplusplus/////ifnbsp;define…

在C与C++混合编写的代码中,我们常常会在头文件里看到如下的声明:   #ifdef __cplusplus //如果定义了表示是c++编译器extern "C" {  #endif  // 在这里写标准c程序   #ifdef __cplusplus  }  #endif   __cplusplus是c++编译器内部定义的宏,如果使用的c编译器__cplusplus不会被定义,所以它用来区分

C++中的 extern “C“

在 C++中,extern "C"是一个链接指示符,用于告诉 C++编译器以 C 语言的方式进行链接。 一、作用 混合编程:当 C++程序需要调用 C 语言编写的函数或库时,使用extern "C"可以确保 C++编译器正确地识别和链接这些 C 函数。因为 C 和 C++的函数命名规则和调用约定可能不同,extern "C"可以解决这种不兼容性。兼容性:对于一些遗留的 C 代码库,或者需要与其

extern:如果在a.c文件中定义了一个全局函数func1,并未在a.h中声明,那么b.c可以直接使用a.c中的func1吗

如果在a.c文件中定义了一个全局函数func1,并未在a.h中声明,那么b.c可以直接使用a.c中的func1吗? 在C语言中,如果a.c文件中定义了一个全局函数,并且这个函数的声明(也就是它的原型)没有在头文件(如a.h)中给出,那么b.c文件不能直接使用a.c中的这个函数,除非在b.c中直接包含了该函数的完整定义(这通常是不推荐的做法,因为它违反了代码的封装和模块化原则)。 然而,在实际的

extern:c语言中的函数可以重复声明吗

在C语言中,函数可以被多次声明,但是有几个要点需要注意: 1.重复声明必须保持一致:函数的多次声明必须完全相同,包括返回类型、函数名和参数列表(包括参数的类型和顺序)。如果声明的任何部分不一致,编译器将会报错。 2.声明的目的:在C语言中,声明的主要目的是向编译器提供函数的存在性和它的一些基本信息(如返回类型和参数列表),以便编译器可以在实际的函数调用之前就知道这些信息。函数声明通常放在头文件

extern “c“使用问题

extern "c"使用问题 extern "c"使用位置c调用c++使用extern "C"使用C风格的函数封装使用函数指针 关于和同事讨论 "extern "c" 应该只会出现在.h文件? extern "c"使用位置 首先,先看下AI的回答 关于在C文件中是否需要添加 extern "C" 的问题,答案通常是不需要。让我解释一下原因和相关细节:C文件与C++文件的区别

C语言extern调用外部函数

> ls xuanzeSort.c common.c > cat common.c #include<stdio.h>//公共函数文件//打印long型数组的内容void print_array_long(long arr[],long length){long i;for(i = 0;i<length;i++){printf("%ld ",arr[i]);}printf("

【C/C++】存储类型auto、static、register、extern的作用

在C语言中,存储类型指的是数据在内存中的生命周期和可见性。C语言中主要有四种存储类型:自动(auto),静态(static),寄存器(register)和外部(extern)。 目录 autostaticregisterextern auto 在C语言中,auto是一个关键字,用于声明自动变量。 那么什么是自动变量呢?简单来说,自动变量是在函数内部定义的变量,它的生命周期与所在

#ifndef #define #endif 和 extern C的作用

ifndef #define #endif 防止头文件被重复引用 被重复引用”是指一个头文件在同一个cpp文件中被include了多次,这种错误常常是由于include嵌套造成的。比如:存在a.h文件#include "c.h"而此时b.cpp文件导入了#include “a.h” 和#include "c.h"此时就会造成c.h重复引用。 头文件被重复引用引起的后果: 有些头文件重复引用只是