C++中的extern “C”用法详解

胖杰吖_1369

胖杰吖_1369

2016-12-19

2389人浏览

原创

简单来说,extern “c”是c++声明或定义c语言符号的方法,是为了与c兼容。说来容易,要理解起来还是得费些周折,首先我们要从c++和c的区别说起。

符号

大家都知道,从代码到可执行程序需要经过编译和链接两个过程,其中编译阶段会做语法检测,代码展开,另外它还会做一件事,就是将变量转成符号,链接的时候其实是通过符号来定位的。编译器在编译C和C++代码时,将变量转成符号的过程是不同的。本文所使用的编译器为gcc4.4.7

我们先来看一段简单的代码

/* hello.c */  
#include <stdio.h>  
  
const char* g_prefix = "hello ";  
  
void hello(const char* name)  
{  
    printf("%s%s", g_prefix, name);  
}</stdio.h>

注意,这里的文件名为hello.c,我们执行编译gcc -c hello.c得到目标文件hello.o,在Linux下用nm查看目标文件的符号表得到如下结果($符号代表shell命令提示符)

$ nm hello.o  
0000000000000000 D g_prefix  
0000000000000000 T hello  
                 U printf

这是C代码编译后的符号列表,其中第三列为编译后的符号名,我们主要看自己定义的全局变量g_prefix和函数hello,它们的编译后的符号名和代码里的名字是一样的。我们将hello.c重命名为hello.cpp,重新编译gcc -c hello.cpp得到hello.o,在用nm查看,结果如下

0000000000000000 T _Z5helloPKc  
                 U __gxx_personality_v0  
0000000000000000 D g_prefix  
                 U printf

这是C++代码编译后的符号列表,gcc会自动根据文件后缀名来识别C和C++代码,这时我们发现g_prefix的符号没变,但函数hello的符号变成了_Z5helloPKc,这就说明gcc在编译C和C++代码时处理方式是不一样的,对于C代码,变量的符号名就是变量本身(在早期编译器会为C代码变量前加下划线_,现在默认都不会了,在编译时可以通过编译选项-fno-leading-underscore和-fleading-underscore来显式设置),而对于C++代码,如果是数据变量并且没有嵌套,符号名也是本身,如果变量名有嵌套(在名称空间或类里)或者是函数名,符号名就会按如下规则来处理


1、 符号以_Z开始
2、 如果有嵌套,后面紧跟N,然后是名称空间、类、函数的名字,名字前的数字是长度,以E结尾
3、 如果没嵌套,则直接是名字长度后面跟着名字
4、 最后是参数列表,类型和符号对应关系如下

    int    -> i  
    float  -> f  
    double -> d  
    char   -> c  
    void   -> v  
    const  -> K  
    *      -> P

这样就很好理解为什么C++代码里的void hello(const char*)编译之后符号为_Z5helloPKc(PKc翻译成类型要从右到左翻译为char const *,这是编译器内部的表示方式,我们习惯的表示方式是const char*,两者是一样的),c++filt工具可以从符号反推名字,使用方法为c++filt _Z5helloPKc

这样也很容易理解为什么C++支持函数重载而C不支持了,因为C++将函数修饰为符号时把函数的参数类型加进去了,而C却没有,所以在C++下,即便函数名相同,只要参数不同,它们的符号名是不会冲突的。我们可以通过下面一个例子来验证变量名和符号的这种关系。

LOVO AI
LOVO AI

一款AI语音生成和文本转语音平台,可根据文字生成自然语音和配音内容,适用于视频、广告、课程及其他音频制作。

下载
/ * filename : test.cpp */  
#include <stdio.h>  
  
namespace myname  
{  
    int var = 42;  
}  
  
extern int _ZN6myname3varE;  
  
int main()  
{  
    printf("%d\n", _ZN6myname3varE);  
    return 0;  
}</stdio.h>

这里我们在名称空间namespace定义了全局变量var,根据前面的内容,它会被修饰为符号_ZN6myname3varE,然后我们手动声明了外部变量_ZN6myname3varE并将其打印出来。编译并运行,它的值正好就是var的值

