对extern “C"的理解

参考:

http://www.cnblogs.com/rollenholt/archive/2012/03/20/2409046.html

被extern "C"修饰的变量和函数是按照C语言方式编译和连接的,实现C++与C的混合编程。

下面做几个实验来理解:

C++和C编译时对函数名的处理

下面是一个C++文件, a.cpp

void func1(void)
{}

void func2(int a)
{}

void func3(int a, int b)
{}

int func4(void)
{}

int func5(int a)
{}

int func6(int a, int b)
{}

int main(int argc, const char *argv[])
{
        func1();
        func2(1);
        func3(1, 2);
        func4();
        func5(1);
        func6(1, 2);
        return 0;
}

使用命令  g++ -S a.cpp 编译这个文件,得到编译后得到的汇编文件 a.s,摘录部分内容如下:

 1 .LFB1026:
 2         .cfi_startproc
 3         pushq   %rbp
 4         .cfi_def_cfa_offset 16
 5         .cfi_offset 6, -16
 6         movq    %rsp, %rbp
 7         .cfi_def_cfa_register 6
 8         subq    $16, %rsp
 9         movl    %edi, -4(%rbp)
10         movq    %rsi, -16(%rbp)
11         call    _Z5func1v
12         movl    $1, %edi
13         call    _Z5func2i
14         movl    $2, %esi
15         movl    $1, %edi
16         call    _Z5func3ii
17         call    _Z5func4v
18         movl    $1, %edi
19         call    _Z5func5i
20         movl    $2, %esi
21         movl    $1, %edi
22         call    _Z5func6ii
23         movl    $0, %eax
24         leave
25         .cfi_def_cfa 7, 8
26         ret
27         .cfi_endproc
28 .LFE1026:

可以看到,将函数名分别进行了如下处理:

void func1(void)                ---> _Z5func1v
void func2(int a)               ---> _Z5func2i
void func3(int a, int b)       ---> _Z5func3ii
int  func4(void)                 ---> _Z5func4v
int  func5(int a)                 ---> _Z5func5i
int func6(int a, int b)         ---> _Z5func6ii

可以看到,将来参数类型会和函数名结合,从而产生新的标示,与返回值的类型没有关系。这就解释了C++允许函数重载的原因,并且知道了为什么仅返回值类型不同不能认为是重载的原因。

还是这个文件,我们重命名为 b.c,然后使用命令  gcc -S b.c,得到汇编文件 b.s,摘录部分内容如下:

main:
.LFB6:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $16, %rsp
        movl    %edi, -4(%rbp)
        movq    %rsi, -16(%rbp)
        call    func1
        movl    $1, %edi
        call    func2
        movl    $2, %esi
        movl    $1, %edi
        call    func3
        call    func4
        movl    $1, %edi
        call    func5
        movl    $2, %esi
        movl    $1, %edi
        call    func6
        movl    $0, %eax
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc

可以看到,在编译过程中,函数的名称并没有改变,这也就解释了C语言为什么不支持函数的重载。

那么,既然C和C++在编译时对函数名的处理不同,那么如何实现C和C++的混合编程呢?即在C中调用C++的函数,在C++中调用C的函数。

先看一下现象:

a.cpp

int main(int argc, const char *argv[])
{
        func1();
        return 0;
}

使用命令 g++ -c a.cpp,出现如下错误:

a.cpp: In function ‘int main(int, const char**)’:
a.cpp:3:8: error: ‘func1’ was not declared in this scope
  func1();
        ^

原因是我们没有声明func1这个函数,我发现,如果是在C语言中,不声明并不会导致编译错误,原因其实很简单,在C++中,编译的时候给函数设置新的标示符的时候要用到函数的参数类型,如果不声明,编译器无法获得func1的参数类型信息,从而编译出错。而在C语言中,编译过程中函数的标识符就是函数名,不考虑参数信息,也就不会出错。

修改a.cpp如下:

extern func1(void);

int main(int argc, const char *argv[])
{
        func1();
        return 0;
}

相应的汇编部分内容如下:

main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $16, %rsp
        movl    %edi, -4(%rbp)
        movq    %rsi, -16(%rbp)
        call    _Z5func1v
        movl    $0, %eax
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:

b.c

void func1(void)
{}

使用如下命令编译: gcc -c b.c

相应的汇编文件如下:

        .file   "b.c"
        .text
        .globl  func1
        .type   func1, @function
func1:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:

然后使用命令 g++ a.o b.o 链接,提示如下错误:

a.o: In function `main‘:
a.cpp:(.text+0x10): undefined reference to `func1()‘
collect2: error: ld returned 1 exit status

原因是, 在链接时,由于找不到_Z5func1v导致编译失败,解决办法如下:

修改a.cpp

extern "C" void func1(void);

int main(int argc, const char *argv[])
{
        func1();
        return 0;
}

我们看一下得到的汇编文件(g++ -S a.cpp):

main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $16, %rsp
        movl    %edi, -4(%rbp)
        movq    %rsi, -16(%rbp)
        call    func1
        movl    $0, %eax
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:

可以看到,标识符已经从_Z5func1v变成了func1,即从C++的编译方式变成了C的编译方式,这样在链接时就不会出错了。

那么如果是在C语言中调用C++的函数,结果如何呢?

a.c

