它指向另一个内存位置,就像您最近收到的明信片上的邮政地址指向您的家一样。指针包含内存位置的地址。邮政地址包含物理位置的地址 - 无论是建筑物、公寓还是邮局……“指向”因此意味着“指的是”的位置。指针和邮政地址都是如此。
指针必须指向另一个内存位置是不正确的。确实,现实世界软件中使用的绝大多数指针确实指向其他地方。那是99.9...% 在任何给定时间世界上存在的指针,小数点后还有 15 个 9。至少,最起码。
但是一个指针当然也可以指向它自己。有时,如果您只想在某处放置一个指针,并了解这个“某处”在哪里,这会很方便。例如,在大多数 C 和 C++ 实现中,以下简短程序将在运行时打印指针变量所在的堆栈地址(是的,实现不必将该变量存储在堆栈中,但在大多数实际情况下它确实是一个堆栈地址):
#include <stdio.h>
int main() {
void *pointer = &pointer;
printf("The variable \"%s\" is stored on stack at address %p\n", "pointer", &pointer);
}
例如,我得到以下输出:
The variable "pointer" is stored on stack at address 0x7fff2350e9f8
指针也可能指向no内存位置。空指针就是一个例子:
// Conformant C and C++
int *nullPointer1 = NULL; // Idiomatic C
int *nullPointer2 = 0;
// Conformant C++
int *nullPointer3 = nullptr; // Idiomatic C++
int *nullPointer4 = {}; // Idiomatic C++
std::unique_ptr<int> nullPointer5; // This is really C++ - raw pointers should be used only when truly needed, in library code etc.
这样的指针是可以的,但它们唯一有效的用途是检查它们是否确实为空。它们不能被取消引用——我的意思是,是的,你可以取消引用它们,但这是未定义的行为,现代编译器将代码中的未定义行为视为删除此类代码的许可。也就是说,如果您取消引用一个空指针,并且编译器可以证明它总是如此,那么执行这种取消引用的代码可能会被删除。示例(gcc 10.1 x64,-O3):
int main() {
int *pointer = 0;
int b = *pointer;
}
// produces same assembly output as
int main() { return 0; }
它变得更好。这段代码:
int main() {
int *pointer = 0;
*pointer = 5;
}
编译时,第二行被修改,就像你写了*pointer = 0,并且在赋值之后紧跟ub2“指令”,触发了未定义的指令异常。换句话说:如果您在映射第 0 个内存页的进程中运行它,它会失败,即使在地址 0 加载 5 将是一个有效的操作!相反,它会在地址0 处加载0,然后失败并出现异常。