1 基本解释
extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。
另外,extern也可用来进行链接指定。

2 问题:extern 变量
在一个源文件里定义了一个数组:char a[6];
在另外一个文件里用下列语句进行了声明:extern char *a;
请问,这样可以吗?
答案与分析:
1)、不可以,程序运行时会告诉你非法访问。原因在于,指向类型T的指针并不等价于类型T的数组。extern char *a声明的是一个指针变量而不是字符数组,因此与实际的定义不同,从而造成运行时非法访问。应该将声明改为extern char a[ ]。
2)、例子分析如下,如果a[] = "abcd",则外部变量a=0x61626364 (abcd的ASCII码值),*a显然没有意义
显然a指向的空间(0x61626364)没有意义,易出现非法内存访问。
3)、这提示我们,在使用extern时候要严格对应声明时的格式,在实际编程中,这样的错误屡见不鲜。
4)、extern用在变量声明中常常有这样一个作用,你在*.c文件中声明了一个全局的变量,这个全局的变量如果要被引用,就放在*.h中并用extern来声明。

4 问题:extern 函数2
当函数提供方单方面修改函数原型时,如果使用方不知情继续沿用原来的extern申明,这样编译时编译器不会报错。但是在运行过程中,因为少了或者多了输入参数,往往会照成系统错误,这种情况应该如何解决?
答案与分析:
目前业界针对这种情况的处理没有一个很完美的方案,通常的做法是提供方在自己的xxx_pub.h中提供对外部接口的声明,然后调用方include该头文件,从而省去extern这一步。以避免这种错误。
宝剑有双锋,对extern的应用,不同的场合应该选择不同的做法。

5 问题:extern “C”
在C++环境下使用C函数的时候,常常会出现编译器无法找到obj模块中的C函数定义,从而导致链接失败的情况,应该如何解决这种情况呢?

