extern “C” 陷阱

本文探讨了C++中extern C的使用及其可能导致的问题。通过实例说明如何阻止C++编译器对函数进行签名,以及不当使用extern C可能导致的函数调用错误。

extern “C” 陷阱

extern “C”通常使用在C++中,由于C++支持函数重载,命名空间等技术,故C++编译器在编译C++代码时会对函数进行签名,也就是说编译后的函数名将发生变化。例如我们有如下的代码:

test.cpp

int fun(int a)

{

///nothing.

}

  在Ubuntu下用g++编译这个cpp文件获取它的目标文件test.o,命令:gcc -c test.cpp我们可以用GNU binutils工具readelf来查看test.o中的符号信息,命令:readelf -s test.o

输出如下:

可以看到所有的符号中并没有出现fun,这是因为g++对函数进行了签名,唯一和fun相似的符号就是_Z3funi,那么这个符号是否就是test.cpp中的fun函数呢?我们可以用c++filt(用来查看符号的未被签名前的原型)来进行验证:c++filt _Z3funi


可以看到在test.o中的符号_Z3funi对应的就是test.cpp中的fun函数。如果我们要阻止c++编译器这种签名机制,extern “C”就派上了用场。我们在fun前添加extern “C”。

extern “C” int fun(int a)

{

///nothing.

}

重新编译我们再查看test.o中的符号表:


可以看到加上extern “C” 函数fun就没有被签名。

在日常开发中extern “C” 使用在C++调用C编写的动态库的接口时显示声明C++中的接口不被签名,从而能正常调用C编写的动态库。下面看一个例子:

print.c:

#include <stdio.h>

void print(int a, int b)

{

printf(“sum = %d\n”, a+b);

}

使用命令:gcc -shared -fPIC -o LibPrint.so print.c 来编译生成动态库LibPrint.so

程序主模块:

main.cpp:

extern “C” void print(int a); ///这里只有一个int参数

int main()

{

print(1);

return 0;

}

使用命令:g++ -o main main.cpp ./LibPrint.so 来编译生成可执行程序main


可以看到编译通过了,没有报任何错误。大家可能会疑惑在动态库中的函数原型为void print(int,int),但在main.cpp中函数原型为void print(int),应该要报找不到符号。程序之所以能编译通过是因为编译器在进行连接时发现print是一个外部符号(为使用了extern “C”声明,main.o模块中函数print并没有被签名),便在动态库LibPrint.so中寻找,而LibPrint中定义了该符号,故能编译通过。

最可怕的是产生的可执行程序能够运行且不会core出,因为LibPrint.so中的print函数只是跨越了自己参数栈多取了参数栈后面的一个数据(int b),而这是合法的(访问的数据还未越界,还在程序栈内)。

综上所述当我们在使用外部用C开发的第三方库需要使用extern “C”时,尤其是在第三方库更新时要确认第三方库的接口是否改变,自己程序中接口声明是否和库中一致。


<think>嗯,用户问的是C语言extern关键字的作用,这需要结合站内引用的三份资料来回答。用户之前讨论的是DFM分析,现在突然转向C语言基础,可能是学习过程中遇到了新问题。 从引用内容看,三份资料都聚焦extern的两个核心功能:声明外部变量和外部函数。引用[1]的结构最清晰,它把extern分为文件内声明和跨文件声明两种情况,还特别提到结构体变量的处理。引用[2]补充了使用注意事项,引用[3]则强调代码复用优势。用户应该需要基础讲解而非进阶技巧。 考虑到用户刚接触这个概念,解释时要避免术语堆砌。比如“作用域扩展到从声明开始”这种表述(引用[1]原文)需要转化成更直白的说法。另外,extern的实际应用场景比理论更重要,应该用文件分离的例子说明,这点引用[3]总结得不错。 用户没有指定开发环境,但跨文件协作是常见需求。可以强调extern在模块化开发中的价值——这正是引用[3]提到的“提高可维护性”。另外要提醒常见陷阱,比如引用[2]指出的重复定义问题,新手容易在这里犯错。 需要特别注意术语准确度。引用[1]中“外部变量是指在函数或者文件外部定义的全局变量”这句话很关键,要明确区分“定义”和“声明”。实际回复时考虑用加粗突出这个区别,毕竟这是extern的核心机制。 最后生成的问题要延续学习路径:作用域细节、头文件用法、与static对比都是自然延伸。避免问太偏的内容,比如“extern C”这种C++兼容性问题可能超出当前范围。</think>在C语言中,`extern`关键字用于**声明变量或函数在其他文件中定义**,从而实现跨文件的变量共享和函数调用。其核心作用是**扩展变量或函数的作用域**,避免重复定义。以下是具体解析: --- ### 一、`extern` 的核心作用 1. **声明外部变量** - 当变量在**当前文件外定义**时,用 `extern` 声明可引用该变量,无需重新定义。 - **示例**: ```c // File1.c int globalVar = 10; // 实际定义 // File2.c extern int globalVar; // 声明引用File1.c中的globalVar void printVar() { printf("%d", globalVar); // 输出10 } ``` 2. **声明外部函数** - 函数默认具有 `extern` 属性(可省略),显式声明用于强调跨文件调用。 - **示例**: ```c // File1.c void func() { /* 函数实现 */ } // File2.c extern void func(); // 声明引用File1.c中的func ``` --- ### 二、关键使用场景 #### 1. **单文件内扩展作用域** 若变量定义在文件末尾,需在函数前使用时,用 `extern` 提前声明: ```c #include <stdio.h> extern int var; // 声明var在后续定义 int main() { printf("%d", var); // 合法使用 return 0; } int var = 20; // 实际定义 ``` #### 2. **多文件共享全局变量** - 在 `main.c` 定义变量,在 `utils.c` 中通过 `extern` 声明使用。 - **避免重复定义**:全局变量只能定义一次,但可被多个文件声明引用[^1][^3]。 #### 3. **跨文件调用函数** 函数声明默认带 `extern`,显式写法则更清晰: ```c // utils.h extern void helper(); // 声明函数在其他文件实现 ``` --- ### 三、注意事项 1. **声明 vs. 定义** - `extern` 是**声明**(Declare),不是**定义**(Define)。 - 声明不分配内存,定义分配内存。 - **错误示例**:`extern int x = 5;` // 含初始化即变为定义,导致链接错误[^2]。 2. **数据类型一致性** 声明时必须与原始定义类型严格匹配: ```c // File1.c float data; // File2.c extern int data; // 错误!类型不匹配 ``` 3. **避免重复定义** 多个文件引用同一全局变量时,**仅在一个文件中定义**,其他文件用 `extern` 声明[^3]。 --- ### 四、总结 | **场景** | **`extern` 的作用** | **示例** | |------------------------|---------------------------------------------|-----------------------------| | 跨文件使用全局变量 | 声明变量在其他文件定义 | `extern int globalVar;` | | 跨文件调用函数 | 显式声明外部函数(可省略) | `extern void func();` | | 单文件内提前使用变量 | 将作用域扩展到声明位置之前 | 文件开头声明,末尾定义变量 | > 通过 `extern` 可实现模块化开发,提升代码复用性[^3]。但需严格区分声明与定义,避免链接错误[^2]。 --- ### 相关问题 1. `extern` 和 `static` 在作用域上有何区别? 2. 如何在头文件中正确使用 `extern` 声明全局变量? 3. 多文件编程时,`extern` 声明放在头文件还是源文件更合适?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值