我知道指针变量存储的是另一个变量的内存地址。但它的意思是 "指针指向另一个内存位置"? 这句话是什么意思?"指针指向..."?
从根本上来说,一个 可变的 是一个名称和内存中的一个位置之间的关联。在其他语言中,它可能比这更复杂,其他信息通常与变量相关联(例如类型),但这与这里无关。
指针变量是一个变量(一个引用内存中某个区域的名称),但这个内存区域里有什么?是什么呢?指针 本身?嗯,它是一个内存地址,是引用另一个内存位置的方式。而不是说 "指针 有价值 这个其他的内存位置",我们说指针 参考文献 或 指向 这另一个内存位置。
我们可以给这另一个内存位置起个名字。这将使它成为一个变量。因此,我们也可以说指针是 参考文献 或 指向 这个其他变量。
在下面的代码中,我们有一个指针--------。p
归属 i
:
int i = 1000;
int *p = &i;
我们可能有这个记忆。
p at addr 0x1122 | ⋮ |
i at addr 0x3344 +--------------+
| 0x3344 | 0x1122
+--------------+
| ⋮ |
+--------------+
| 1000 | 0x3344
+--------------+
| ⋮ |
那么下面两个语句有什么区别呢?
int *q = p;
int j = *p;
在第一个语句中,我们复制的是指针本身,即内存地址。
在第二条语句中,我们是间接地访问 i
. 我们告诉C 提述 指针,也就是访问指针所指向的东西。这就是 i
.
我们可能有这个记忆。
p at addr 0x1122 | ⋮ |
i at addr 0x3344 +--------------+
q at addr 0x5566 | 0x3344 | 0x1122
j at addr 0x7788 +--------------+
| ⋮ |
+--------------+
| 1000 | 0x3344
+--------------+
| ⋮ |
+--------------+
| 0x3344 | 0x5566
+--------------+
| ⋮ |
+--------------+
| 1000 | 0x7788
+--------------+
| ⋮ |
如果你改变了其中一个人,其他人会怎么样?*p
, *q
, i
和 j
? 我让你假设和实验:)
它指向另一个内存位置,就像你最近收到的明信片上的邮政地址指向你的家一样。一个指针包含了一个内存位置的地址。邮政地址包含了一个物理位置的地址--无论是一栋楼,一间公寓,一个邮政信箱......。因此,"指向 "的意思是 "指的是一个位置的"。指向和邮政地址都是如此。
并不是说指针一定要指向以下地点 另一个 内存位置。的确,在现实世界的软件中,绝大多数使用的指针确实指向其他地方。那就是 99.9...%
世界上任何时候存在的指针的数量,小数点后还有15个九。最起码是这样的。
但指针也肯定可以指向自己。有时候,如果你只想把指针插在某个地方,并了解这个 "地方 "在哪里,那就很方便了。例如,在大多数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
一个指针也可能指向 没有 内存位置。空指针就是一个例子。
// 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-3个内存页的进程中运行这条指令,它就会失败,即使加载了 5
在地址 0
会是一个有效的操作!相反,它会加载 0
在地址 0
,然后以异常方式失败。