【C++】extern “C“详解

参考:
extern “C“的作用及理解
extern C里面能有C++代码吗?

1. 概述

extern “C” 是C++特有的指令(C无法使用该指令),目的在于支持C++与C混合编程。

2. 作用

extern “C” 的作用是告诉C++编译器用C规则编译指定的代码(除函数重载外,extern “C” 不影响C++其他特性)。

3. 原因

为什么要用C规则编译C++代码呢?

因为C和C++的编译规则不一样,主要区别体现在编译期间生成函数符号的规则不一致

C++比C出道晚,但是增加了很多优秀的功能,函数重载就是其中之一。由于C++需要支持重载,单纯的函数名无法区分出具体的函数,所以在编译阶段就需要将形参列表作为附加项增加到函数符号中。如以下代码:

void Function(int a, int b)
{
    printf("Hello!!! a = %d, b = %d\n", a, b);
}

C和C++对应的的汇编码如下:

  • C汇编结果:
...
Function:
.LFB11:
    .cfi_startproc
    movl    %esi, %edx
    xorl    %eax, %eax
    movl    %edi, %esi
    movl    $.LC0, %edi
    jmp    printf
    .cfi_endproc
...
  • C++汇编结果:
...
_Z8Functionii:
.LFB12:
    .cfi_startproc
    movl    %esi, %edx
    xorl    %eax, %eax
    movl    %edi, %esi
    movl    $.LC0, %edi
    jmp    printf
    .cfi_endproc
...

容易发现,两段代码的区别仅在于函数 Function(int a, int b) 编译后对应的符号不同:

  • C:Function
  • C++:_Z8Functionii

C++编出来的函数符号明显比C的多出了一些信息(如ii),这里多出来的后缀信息就是形参列表的参数类型信息

好,C和C++编译规则对编译期间产生函数符号的影响我们知道了,但这又有什么用呢?

别急,很多人可能都遇到过类似这样的情况:

/* MyFunction.c */
void Function(int a, int b)
{
    printf("Hello!!! a = %d, b = %d\n", a, b);
}
 
 
/* main,cpp */
extern void Function(int a, int b);
 
int main()
{
    Function(1, 2);
}

C提供写了一个函数,用C++代码调用该函数,看起来没什么问题,但是编译的时候…

/tmp/ccbkGl1J.o:在函数‘main’中:
main.cpp:(.text.startup+0xf):对‘Function(int, int)’未定义的引用
collect2: 错误:ld 返回 1

找不到对Function(int, int)的定义?怎么可能?

如果找到就玄幻了,大家注意观察,错误出在哪一步?对,编译阶段没报错,是链接的阶段报错了。我们来看看两个文件的汇编结果:

    .file    "MyFunction.c"
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
    .string    "Hello!!! a = %d, b = %d\n"
    .text
    .p2align 4,,15
    .globl    Function
    .type    Function, @function
Function:
.LFB11:
    .cfi_startproc
    movl    %esi, %edx
    xorl    %eax, %eax
    movl    %edi, %esi
    movl    $.LC0, %edi
    jmp    printf
    .cfi_endproc
.LFE11:
    .size    Function, .-Function
    .ident    "GCC: (GNU) 6.4.1 20170727 (Red Hat 6.4.1-1)"
    .section    .note.GNU-stack,"",@progbits
    .file    "main.cpp"
    .section    .text.startup,"ax",@progbits
    .p2align 4,,15
    .globl    main
    .type    main, @function
main:
.LFB0:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $2, %esi
    movl    $1, %edi
    call    _Z8Functionii
    xorl    %eax, %eax
    addq    $8, %rsp
    .cfi_def_cfa_offset 8
    ret
    .cfi_endproc
.LFE0:
    .size    main, .-main
    .ident    "GCC: (GNU) 6.4.1 20170727 (Red Hat 6.4.1-1)"
    .section    .note.GNU-stack,"",@progbits

可以看到,MyFunction.s(源文件为.c文件)中定义的是Function,而main.s(源文件为.cpp文件)中调用的是_Z8Functionii,函数名不一样,所以连接的时候找不到函数实现。到这里我们知道C和C++编译期间后得到的函数符号不同,所以C++代码和C代码不能互相调用。

要想实现C、C++混合编程该怎么办呢?让编译后的函数符号一致。

怎么一致呢?用extern “C”!

所以,extern “C”的作用就是告诉C++编译器,将指定的函数用C规则编译(注意,除了函数重载外,extern “C”不影响C++的其他特性),然后后面的事情就顺理成章了。

