我的编程空间,编程开发者的网络收藏夹
学习永远不晚

C/C++开发中extern的一些使用注意事项

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

C/C++开发中extern的一些使用注意事项

前言

前些日子,有友友问了我这样的一道问题:

数组通过外部声明为指针时,数组和指针是不能互换使用的;那么请思考一下,在 A 文件中定义数组 char a[100];在 B 文件中声明为指针:extern char *a;此时访问 a[i],会发生什么;

先说结果,会引起 segmentation fault 报错;

那接下来由博主来分析一番;

数组与指针的区别

在介绍 extern 之前,我们需要了解一下数组与指针有什么区别?

数组变量代表了存放该数组的那块内存,它是这块内存的首地址。这就说明了数组变量是一个地址,而且,还是一个不可修改的常量,具体来说,就是一个地址常量。

数组变量跟枚举常量一样,都属于符号常量。数组变量这个符号,就代表了那块内存的首地址。注意,不是数组变量这个符号的值是那块内存的首地址,而是数组变量这个符号本身代表了首地址,它就是这个地址值。这就是数组变量属于符号常量的意义所在。

由于数组变量是一种符号常量,它是一个右值,而指针,作为变量,却是一个左值,一个右值永远都不是左值,那么,数组名永远都不会是指针!

举个例子,char a[] 中的 a 是常量,是一个地址,char *a 中 a 是一个变量,一个可以存放地址的变量。

具体分析

了解了数组与指针的区别之后,让我们来看看 extern 声明全局变量的内部实现;

extern 是 C/C++ 语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。

TIP :被 extern 修饰的全局变量不被分配空间,而是在链接的时候到别的文件中通过查找索引定位该全局变量的地址。

extern char a[];

这是一个外部变量的声明,它声明了一个名为 a 的字符数组,编译器看到这个声明就知道不必为这个变量分配空间,这个 .cpp 文件中所有对数组 a 的引用都化为一个不包含类型的标号,具体地址的定位留给链接器完成。编译完成之后也得到一个中间文件,链接器遍历这个文件,发现有未经定位的标号,于是它搜索其他中间文件,试图寻找到一个匹配的空间地址,在此例中无疑链接器将成功地寻找到这个地址并将此中间文件中所有的这个标号替换为链接器所寻找到的地址,最终生成的可执行文件中,所有曾经的标号都应当已经被替换为地址。这是一个正常工作过程,链接出来的可执行文件至少在对于该数组的引用部分将工作得很好。

extern char * a; 

这是一个外部变量的声明,它声明了一个名为 a 的字符指针,中间过程与上同,经过一番搜索,找到了一个分配过空间的名为 a 的地方(也就是我们先定义的那个字符数组),链接器并不知道它们的类型,仅仅是发现它们的名字一样,就认为应该把 extern 声明的标号链接到数组 a 的首地址上,因此链接器把指针 a 对应的标号替换为数组 a 的首地址。这里问题就出现了:由于在这个文件中声明的 a 是一个指针变量而不是数组,链接器的行为实际上是把指针 a 自身的地址定位到了另一个 .c 文件中定义的数组首地址上,而不是我们所希望的把数组的首地址赋予指针 a(这很容易理解:指针变量也需要占用空间,如果说把数组的首地址赋给了指针 a,那么指针 a 本身在哪里存放呢?)。这就是症结所在了。所以此例中指针 a 的内容实际上变成了数组 a 首地址开始的 4 字节表示的地址(如果在 16 位机上,就是 2 字节)。

上述加粗部分的可以理解为,链接器认为 a 变量本身的内存位置是数组的首地址,但其实 a 的位置是其他位置,其内容才是数组首地址。

举个例子,定义 char a[] = "abcd",则外部变量 extern char a[] 的地址是 0x12345678 (数组的起始地址),而 extern char *a 是重新定义了一个指针变量 a,其地址可能是 0x87654321,因此直接使用 extern char *a 是错误的。

通过上述分析,我们得到的最重要的结论是:使用 extern 修饰的变量在链接的时候只找寻同名的标号,不检查类型,所以才会导致编译通过,运行时出错。

extern "C"

extern "C" 包含双重含义,从字面上即可得到:

  • 首先,被它修饰的目标是 extern 的;
  • 其次,被它修饰的目标是 C 的。

1、 被 extern "C" 限定的函数或变量是 extern 类型的;

extern int a;

仅仅是一个变量的声明,其并不是在定义变量 a,并未为 a 分配内存空间。变量 a 在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。

通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字 extern 声明。例如,如果模块 B 欲引用该模块 A 中定义的全局变量和函数时只需包含模块 A 的头文件即可。这样,模块 B 中调用模块 A 中的函数时,在编译阶段,模块 B 虽然找不到该函数,但是并不会报错,它会在连接阶段中从模块 A 编译生成的目标代码中找到此函数。

