内存地址
如果您在计算机硬件的层面上理解了内存地址的原理,前面的讨论就会变得更加清晰了。您若还没有阅读过位和字节,那么现在应该去读一遍这篇文章,它会帮您弄清位、字节和字的概念。
所有计算机都配有内存,也称RAM(随机存取存储器)。比如您的计算机现在可能配有16、32或64兆字节的RAM。RAM用于存储计算机正在执行的程序以及程序使用的数据(即程序的变量和数据结构)。内存可以看作是一个简单的字节数组。在这个数组中,每个内存单元都有自己的地址:第一个字节的地址是0,后面依次是1、2、3,等等。内存地址相当于普通数组的下标。计算机可以随时访问内存的任何位置(所以称为“随机存取存储器”)。根据需要,多个字节可以组合起来构成较大的变量、数组和结构体。例如,一个浮点型变量占用4个连续字节的内存空间。您可以像下面这样在程序中声明一个全局变量:
float f;
上面这条语句的意思是说:“声明一个名为f的可以保存一个浮点值的内存位置。”程序执行的时候,计算机就会在内存中某个位置为变量f预留空间。这个位置在内存空间中有确定的地址,如下图所示:
变量f在内存某处占用四个字节的空间。此位置有确定的地址,本例中是248,440。
您认为的变量f在计算机看来就是一个具体的内存地址(如248,440)。因此,当您写下这样的语句时:
f=3.14;
编译器可能把它翻译成:“将数值3.14装入到内存地址是248,440的位置。”计算机总是通过操作地址和操作地址的值来使用内存的。
另外,计算机的这种使用内存的方式还会带来一些“副作用”。例如,您的程序包含了下面的代码:
int i, s[4], t[4], u=0;
for (i=0; i=4; i++)
s[i] = i;t[i] =i;
printf("s:tn");
for (i=0; i=4; i++)
printf("%d:%dn", s[i], t[i]);
printf("u=%dn", u);
您很可能会看到这样的程序输出:
s:t
1:5
2:2
3:3
4:4
5:5
u=5
t[0]和u的值为什么不对?仔细观察代码就会发现,两个for循环在访问数组时都越界了一个元素。在内存中,两个数组是相邻存储的,如下图所示:
因此,当向s[4]这个并不存在的数组元素写数据时,实际上覆盖了t[0],因为它正处于s[4]的位置上。当向t[4]写数据时,实际上就覆盖了u。对于计算机来说,s[4]只是一个可以写数据的内存位置而已。您会发现尽管计算机执行了程序,但程序却是不正确或不正常的。这个程序在运行时损坏了t 数组。执行下面的语句将会产生更加严重的后果:
s[1000000]=5;
s[1000000] 这个位置很可能在程序的内存空间之外。也就是说,您向不属于您程序的内存空间写入数据。在具备存储空间保护的系统上(如UNIX和Windows 98/NT),这样的语句会使系统终止程序的执行。而在其他系统(如Windows 3.1和早期的Mac)会听任程序为所欲为。结果是另一个程序的代码或变量被破坏了。这种侵犯的后果小到不产生任何影响,大到导致彻底的系统崩溃。在内存中,变量i、s、t、u具有前后相邻的确定地址。因此,如果您对一个变量越界写入,计算机会照您说的做,但将破坏另一处内存位置的数据。
因为C和C++在访问数组元素时不做任何形式的边界检查,所以您作为一名程序员自己一定要严加注意数组边界,不要超越。对超越数组边界内容的无意读写总是会导致程序出现问题。
下面的代码是另一个例子:
#include
int main()
int i,j;int *p; /* 指向整数的指针 */
printf("%d %dn", p, i);p = i;printf("%d %d n", p, i);return 0;
这段代码告诉编译器打印p保存的地址和i占用的地址。变量p一开始是一个随意的数值或0。i的地址一般是一个很大的数字。例如,运行这段代码后得到的输出是:
0 2147478276
2147478276 2147478276
可知i的地址是2147478276。p=i;这条语句执行之后,就保存了i的地址。再试试下面的代码:
#include
void main()
int *p; /* 指向整数的指针 */
printf("%dn",*p);
这段代码告诉编译器打印p指向的值。然而p尚未初始化,它保存的地址是0或一个随机地址。多数情况下这将引发一个段错误(或某些其他运行时错误),表明您使用了一个指向无效内存空间的指针。段错误几乎总是由未初始化的指针或错误的内存地址导致的。
通过以上的介绍,现在我们可以从新的角度来理解指针了。请看下面的例子:
#include
int main()
int i;int *p; /*
p = i;*p=5;printf("%d %d
程序的运行过程是这样的:
变量i占4字节的内存。指针p也占4字节(在当今使用的多数计算机上,一个指针占4字节内存。现在大部分CPU的内存地址都是32位的,尽管64位寻址已渐成趋势)。i所代表的内存位置有一个确定的地址,本例中是248,440。执行过p=i;后,指针p也将保存同样的地址。因此变量*p和i是等价的。
指针p原样保存着i的地址。当执行如下的语句时:
printf("%d", p);
程序就会打印变量i的实际内存地址。