答案与分析:
C++语言在编译的时候为了解决函数的多态问题,会将函数名和参数联合起来生成一个中间的函数名称,而C语言则不会,因此会造成链接时找不到对应函数的情况,此时C函数就需要用extern “C”进行链接指定,这告诉编译器,请保持我的名称,不要给我生成用于链接的中间函数名。
下面是一个标准的写法:
//在.h文件的头上
#ifdef __cplusplus
#if __cplusplus
extern "C"{
#endif
#endif /* __cplusplus */


//.h文件结束的地方
#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif /* __cplusplus */

3 问题:extern 函数1
常常见extern放在函数的前面成为函数声明的一部分,那么,C语言的关键字extern在函数的声明中起什么作用?
答案与分析:
如果函数的声明中带有关键字extern,仅仅是暗示这个函数可能在别的源文件里定义,没有其它作用。即下述两个函数声明没有明显的区别:
extern int f(); 和int f();
当然,这样的用处还是有的,就是在程序中取代include “*.h”来声明函数,在一些复杂的项目中,我比较习惯在所有的函数声明前添加extern修饰。

-----------------------------------------------------------

extern数组与extern指针

数组名代表了存放该数组的那块内存,它是这块内存的首地址。这就说明了数组名是一个地址,而且,还是一个不可修改的常量,完整地说,就是一个地址常量。数组名跟枚举常量一样,都属于符号常量。数组名这个符号,就代表了那块内存的首地址。注意了!不是数组名这个符号的值是那块内存的首地址,而是数组名这个符号本身就代表了首地址这个地址值,它就是这个地址。这就是数组名属于符号常量的意义所在。由于数组名是一种符号常量,它是一个右值,而指针,作为变量,却是一个左值,一个右值永远都不是左值,那么,数组名永远都不会是指针!

对于这段话我是这么理解的:数组名在经过编译之后将变成一个数值,这个数值就是该数组的首地址。由于数组名是一个地址,那么把它赋给一个指针变量也就不足为奇了。

例如有定义

char a[14];

char * p;

char * q;

void foo(char * pt)

{

};

考虑以下几种赋值:

p=a;//合法,将一个地址赋给一个指针变量;

q=p;//合法,将一个指针变量的值赋给另一个指针变量;

a=p;//非法,a是数组名即地址,不是一个变量,不可被赋值(也就是上文中说的"数组名是右值")

再看这几种调用:

foo(a);//将一个地址作为参数传入函数,函数中用一个指针变量接收这个地址值

foo(p);//将一个指针变量的值传入函数(也是一个地址),函数中用一个指针变量接收这个地址

可以看出许多时候数组名和指针可以等同地看待,而c也把它们看作是兼容的类型对待,这就是为什么我那个错误的声明不被编译器在语法检查的时候“喀嚓”的原因。

关于extern的作用,许多地方都有说明,例如可以在c++里进行c格式函数的声明,可以声明一个变量或函数是外部变量或外部函数;我们这里要讨论的是外部变量的声明。被extern修饰的全局变量不被分配空间,而是在连接的时候到别的文件中通过查找索引定位该全局变量的地址。

有了这些基础后,我们现在正式开始研究extern 数组和extern 指针的问题:

首先在一个.c文件中有如下定义:

char a[]={1,2,3,4};

分析:这是一个数组变量的定义,编译器将为这个数组分配4字节的空间,并且建立一个索引,把这个数组名、数组类型和它被分配的空间首地址对应起来。它被编译之后生成一个中间文件

然后我们在另一个.c文件中分别以不同的形式进行声明:

(1) extern char a[];

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

(2) extern char * a;

分析:这是一个外部变量的声明,它声明了一个名为a的字符指针,编译器看到这个声明就知道不必为这个指针变量分配空间,这个.c文件中所有对指针a的引用都化为一个不包含类型的标号,具体地址的定位留给连接器完成。编译完成之后仍然得到一个中间文件,连接器遍历这个文件,发现有未经定位的标号,于是它搜索其他中间文件,试图寻找到一个匹配的空间地址,经过一番搜索,找到了一个分配过空间的名为a的地方(也就是我们先定义的那个字符数组),连接器并不知道它们的类型,仅仅是发现它们的名字一样,就认为应该把extern声明的标号连接到数组a的首地址上,因此连接器把指针a对应的标号替换为数组a的首地址。这里问题就出现了:由于在这个文件中声明的a是一个指针变量而不是数组,连接器的行为实际上是把指针a自身的地址定位到了另一个.c文件中定义的数组首地址之上,而不是我们所希望的把数组的首地址赋予指针a(这很容易理解:指针变量也需要占用空间,如果说把数组的首地址赋给了指针a,那么指针a本身在哪里存放呢?)。这就是症结所在了。所以此例中指针a的内容实际上变成了数组a首地址开始的4字节表示的地址(如果在16位机上,就是2字节)。本例中指针a的初值将会是0x0a090807(little endian)(4321),显然不是我们的期望值,所以运行会出错也就理所应当了。

?

几点细节:我们发现,使用extern修饰的变量在连接的时候只找寻同名的标号,不检查类型,例如如果我们定义的甚至不是一个变量而是一个全局的函数,比如去掉定义

char a[]={....};

代之以

void a(){};

连接器居然也会连接通过。

实例如下:

比如在a.c文件中有这样一段代码

int g_i[] = {1, 2, 3, 4};

extern void testdotp();

void main(void)

{

int i = 0;

int num = 0;

num = sizeof(g_i) / sizeof(int);

printf("in main: g_i =%d", i, g_i);

for (i = 0; i < num; i++)

{

printf("g_i[%d] = %d ", i, g_i[i]);

}

printf("/n");

testdotp();

}

而在b.c中的代码如下:

extern int *g_i;

void testdotp()

{

printf("*(&g_i + 2) = %d/n", *(&g_i + 2));

printf("&g_i = %d/n", &g_i);

printf("&g_i + 1= %d/n", &g_i + 1);

printf("g_i = %d/n", g_i);

printf("g_i + 1 = %d/n", g_i + 1);

}

运行结果为

in main: g_i =134518852 g_i[0] = 1 g_i[1] = 2 g_i[2] = 3 g_i[3] = 4 /n
in b.c:
*(&g_i + 2) = 3
&g_i = 134518852
&g_i + 1= 134518856
g_i = 1
g_i + 1 = 5

分析如下:

因为b.c文件中g_i变量的地址是a.c文件中g_i数组的首地址,故g_i的值为g_i[0]的值,&g_i的值为g_i地址的首地址。

而*(&g_i + 2)的值:&g_i的值为g_i数组的首地址,(&g_i + 2)就为数组g_i第3个元素的地址,*(&g_i + 2)就为第2个元素的值,即3。

&g_i + 1:由于&g_i的值为g_i数组首地址,由于在32位机上运行,故&g_i + 1在&g_i基础上加上4个字节

g_i + 1:由于g_i是一个指针变量,g_i变量内存放的是地址,又因为g_i的值为1,而g_i + 1就为1 + 4的单元的内存空间(32位机上),故g_i + 1为5。

 

转载于:https://www.cnblogs.com/york-hust/archive/2012/05/30/2526455.html

extern使用说明相关推荐

  1. C艹笔记--面向对象程序设计

    文章目录 类与对象简介 类与结构的区别 定义成员函数 重载与覆盖的区别 虚函数(Virtual) 继承 继承小总结 [C++中::和:, .和->的作用和区别](https://zhuanlan ...

  2. extern 与 #define 使用说明

    extern与#define两者的使用从本质上讲没有本质的区别,无非就是为编译器的编译格式所服务的: 他们在编译的过程不是同一个阶段,#define是在编译器的预编译时期,而extern是在编译器的编 ...

  3. 微软图表控件MsChart使用说明[转]

    微软图表控件MsChart使用说明 建立一个.NET3.5的Web项目,像使用普通控件一样拖放到要使用的Web界面即可.初步研究了一下,整个图形控件主要由以下几个部份组成: 1.Annotations ...

  4. C/C++调用Fortran的使用说明

    C/C++调用Fortran的使用说明 [日期:2010-11-22] 来源:C/C++ 作者:C/C++ [字体:大 中 小] 这里将详细介绍一下在C++中如何调用用Fortran语言编写函数的问题 ...

  5. toLua:简洁的使用说明

    tolua的最新版本是5.0,下载地址:http://www.tecgraf.puc-rio.br/~celes/tolua/ 以下是简单的使用说明: 功能内容:可以在LUA脚本中使用C++的方便对象 ...

  6. MINIO使用说明(附文件上传下载)

    一.项目信息说明 minio Server版本:linux-amd64/minio 与 windows-amd64/minio.exe minio Client版本:linux-amd64/mc 与 ...

  7. DexExtractor的原理分析和使用说明

    本文博客链接:http://blog.csdn.net/qq1084283172/article/details/53557894 周末有空就写下博客了,今天来扯一扯Android平台的脱壳工具Dex ...

  8. C++ 笔记(05)— 变量(变量定义、声明、初始化、extern关键字、变量之间转换)

    1. 变量定义 变量定义就是告诉编译器在何处创建变量的存储,以及如何创建变量的存储.变量定义指定一个数据类型,并包含了该类型的一个或多个变量的列表,如下所示: type variable_list; ...

  9. abaqus高性能服务器怎么用,高性能计算平台ABAQUS任务调度使用说明作者陈林E-Mailchenlin.PDF...

    高性能计算平台ABAQUS任务调度使用说明作者陈林E-Mailchenlin.PDF 高性能计算平台ABAQUS 任务调度使用说明 作者:陈林 E-Mail:chenlin@ 日期:2017-1-10 ...

最新文章

  1. mysql变量使用总结
  2. Python中获取异常(try Exception)信息
  3. Kafka剖析(一):Kafka背景及架构介绍--转
  4. 【机器学习】LR与最大熵模型的关系
  5. 如何实现远程登陆,如何实现远程桌面
  6. k8s dashboard_k8s集群部署Dashboard
  7. 个人作业-Week1
  8. 【SpringCloud】 第十篇: 高可用的服务注册中心
  9. EurekaServer高可用的注册中心集群搭建
  10. java.lang.ClassNotFoundException: com.sun.xml.ws.spi.ProviderImpl解决办法
  11. 使用Windows Server 2003搭建一个asp+access网站
  12. linux:fdisk分区命令详解
  13. Spring Boot/Cloud 背后豪华的研发团队
  14. 支付宝小程序平台的IM聊天插件
  15. 贫苦云主机用户的安全加固策略
  16. JavaScript删除节点
  17. 监控电脑屏幕python
  18. sql 查询记录条数
  19. UE4+Cubemap(jpg导入UE4生成Cubemap)
  20. 如何使用Win10自带的截图工具截屏

热门文章

  1. Linux网络监测在线工具
  2. AngularJS路由使用案例
  3. 微软自带输入法如何关闭桌面右下角「拼」图标
  4. python多线程下载ts_基于Python的ERA-5多线程下载(1)
  5. 预测大盘最准确的指标_上证指数11月23日走势预测
  6. springboot项目不输出nohup.out日志
  7. visio2013画图时两条直线交叉 如何让它不弯曲
  8. toArray()方法使用说明
  9. 架构与设计 之一 C 嵌入式设计模式(Design Patterns for Embedded Systems in C)的学习记录
  10. 题库练习5(句子逆序、字符串排序、int型二进制表示中1的个数、购物单)