C++中的extern “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没有被定义,所以也不会有语法错误。这样的技巧在系统头文件里经常被用到。

(0)

相关推荐

  • C/C++中extern "C" 的作用分析

    我们经常会在C/C++程序中见到extern "C",这是一个很重要的概念.本文就来以实例形式讲述C/C++中extern "C"的作用.分享给大家供大家参考之用.具体分析如下: 作用:实现C和C++混合编程. 原理:C和C++编译器编译之后,函数名会编译成不同的名字,链接阶段名字查找会找不到目标,后面实例中会详解. 用法: ①.c文件中定义的函数,.cpp文件要调用时,该.cpp文件中要用extern "C"声明该函数: ②.反过来,.cpp文

  • 从汇编看c++中extern关键字的使用

    在c++中,extern关键字用来声明变量和函数,在声明函数的时候,有和没有extern的效果一样,即下面两条语句具有同样的效果: 复制代码 代码如下: extern void fun(); void fun(); 但是对于变量,有和没有extern就有区别,当有extern时,只是告知编译器存在这个变量,编译器并不为该变量分配存储空间,即真正的声明:若没有extern,则在声明的同时,编译器也为该变量分配存储空间. 下面是有extern的情形时的c++源码: 复制代码 代码如下: int ma

  • C/C++ 中extern关键字详解

    C/C++ 中extern关键字详解 在C/C++编程过程中,经常会进行变量和函数的声明和定义,各个模块间共用同一个全局变量时,此时extern就派上用场了. 定义 extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义,不需要分配内存,直接使用. 推荐:在.h中声明,因为在头文件定义的话,其他模块include此头文件,就会报重复定义错误 实验结论 1.在.h中声明 extern int g_a; 在.c中定义 int g_

  • C/C++混合编程之extern “C”的使用示例

    前言 本文主要给大家介绍了关于C/C++混合编程extern "C"使用的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 首先要明白: C++号称是C语言的超集,也确实,从语言的基本语法上,C++是包含所有C语言的语法的,而且C++为了兼容C,连C语言的标准库也被纳入到C++的标准库中,比如在C++中我们仍然可以使用<stdio.h> ,它就是C++标准库的一部分(注意最好用新的标准<cstdio> ,而不是老的<stdio&g

  • 实例详解C/C++中extern关键字

    1 基本解释:extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义.此外extern也可用来进行链接指定. 也就是说extern有两个作用,第一个,当它与"C"一起连用时,如: extern "C" void fun(int a, int b);则告诉编译器在编译fun这个函数名时按着C的规则去翻译相应的函数名而不是C++的,C++的规则在翻译这个函数名时会把fun这个名字变得面目全非,可能是f

  • 浅谈C/C++中的static与extern关键字的使用详解

    一.C语言中的static关键字在C语言中,static可以用来修饰局部变量,全局变量以及函数.在不同的情况下static的作用不尽相同.(1)修饰局部变量一般情况下,对于局部变量是存放在栈区的,并且局部变量的生命周期在该语句块执行结束时便结束了.但是如果用static进行修饰的话,该变量便存放在静态数据区,其生命周期一直持续到整个程序执行结束.但是在这里要注意的是,虽然用static对局部变量进行修饰过后,其生命周期以及存储空间发生了变化,但是其作用域并没有改变,其仍然是一个局部变量,作用域仅

  • C++中的extern声明变量详解

    extern声明变量无外乎如下两种: 1.声明全局变量 2.声明函数 今天我们只谈extern,什么const.static之类等等与之相关或不相关的一律忽略,下面就分别对以上两种情况一一讲解 声明和定义 既然提到extern声明变量,那我们就必须搞清楚声明和定义的区别. 这里我们将普通数据变量和函数统称变量.从内存分配角度来说,声明和定义的区别在于声明一个变量不会分配内存,而定义一个变量会分配内存.一个变量可以被声明多次,但是只能被定义一次. 基于以上前提,我们可以把声明和定义类比为指针和内存

  • C++中extern "C"的用法

    学习过C++的人都知道,extern关键字可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义.这里起到的是声明作用范围的用处.另外,extern还可以与"C"连用,作为链接指示.本文就此进行实例说明如下: 一.C++名字修饰(Name Mangling) 首先需要从C++的重载说起,在C++中函数重载指的是几个函数的函数名相同,参数列表不同.那么当生成obj中间文件/目标文件的时候,C++编译器如何区分这几个重载函数呢?可以

  • 深入理解C语言 static、extern与指针函数

    1.exit(0)正常退出程序 exit(1)程序异常时退出程序 2.static(静态变量)修饰局部变量 在局部变量使用static修饰,会延长局部变量的存在期.但我们需要注意一下几点: •虽然static修饰变量的生存期很长,但它始终是局部变量,不能在其他函数中使用•static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别?     全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量.全局变量本身

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

    简单来说,extern "C"是C++声明或定义C语言符号的方法,是为了与C兼容.说来容易,要理解起来还是得费些周折,首先我们要从C++和C的区别说起. 符号 大家都知道,从代码到可执行程序需要经过编译和链接两个过程,其中编译阶段会做语法检测,代码展开,另外它还会做一件事,就是将变量转成符号,链接的时候其实是通过符号来定位的.编译器在编译C和C++代码时,将变量转成符号的过程是不同的.本文所使用的编译器为gcc4.4.7 我们先来看一段简单的代码 复制代码 代码如下: /* hello

  • Oracle中游标Cursor基本用法详解

    查询 SELECT语句用于从数据库中查询数据,当在PL/SQL中使用SELECT语句时,要与INTO子句一起使用,查询的 返回值被赋予INTO子句中的变量,变量的声明是在DELCARE中.SELECT INTO语法如下: SELECT [DISTICT|ALL]{*|column[,column,...]} INTO (variable[,variable,...] |record) FROM {table|(sub-query)}[alias] WHERE............ PL/SQL

  • JSP中EL表达式的用法详解(必看篇)

    EL 全名为Expression Language EL 语法很简单,它最大的特点就是使用上很方便.接下来介绍EL主要的语法结构: ${sessionScope.user.sex} 所有EL都是以${为起始.以}为结尾的.上述EL范例的意思是:从Session的范围中,取得 用户的性别.假若依照之前JSP Scriptlet的写法如下: User user =(User)session.getAttribute("user"); String sex =user.getSex( );

  • oracle数据库中sql%notfound的用法详解

    SQL%NOTFOUND 是一个布尔值.与最近的sql语句(update,insert,delete,select)发生交互,当最近的一条sql语句没有涉及任何行的时候,则返回true.否则返回false.这样的语句在实际应用中,是非常有用的.例如要update一行数据时,如果没有找到,就可以作相应操作.如: begin update table_name set salary = 10000 where emp_id = 10; if sql%notfound then insert into

  • JavaScript中的splice方法用法详解

    JavaScript中的splice主要用来对js中的数组进行操作,包括删除,添加,替换等. 注意:这种方法会改变原始数组!. 1.删除-用于删除元素,两个参数,第一个参数(要删除第一项的位置),第二个参数(要删除的项数) 2.插入-向数组指定位置插入任意项元素.三个参数,第一个参数(插入位置),第二个参数(0),第三个参数(插入的项) 3.替换-向数组指定位置插入任意项元素,同时删除任意数量的项,三个参数.第一个参数(起始位置),第二个参数(删除的项数),第三个参数(插入任意数量的项) 示例:

  • 对pandas中apply函数的用法详解

    最近在使用apply函数,总结一下用法. apply函数可以对DataFrame对象进行操作,既可以作用于一行或者一列的元素,也可以作用于单个元素. 例:列元素 行元素 列 行 以上这篇对pandas中apply函数的用法详解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们. 您可能感兴趣的文章: 浅谈Pandas中map, applymap and apply的区别

  • Python3网络爬虫中的requests高级用法详解

    本节我们再来了解下 Requests 的一些高级用法,如文件上传,代理设置,Cookies 设置等等. 1. 文件上传 我们知道 Reqeuests 可以模拟提交一些数据,假如有的网站需要我们上传文件,我们同样可以利用它来上传,实现非常简单,实例如下: import requests files = {'file': open('favicon.ico', 'rb')} r = requests.post('http://httpbin.org/post', files=files) print

  • PyTorch中反卷积的用法详解

    pytorch中的 2D 卷积层 和 2D 反卷积层 函数分别如下: class torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, groups=1, bias=True) class torch.nn.ConvTranspose2d(in_channels, out_channels, kernel_size, stride=1, padding=0, output_padding=0, b

  • pytorch中nn.Conv1d的用法详解

    先粘贴一段official guide:nn.conv1d官方 我一开始被in_channels.out_channels卡住了很久,结果发现就和conv2d是一毛一样的.话不多说,先粘代码(菜鸡的自我修养) class CNN1d(nn.Module): def __init__(self): super(CNN1d,self).__init__() self.layer1 = nn.Sequential( nn.Conv1d(1,100,2), nn.BatchNorm1d(100), nn

  • PyTorch中topk函数的用法详解

    听名字就知道这个函数是用来求tensor中某个dim的前k大或者前k小的值以及对应的index. 用法 torch.topk(input, k, dim=None, largest=True, sorted=True, out=None) -> (Tensor, LongTensor) input:一个tensor数据 k:指明是得到前k个数据以及其index dim: 指定在哪个维度上排序, 默认是最后一个维度 largest:如果为True,按照大到小排序: 如果为False,按照小到大排序

随机推荐