extern 对应的关键字是 static,被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其不可能被 extern "C" 修饰。

2、被 extern "C" 修饰的变量和函数是按照 C 语言方式编译和连接的;

未加 extern "C" 声明时的编译方式

作为一种面向对象的语言,C++ 支持函数重载,而过程式语言 C 则不支持。函数被 C++ 编译后在符号库中的名字与 C 语言的不同。例如,假设某个函数的原型为:

void foo( int x, int y );

该函数被 C 编译器编译后在符号库中的名字为 _foo,而 C++ 编译器则会产生像 _foo_int_int 之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为 “mangled name”)。

_foo_int_int 这样的名字包含了函数名、函数参数数量及类型信息,C++ 就是靠这种机制来实现函数重载的。例如,在 C++ 中,函数 void foo(int x, int y)void foo(int x, float y) 编译生成的符号是不相同的,后者为 _foo_int_float

同样地,C++ 中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以 . 来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。

未加 extern "C" 声明时的连接方式

假设在 C++ 中,模块 A 的头文件如下:

// 模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
int foo(int x, int y);
#endif

在模块 B 中引用该函数:

// 模块B实现文件 moduleB.cpp
#include "moduleA.h"
foo(2, 3);

实际上,在连接阶段,连接器会从模块 A 生成的目标文件 moduleA.obj 中寻找 _foo_int_int 这样的符号;

extern "C" 声明后的编译和连接方式

extern "C" 声明后,模块 A 的头文件变为:

// 模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
extern "C" int foo(int x, int y);
#endif

在模块 B 的实现文件中仍然调用 foo(2, 3),其结果是:

  • 模块 A 编译生成 foo 的目标代码时,没有对其名字进行特殊处理,采用了 C 语言的方式;
  • 连接器在为模块 B 的目标代码寻找 foo(2, 3) 调用时,寻找的是未经修改的符号名 _foo

如果在模块 A 中函数声明了 fooextern "C" 类型,而模块 B 中包含的是 extern int foo(int x, int y),则模块 B 找不到模块 A 中的函数;反之亦然。

所以,可以用一句话概括 extern "C" 这个声明的真实目的:实现 C++ 与 C 及其它语言的混合编程。

以上就是C/C++开发中extern的一些使用注意事项的详细内容,更多关于C/C++开发extern使用事项的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

C/C++开发中extern的一些使用注意事项

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

C/C++开发中extern的一些使用注意事项

这篇文章主要为大家介绍了C/C++开发中extern一些使用注意事项的事例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-01-04

C#中HttpClient使用注意事项有哪些

小编给大家分享一下C#中HttpClient使用注意事项有哪些,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!最近在测试一个第三方API,准备集成在我们的网站应用中
2023-06-29

C++开发注意事项:避免常见的C++开发错误

C++作为一种高级编程语言,在软件开发中应用非常广泛。然而,由于C++具有一定的复杂性和繁琐性,开发人员在进行C++开发时往往会遇到一些常见的错误。这些错误在程序的性能、可靠性和可维护性方面都会产生深远的影响。因此,本文将介绍一些C++开发
C++开发注意事项:避免常见的C++开发错误
2023-11-23

C++开发注意事项:避免C++代码中的异常不一致性

C++开发注意事项:避免C++代码中的异常不一致性引言:C++是一种功能强大且灵活的编程语言,但在开发过程中,不一致的异常处理可能会导致程序的不可预知的行为和错误。本文将探讨一些重要的注意事项,以帮助开发人员避免C++代码中的异常不一致性。
C++开发注意事项:避免C++代码中的异常不一致性
2023-11-22

C语言中使用break要注意哪些事项

在C语言中,使用break语句时需要注意以下几点事项:1. break只能用于循环语句和switch语句中,用来跳出当前循环或switch语句的执行。2. 在多层循环嵌套的情况下,break只会跳出最内层的循环,如果需要跳出外层循环,可以使
2023-10-12

C++开发注意事项:避免C++多线程开发的陷阱

C++开发注意事项:避免C++多线程开发的陷阱在当今的软件开发领域中,多线程编程已经变得异常重要。无论是为了提高程序性能还是为了避免阻塞,利用多线程来进行并行处理已经成为了一个普遍的趋势。而对于C++开发者而言,多线程编程更是一个十分重要的
C++开发注意事项:避免C++多线程开发的陷阱
2023-11-22

C++开发注意事项:避免C++安全漏洞的发生

C++开发是一项广泛应用于软件开发领域的技术,其灵活性和高效性使其成为许多项目的首选语言。然而,随之而来的是需要特别注意C++代码中的安全漏洞。本文将介绍一些C++开发注意事项,以帮助开发人员避免常见的安全漏洞的发生。数组越界访问:C++中
C++开发注意事项:避免C++安全漏洞的发生
2023-11-22

