分类: LINUX
2011-05-07 11:04:57
数组名代表了存放该数组的那块内存,它是这块内存的首地址。这就说明了数组名是一个地址,而且,还是一个不可修改的常量,完整地说,就是一个地址常量。数组名跟枚举常量一样,都属于符号常量。数组名这个符号,就代表了那块内存的首地址。注意了!不是数组名这个符号的值是那块内存的首地址,而是数组名这个符号本身就代表了首地址这个地址值,它就是这个地址。这就是数组名属于符号常量的意义所在。由于数组名是一种符号常量,它是一个右值,而指针,作为变量,却是一个左值,一个右值永远都不是左值,那么,数组名永远都不会是指针! 对于这段话我是这么理解的:数组名在经过编译之后将变成一个数值,这个数值就是该数组的首地址。由于数组名是一个地址,那么把它赋给一个指针变量也就不足为奇了。 例如有定义 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),显然不是我们的期望值,所以运行会出错也就理所应当了。 ? 几点细节:我们发现,使用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。