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

 更新时间:2015年03月17日 09:45:03   投稿:junjie  
这篇文章主要介绍了C++中的extern “C”用法详解,简单来说,extern “C”是C++声明或定义C语言符号的方法,是为了与C兼容,需要的朋友可以参考下

简单来说,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); 


注意,这里的文件名为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++下,即便函数名相同,只要参数不同,它们的符号名是不会冲突的。我们可以通过下面一个例子来验证变量名和符号的这种关系。

复制代码 代码如下:

/ * filename : test.cpp */ 
#include <stdio.h> 
 
namespace myname 

    int var = 42; 

 
extern int _ZN6myname3varE; 
 
int main() 

    printf("%d\n", _ZN6myname3varE); 
    return 0; 
}  

这里我们在名称空间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++实现LeetCode(126.词语阶梯之二)

    C++实现LeetCode(126.词语阶梯之二)

    这篇文章主要介绍了C++实现LeetCode(126.词语阶梯之二),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-07-07
  • 利用stream实现一个简单的http下载器

    利用stream实现一个简单的http下载器

    这篇文章主要介绍了利用stream实现一个简单的http下载器的相关资料,需要的朋友可以参考下
    2015-03-03
  • C++实现LeetCode(162.求数组的局部峰值)

    C++实现LeetCode(162.求数组的局部峰值)

    这篇文章主要介绍了C++实现LeetCode(162.求数组的局部峰值),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-07-07
  • C++  boost 时间与日期处理详细介绍

    C++ boost 时间与日期处理详细介绍

    这篇文章主要介绍了C++ boost 时间与日期处理详细介绍的相关资料,这里提供实例代码,及实现效果,需要的朋友可以参考下
    2016-11-11
  • 使用C语言实现扫雷小游戏

    使用C语言实现扫雷小游戏

    这篇文章主要为大家详细介绍了使用C语言实现扫雷小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-05-05
  • C++中的std::funture和std::promise实例详解

    C++中的std::funture和std::promise实例详解

    在线程池中获取线程执行函数的返回值时,通常使用 std::future 而不是 std::promise 来传递返回值,这篇文章主要介绍了C++中的std::funture和std::promise实例详解,需要的朋友可以参考下
    2024-05-05
  • C语言实现随机生成6位数密码

    C语言实现随机生成6位数密码

    这篇文章主要为大家详细介绍了如何使用C语言实现一个简单而实用的随机密码生成器,该生成器将生成包含字母、数字和特殊字符的随机密码,有需要的小伙伴可以参考下
    2023-11-11
  • 浅谈C++中thread库join和detach的区别

    浅谈C++中thread库join和detach的区别

    这篇文章主要为大家介绍了C++中thread库join和detach的区别,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2021-11-11
  • C++实现简单插件机制原理解析

    C++实现简单插件机制原理解析

    这篇文章主要介绍了C++实现简单插件机制原理解析,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-02-02
  • C语言冷门知识之你可能没听过的柔性数组

    C语言冷门知识之你可能没听过的柔性数组

    柔性数组(Flexible Array)是引入的一个新特性,它允许你在定义结构体时创建一个空数组,而这个数组的大小可以在程序运行的过程中根据你的需求进行更改特别注意的一点是:这个空数组必须声明为结构体的最后一个成员,并且还要求这样的结构体至少包含一个其他类型的成员
    2021-10-10

最新评论