int main(int argc, const char *argv[])
{
        func1();
        return 0;
}

使用命令 gcc -c a.c编译得到a.o

得到的汇编文件如下:

main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $16, %rsp
        movl    %edi, -4(%rbp)
        movq    %rsi, -16(%rbp)
        movl    $0, %eax
        call    func1
        movl    $0, %eax
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:

b.cpp

void func1(void)
{}

得到的汇编文件如下:

        .file   "b.cpp"
        .text
        .globl  _Z5func1v
        .type   _Z5func1v, @function
_Z5func1v:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:

使用命令 g++ -c b.cpp编译得到b.o

使用命令gcc a.o b.o出现如下错误的提示

a.o: In function `main‘:
a.cpp:(.text+0x10): undefined reference to `func1‘
collect2: error: ld returned 1 exit status

修改b.cpp

extern "C" void func1(void)
{}

注:这里不能修改a.c,因为C语言中无法识别extern "C"

得到的汇编如下(g++ -S b.cpp):

        .file   "b.cpp"
        .text
        .globl  func1
        .type   func1, @function
func1:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:

这样在链接时就不会出错了。

时间: 2024-10-06 01:17:57

对extern “C"的理解的相关文章

C++(extern关键字的理解和作用深入)

extern关键字的理解和作用深入 extern是一个关键字,它告诉编译器存在着一个变量或者一个函数,如果在当前编译语句的前面中没有找到相应的变量或者函数, 也会在当前文件的后面或者其它文件中定义 引文一.(主要是实战中的各种情况)http://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777431.html 1 基本解释:extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找

关于extern的说明

extern的作用: extern可置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义. 声明外部变量 现代编译器一般采用按文件编译的方式,因此在编译时,各个文件中定义的全局变量是互相不透明的.也就是说,在编译时,全局变量的可见域限制在文件内部.但是到了链接阶段,要将各个文件的内容“合为一体”,因此,如果某些文件中定义的全局变量名相同的话,在这个时候就会出现重复定义的错误. extern的原理很简单,就是告诉编译器:“你现在编译的文件中,

IOS的变量前加extern和static字段

IOS的变量前加extern和static字段 前一阵子,做项目的时候到网上找Demo,打开运行的时候发现其中变量前有关键字extern和static,所以我研究了一下子 对于extern来说可以理解为扩展吧是这样的是从一个类扩展到另一个类中的 在SplashViewController.m中定义一个变量是这样的 #import "SplashViewController.h" NSString* [email protected]"123"; @implement

extern "C"——用“C”来规约在C++中用C的方式进行编译和链接

C++中的extern “C”用法详解     extern "C"表明了一种编译规约,其中extern是关键字属性,“C”表征了编译器链接规范.对于extern "C"可以理解成在C++/C中的混合编程的编译指令.用“C”来规约在C++中用C的方式进行编译和链接. extern "C" 包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”的:其次,被它修饰的目标是“C”的.让我们来详细解读这两重含义. 1.    被exter

Linux链接库三

http://www.cppblog.com/wolf/articles/74928.html http://www.cppblog.com/wolf/articles/77828.html http://www.jb51.net/article/34990.htm C和C++之间库的互相调用 extern "C"的理解:很多人认为"C"表示的C语言,实际并非如此,"C"表示的是一种链接约定,只是因C和C++语言之间的密切关系而在它们之间更多的应

Linux链接库三(C跟C++之间动态库的相互调用)

http://www.cppblog.com/wolf/articles/74928.html http://www.cppblog.com/wolf/articles/77828.html http://www.jb51.net/article/34990.htm C和C++之间库的互相调用 extern "C"的理解:很多人认为"C"表示的C语言,实际并非如此,"C"表示的是一种链接约定,只是因C和C++语言之间的密切关系而在它们之间更多的应

【转载】理解C语言中的关键字extern

原文:理解C语言中的关键字extern 最近写了一段C程序,编译时出现变量重复定义的错误,自己查看没发现错误.使用Google发现,自己对extern理解不透彻,我搜到了这篇文章,写得不错.我拙劣的翻译了一下.(原文:http://www.geeksforgeeks.org/understanding-extern-keyword-in-c/)   我确定这篇文章对c语言的初学者会有很大的帮助,因为这将使他们更好更熟练的使用c语言.所以就让我先来说说extern关键字在变量和函数上的应用.最基本

static和extern理解

C语言static修饰函数详细解析(http://www.jb51.net/article/40520.htm) 在C语言中,static的字面意思很容易把我们导入歧途,其实它的作用有三条.介绍它的第一条也是最重要的一条:隐藏.当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性.为理解这句话,我举例来说明.我们要同时编译两个源文件,一个是a.c,另一个是main.c. 下面是a.c的内容 char a = 'A'; // global variable void

深入理解extern用法

一. extern做变量声明 l  声明extern关键字的全局变量和函数可以使得它们能够跨文件被访问. 我们一般把所有的全局变量和全局函数的实现都放在一个*.cpp文件里面,然后用一个同名的*.h文件包含所有的函数和变量的声明.如: /*Demo.h*/ #pragma once extern inta; extern intb; intadd(inta,intb); /*Demo.cpp*/ #include "Demo.h" /*这句话写或者不写在本例中都行,不过建议不写*/ /