首页 > extern数组与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 的初值将会是0x0a090807little endian ),显然不是我们的期望值,所以运行会出错也就理所应当了。

?

几点细节:我们发现,使用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 );

       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);

}

运行结果为

g_i[0] = 1 g_i[1] = 2 g_i[2] = 3 g_i[3] = 4

*(&g_i + 2) = 3

&g_i = 4344368

&g_i + 1= 4344372

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

转自:http://blog.csdn.net/hxg130435477/archive/2009/03/21/4012686.aspx

更多相关:

  • 51 三菱PLC可读不可写Q:MT8102IQ和三菱Q系列PLC通讯,屏无法写入PLC,但是可以读取PLC的状态和数值?A:PLC程序中"允许RUN中写入"打钩,程序下载重启后解决。52 控制不了输入点Q:触摸屏做了三菱PLC的X点的元件,但是控制不了X输出?A:是的,PLC端X点无法通过触摸屏控制输出,屏上只能做X点的显示。53 M...

  • 传统方法(仅适用于普通项目):   1、在vscode中安装 Live Server 插件: 2、安装成功后,vscode右下角会有 Go Live 标识点击: 3、cmd ipconfig 查询自己电脑的ip地址: 4、复制地址替换端口前的地址(http://127.0.0.1:8080修改为http://192.168....

  • ngx_http_geo_module模块,默认情况下,nginx会加载,除非人为的 --without-http_geo_module。 这个模块提供了一个非常好用的geo指令,可以用它来创建变量,诞生其值依赖于客户端IP地址。 ngx_http_geo_module 模块官网地址 http://nginx.org/en...

  • uboot启动Linux内核过程分为4大步骤: 问题2: uboot阶段DDR的分区的问题 上述步骤2和步骤4中,有将uboot/kernel拷贝纸DDR的步骤,具体要拷贝到DDR的什么位置呢? 分清楚这两个概念: 链接地址:链接时指定的地址(指定方式为:Makefile中用-Ttext,或者链接脚本) 运行地址:程序实际运行...

  • 在Linux系统中,以32bit x86系统来说,进程的4GB内存空间(虚拟地址空间)被划分成为两个部分 ------用户空间和内核空间,大小分别为0-3G,3-4G。        用户进程通常情况下,只能访问用户空间的虚拟地址,不能访问到内核空间。          每个进程的用户空间存放用户的程序和代码(堆栈,数据区,代码区等)...

  • 原文出处: 韩昊    1 2 3 4 5 6 7 8 9 10 作 者:韩 昊 知 乎:Heinrich 微 博:@花生油工人 知乎专栏:与时间无关的故事   谨以此文献给大连海事大学的吴楠老师,柳晓鸣老师,王新年老师以及张晶泊老师。   转载的同学请保留上面这句话,谢谢。如果还能保留文章来源就更感激不尽了。 我保证这篇文章...

  • 原文出处: 韩昊   我保证这篇文章和你以前看过的所有文章都不同,这是 2012 年还在果壳的时候写的,但是当时没有来得及写完就出国了……于是拖了两年,嗯,我是拖延症患者…… 这篇文章的核心思想就是: 要让读者在不看任何数学公式的情况下理解傅里叶分析。 傅里叶分析不仅仅是一个数学工具,更是一种可以彻底颠覆一个人以前世界观的思维...

  • 很多Linux高手都喜欢使用screen命令,screen命令可以使你轻松地使用一个终端控制其他终端。尽管screen本身是一个非常有用的工具,byobu作为screen的增强版本,比screen更加好用而且美观,并且提供有用的信息和快捷的热键。 想象一下这样一个场景:你通过Secure Shell(ssh)链接到一个服务器,并...

  • NarrowbandPrimary Synchronization Signal时域位置每1个SFN存在一个NPSSSFNSubframeSymbol长度每个SFN5最后11个symbol11个symbols频域位置NB-IOT下行带宽固定180kHz,一个PRB,12个子载波。...

  •  [h1]反斜杠只能够阻止一个字符  [h2]位于键盘的左上角,和~公用一个键。...

  • 学习目标:了解什么是数组;数组如何访问内存地址(一维,二维);什么是数组是由相同类型的元素的集合所组成的数据结构,分配一块连续的内存来存储。利用元素的索引可以计算出该元素对应的存储地址。 最简单的数据结构类型是一维数组。数组如何实现随机访问?数组是一种线性表数据结构,用一直连续的内存空间来储存一组具有相同类型的数据。根据数组的特性(连...

  • 一、静态数据及动态数组的创建     静态数据:               int a[10];             int a[]={1,2,3};             数组的长度必须为常量。     动态数组:             int len;             int *a=new int...

  • 给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。 示例 1: 给定 nums = [3,2,2,3], val...

  • 给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 示例 1: 给定数组 nums = [1,1,2],  函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2...

  • 文章目录1. 数组的声明2. 数组元素的遍历3. 数组的截取4. Go 语言的切片5. 数组 和 切片的共同点...