C++开发注意事项:避免C++代码中的资源泄漏

C++作为一种强大的编程语言,广泛应用于软件开发领域。然而,在开发过程中,很容易遇到资源泄漏的问题,导致程序运行不稳定或者出现错误。本文将介绍一些C++开发中避免资源泄漏的注意事项。资源泄漏是指在程序中分配了一定的资源(如内存、文件句柄、数
C++开发注意事项:避免C++代码中的资源泄漏
2023-11-22

C++开发注意事项:避免C++代码中的魔法数字

C++开发注意事项:避免C++代码中的魔法数字在C++开发中,魔法数字指的是代码中直接出现的未经解释的硬编码数字。这些数字通常会在代码中直接出现多次,给代码的维护和理解带来了困难。因此,在C++开发中,避免使用魔法数字是一个很重要的注意事项
C++开发注意事项:避免C++代码中的魔法数字
2023-11-22

C++开发注意事项:避免C++代码中的内存溢出

C++开发注意事项:避免C++代码中的内存溢出C++语言作为一种强大的编程语言,被广泛应用于系统软件、游戏开发、嵌入式系统和高性能应用程序等领域。然而,在C++开发过程中,内存溢出是一个常见的问题,它可能导致程序崩溃、安全漏洞和性能问题。因
C++开发注意事项:避免C++代码中的内存溢出
2023-11-22

C++开发注意事项:避免C++代码中的死锁问题

C++开发注意事项:避免C++代码中的死锁问题引言:在C++开发中,死锁(Deadlock)是一个很常见的问题,它会导致程序出现无响应、崩溃等严重后果。因此,我们在编写C++代码时,要特别注意避免死锁的发生。本文将介绍一些常见的死锁问题以及
C++开发注意事项:避免C++代码中的死锁问题
2023-11-22

C++开发注意事项:避免C++代码中的循环引用问题

C++是一种广泛使用的编程语言,被广泛应用于游戏开发、嵌入式系统开发等各个领域。在C++开发过程中,有一种常见的问题被称为“循环引用”问题。循环引用指的是两个或多个类之间互相引用对方,形成一个循环的引用关系。这种情况会导致编译错误或运行时错
C++开发注意事项:避免C++代码中的循环引用问题
2023-11-22

C++开发注意事项:避免C++代码中的空指针异常

C++开发中,空指针异常是一种常见的错误,经常出现在指针没有被初始化或被释放后继续使用等情况下。空指针异常不仅会导致程序崩溃,还可能造成安全漏洞,因此需要特别注意。本文将介绍如何避免C++代码中的空指针异常。初始化指针变量C++中的指针必须
C++开发注意事项:避免C++代码中的空指针异常
2023-11-22

C++开发注意事项:避免C++性能优化的误区

C++开发注意事项:避免C++性能优化的误区在C++开发中,性能优化是一个非常重要的因素。优化代码的性能可以提高程序的执行效率和响应速度,对于大型项目和要求高性能的应用尤为重要。然而,在进行C++性能优化时,我们也需要注意一些误区,以避免引
C++开发注意事项:避免C++性能优化的误区
2023-11-22

使用 C++ lambda 表达式有哪些注意事项?

使用 c++++ lambda 表达式时需注意:小心捕获变量,避免意外修改。可通过引用或值捕获变量,引用捕获用于修改外部变量。lambda 表达式生命周期与捕获它的函数不同,可能导致内存泄漏。考虑使用函数指针或函数对象以优化性能。使用 C+
使用 C++ lambda 表达式有哪些注意事项?
2024-04-17

使用c语言常量要注意哪些事项

使用C语言常量时需要注意以下事项:常量的值不能被修改。一旦常量被定义后,它的值就不能被修改。常量的命名应该符合命名规范,使用具有描述性的名称,以提高代码的可读性。在定义常量时,建议使用大写字母,以便于与变量进行区分。在定义常量时,应使用co
2023-10-23

C++开发注意事项:避免C++代码中的编码规范问题

在进行C++开发时,除了关注功能实现和性能优化等方面的问题外,开发人员还需要注意代码的编码规范。良好的编码规范不仅可以提高代码的可读性和可维护性,还有助于减少错误和增加代码的一致性。本文将介绍一些常见的C++开发注意事项,帮助开发人员避免编
C++开发注意事项:避免C++代码中的编码规范问题
2023-11-22

C++开发注意事项:避免C++多态性的潜在问题

C++作为一种面向对象的编程语言,多态性是其的一大特点。多态性可以帮助我们在编写程序时更加灵活,有效地复用代码。但是,当我们不小心使用不当的多态性方法时,就会出现潜在的问题。本文将介绍一些C++开发注意事项,以避免多态性带来的潜在问题。避免
C++开发注意事项:避免C++多态性的潜在问题
2023-11-22

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录