指针:理解内存地址
如果你了解内存地址在计算机硬件中的工作原理,之前的讨论会更清晰一些。如果你还没读过,现在是阅读《位和字节如何工作》的好时机,以便充分理解位、字节和字。
所有计算机都拥有内存,也称为RAM(随机存取存储器)。例如,你的计算机现在可能安装了 16、32 或 64 兆字节的RAM。RAM 存储着计算机当前运行的程序以及它们正在操作的数据(它们的变量和数据结构)。内存可以简单地看作一个字节数组。在这个数组中,每个内存位置都有自己的地址——第一个字节的地址是 0,然后是 1、2、3,依此类推。内存地址的作用就像普通数组的索引。计算机可以随时访问内存中的任何地址(因此得名“随机存取存储器”)。它还可以根据需要将字节组合起来,形成更大的变量、数组和结构。例如,一个浮点变量在内存中占用 4 个连续字节。你可以在程序中进行以下全局声明:
广告
float f;
这个语句表示:“声明一个名为 f 的位置,它可以存储一个浮点值。”当程序运行时,计算机在内存中为变量 f 预留空间。该位置在内存空间中有一个固定的地址,如下所示:
当你想到变量 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:t\n"); for (i=0; i<=4; i++) printf("%d:%d\n", s[i], t[i]); printf("u = %d\n", u);
你从程序中看到的输出可能如下所示:
s:t 1:5 2:2 3:3 4:4 5:5 u = 5
为什么 t[0] 和 u 不正确?如果你仔细查看代码,会发现 for 循环写入了每个数组末尾之后的一个元素。在内存中,数组是彼此相邻放置的,如下所示:
因此,当你尝试写入不存在的 s[4] 时,系统会转而写入 t[0],因为 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 <stdio.h> int main() { int i,j; int *p; /* a pointer to an integer */ printf("%d %d\n", p, &i); p = &i; printf("%d %d\n", p, &i); return 0; }
这段代码告诉编译器打印出存储在 in p 中的地址,以及 i 的地址。变量 p 最初会带有一些随机值或为 0。 i 的地址通常是一个很大的值。例如,当我运行这段代码时,我收到了以下输出:
0 2147478276 2147478276 2147478276
这意味着 i 的地址是 2147478276。一旦语句 p = &i; 被执行, p 就包含了 i 的地址。你也可以尝试这个:
#include <stdio.h> void main() { int *p; /* a pointer to an integer */ printf("%d\n",*p); }
这段代码告诉编译器打印 p 指向的值。然而, p 尚未初始化;它包含地址 0 或某个随机地址。在大多数情况下,会产生段错误(或其他运行时错误),这意味着你使用了指向无效内存区域的指针。几乎总是,未初始化的指针或错误的指针地址是段错误的原因。
说了这么多,我们现在可以从全新的角度来看待指针了。例如,看这个程序:
#include <stdio.h> int main() { int i; int *p; /* a pointer to an integer */ p = &i; *p=5; printf("%d %d\n", i, *p); return 0; }
事情是这样的:
变量 i 占用 4 字节内存。指针 p 也占用 4 字节(在当今大多数使用的机器上,指针占用 4 字节内存。当今大多数 CPU 上的内存地址是 32 位的,尽管越来越趋向于 64 位寻址)。i 的位置有一个特定地址,在本例中是 248,440。一旦你说 p = &i;,指针 p 就持有该地址。因此,变量 *p 和 i 是等价的。
当你在程序中说出这样的话时:
printf("%d", p);
输出的是变量 i 的实际地址。