$ gcc test.cpp -o test -lstdc++  
$ ./test  
42

extern "C"

有了符号的概念我们再来看extern “C”的用法就很容易了

extern "C"  
{  
    int func(int);  
    int var;  
}

它的意思就是告诉编译器将extern “C”后面的括号里的代码当做C代码来处理,当然我们也可以以单条语句来声明

extern "C" int func(int);  
extern "C" int var;

这样就声明了C类型的func和var。很多时候我们写一个头文件声明了一些C语言的函数,而这些函数可能被C和C++代码调用,当我们提供给C++代码调用时,需要在头文件里加extern “C”,否则C++编译的时候会找不到符号,而给C代码调用时又不能加extern “C”,因为C是不支持这样的语法的,常见的处理方式是这样的,我们以C的库函数memset为例

#ifdef __cplusplus  
extern "C" {  
#endif  
  
void *memset(void*, int, size_t);  
  
#ifdef __cplusplus  
}  
#endif

其中__cplusplus是C++编译器定义的一个宏,如果这份代码和C++一起编译,那么memset会在extern "C"里被声明,如果是和C代码一起编译则直接声明,由于__cplusplus没有被定义,所以也不会有语法错误。这样的技巧在系统头文件里经常被用到。



更多C++中的extern “C”用法详解相关文章请关注PHP中文网!

C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!

相关文章

c++速学教程(入门到精通)
c++速学教程(入门到精通)

c++怎么学习?c++怎么入门?c++在哪学?c++怎么学才快?不用担心,这里为大家提供了c++速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

2026.10.28

40

16

Vite 如何搭配 Vue.js
Vite 如何搭配 Vue.js

Vite搭配Vue.js专题汇集创建Vue 3项目、配置开发环境、路径别名、Vue插件、代理、多环境构建与调试的已审核文章,帮助读者完成从初始化到日常开发的配置。

2026.10.28

20

15

Vite 项目目录怎么看
Vite 项目目录怎么看

Vite项目目录专题汇集入口文件、src源码、public静态资源、路径别名、项目根目录和构建输出目录相关的已审核文章,帮助读者理解各文件夹的用途与引用方式。

2026.10.28

40

10

Gradle插件开发入门教程
Gradle插件开发入门教程

本专题介绍Gradle插件开发的基本流程,涵盖插件类编写、自定义任务注册、扩展参数设计和插件应用方法,帮助你理解插件如何组织构建逻辑,减少多个项目之间的重复配置。

2026.10.22

120

11

Gradle Wrapper配置教程
Gradle Wrapper配置教程

本专题介绍Gradle Wrapper的基本作用,涵盖文件组成、生成方法、版本配置和任务调用,帮助你理解项目如何使用指定的Gradle版本,减少不同开发环境带来的构建差异。

2026.10.22

80

10

Golangci-lint检查器使用教程
Golangci-lint检查器使用教程

本专题整理Golangci-lint常用Linters的用途与配置方法,覆盖错误处理、无效代码、安全、复杂度、命名和依赖检查,帮助项目合理选规则。

2026.10.22

80

15

Gradle怎么构建Java项目
Gradle怎么构建Java项目

本专题介绍Gradle Java项目构建方法,涵盖Java插件配置、依赖声明、源码编译、测试执行和打包检查,帮助你理解各阶段的任务关系,逐步完成项目构建。

2026.10.22

40

10

PHP时间戳漏洞修复教程
PHP时间戳漏洞修复教程

本专题汇总PHP常见时间戳漏洞,包括2038年溢出、时区漂移、时间戳伪造、重放攻击、时间盲注与日志审计失效等,并给出升级64位环境、使用 DateTimeImmutable、统一UTC、严格超时校验、预处理绑定、hash_equals 比对等修复方案,帮助开发者快速排查和加固时间相关代码。

2026.10.22

80

25

Golangci-lint配置文件与规则设置
Golangci-lint配置文件与规则设置

本专题聚焦Golangci-lint YAML配置文件,讲清运行参数、规则启停、目录排除、问题过滤及严重级别设置,便于团队统一检查标准。

2026.10.22

40

15

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.7万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 134.1万人学习