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_i3个元素的地址,*(&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 + 15

 

  • 5
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
在C语言中,extern关键字用于声明变量或函数的定义在其他文件中。它的作用是告诉编译器在遇到该变量或函数时,在其他模块中寻找其定义。另外,extern还可以用来进行链接指定。 在使用extern关键字时,需要注意以下几点: 1. 在一个源文件中定义了一个数组char a,在另外一个文件中使用extern char *a进行声明,这是不合法的。因为指向类型T的指针与类型T的数组并不等价。应该将声明改为extern char a[]。这样才能正确访问数组元素。 2. extern关键字常常用于变量声明中,当在.c文件中声明了一个全局变量,并且要被其他文件引用时,需要将其放在头文件中,并使用extern进行声明。这样可以确保其他文件能正确访问该变量。 3. 当C语言调用一个由C语言编写的动态链接库(DLL)时,在包括DLL的头文件或声明接口函数时,应该加上extern "C" {}。这是为了确保C语言能够正确引用DLL中的函数和变量。 总结来说,extern关键字在C语言中的使用常见于实现C与C及其他语言的混合编程。它能够帮助我们引用其他文件的函数和变量,并确保链接的正确性。在使用extern时需要注意格式的严格对应,以避免出现运行时错误。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [深入理解C语言之extern](https://blog.csdn.net/shenwanjiang111/article/details/52912753)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值