总结
1. 语法:

首先从语法上讲,不是“函数外边包一层extern “C” {…}”这样子。你只需要在函数的声明上面加一个extern "C"就行了,如果有多个函数需要声明,就再加个{}一起搞定。记住是函数的声明,不是函数定义,所以一般extern "C"放在头文件内。当然你非要函数定义上加extern "C"也可以,只是要注意要和前面头文件内的声明一致。

2. 原理:

C编译器编译代码生成的obj文件的符号表内,函数名称保持原样,比如int add(int,int)函数在符号表内就叫做add;C++编译器编译C++代码生成的obj文件符号表内,因为有overload的存在,函数名称的符号不再是原来的比如add,而是类似_Z3addii这样的(这是我的g++结果)。那么,一个C程序需要使用某个C++库内的add函数时,C程序这边期望的是add,但C++库内是_Z3addii这样的,不匹配嘛对不对,所以链接阶段要报错,说找不到add这个函数。同样,一个C++程序需要使用某个C库内的add函数,C++程序这边期望的是_Z3addii,但C库内是add这样的,同样不匹配,链接阶段也是报错,这次是说找不到_Z3addii。

extern "C"的意思,是让C++编译器(不是C编译器,而且是编译阶段,不是链接阶段)在编译C++代码时,为被extern “C”所修饰的函数在符号表中按C语言方式产生符号名(比如前面的add),而不是按C++那样的增加了参数类型和数目信息的名称(_Z3addii)。

展开来细说,就是:

如果是C调用C++函数,在C++一侧对函数声明加了extern "C"后符号表内就是add这样的名称,C程序就能正常找到add来调用;如果是C++调用C函数,在C++一侧在声明这个外部函数时,加上extern "C"后,C++产生的obj文件符号表内就也是标记为它需要一个名为add的外部函数,这样配合C库,就一切都好。

所以,

不管是C代码调用C++编译器生成的库函数,还是C++代码调用C编译器生成的库函数,都需要在C++代码一侧对相应的函数进行extern “C”声明

### 关于金百达KP330闪存颗粒的信息 金百达KP330是一款基于联芸MAS0902主控的固态硬盘,其存储介质采用了镁光颗粒[^1]。这些颗粒通常被认为是白片(即未标注具体型号或规格),这可能导致部分用户对其性能和稳定性存在疑虑。 #### 颗粒规格与性能 尽管具体的颗粒型号并未被明确提及,但从实际应用来看,金百达KP330的表现相对稳定,在批量部署的情况下,三年内的故障率较低(仅有一个样本出现问题)。这一数据表明其颗粒质量在日常办公环境中具有一定的可靠性。 关于性能方面,虽然没有直接针对金百达KP330的具体评测数据,但可以从类似的消费级SSD产品中推测其大致水平。例如,采用TOSHIBA MLC颗粒的产品可实现约79.2 MB/s的最大写入速度,而HYNIX MLC颗粒的速度稍低,约为70.2 MB/s[^2]。考虑到金百达KP330定位为入门级企业或教育用途设备,其性能应接近上述范围。 另外,有其他品牌产品的测试结果显示,搭载相似级别硬件配置的SSD可以在AS SSD Benchmark工具下取得不错的成绩,比如持续读取速度可达501 MB/s,4K小文件随机读写能力也较为出色,达到65 MB/s[^3]。不过需要注意的是,这类数值可能会因控制器优化程度以及颗粒品质的不同而有所差异。 #### 实际用户体验反馈 对于长期使用的场景来说,除了理论上的跑分外,真实环境下的耐用性和错误发生频率同样重要。根据已有案例描述,“可以进系统但老是蓝屏”的现象可能是由多种因素共同作用造成的,并不一定完全归咎于闪存本身的质量问题;也可能涉及操作系统兼容性或者驱动程序版本等因素的影响。 综上所述,金百达KP330所选用的镁光颗粒具备一定性价比优势,在满足基本需求前提下提供了可靠的服务期限表现。但对于追求极致效能的应用场合,则需谨慎评估是否适合选用此类产品。 ```python # 示例代码展示如何通过Python脚本简单检测硬盘健康状态 (假设使用smartctl工具) import subprocess def check_disk_health(disk_name): result = subprocess.run(['sudo', 'smartctl', '-a', disk_name], capture_output=True, text=True) print(result.stdout) check_disk_health('/dev/sda') # 替换为您的目标磁盘路径 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值