在学习C++时,或者至少是我通过C++ Primer学习时,指针被称为它们所指向元素的“内存地址”。我想知道这个说法的程度如何。
例如,如果两个元素 *p1
和 *p2
满足条件 p2 = p1 + 1
或 p1 = p2 + 1
, 那么它们当且仅当在物理内存中相邻吗?
你应该将指针视为虚拟内存的地址:现代消费者操作系统和运行时环境在物理内存与指针值之间至少放置了一层抽象。
至于你的最后陈述,即使在虚拟内存地址空间中,你也不能作出这样的假设。指针算术仅适用于连续内存块(如数组)内部。虽然在 C 和 C++ 中允许将指针分配给数组(或标量)的下一个位置,但对于取消引用此类指针的行为是未定义的。在 C 和 C++ 的上下文中推测物理内存中的相邻性是毫无意义的。
完全不是这样。
C++ 是对计算机将执行的代码的一种抽象。我们在一些地方看到这种抽象泄漏(例如需要存储的类成员引用),但总的来说,如果你只编写针对抽象的代码而不涉及其他内容,那么你会更好。
指针就是指针。它们指向某些东西。它们在实际中是否被实现为内存地址?也许。它们也可能被优化掉,或者(例如指向成员的指针)它们可能比简单的数字地址更加复杂。
当你开始将指针视为映射到内存地址的整数时,你就开始忘记例如持有指向不存在对象的指针是未定义的(你不能随便增加和减少指向任何内存地址的指针)。
const_cast
修改一个const
对象,还可以用Stanley刀杀死一屋子人。标准不允许一个格式良好的程序做这些事情。这不仅仅是一种风格问题。 - Lightness Races in Orbit*p1和*p2具有属性p2 = p1 + 1或p1 = p2 + 1,当且仅当它们在物理内存中相邻
只有当p1
和p2
具有相同类型或指向相同大小的类型时,才是正确的。
p1
和p2
必须指向同一数组中相邻的元素。顺便提一下,这不是*p1
和*p2
的属性。 - chqrliep1
和 p2
指向不同的、不属于同一数组的相同类型对象,则即使 (uintptr_t)p1 == (uintptr_t)(p2 + 1)
,也不能保证 p1 == p2 + 1
。 - chqrlieT* ptr;
,则 ptr++
会执行 ((char*)ptr) + sizeof(T);
或者 ptr + n
是 ((char*)ptr) + n*sizeof(T)
。这也意味着您的 p1 == p2 + 1
需要 p1
和 p2
是相同类型的 T
,因为 +1
实际上是 +sizeof(T)*1
。与其他变量一样,指针存储数据,可以是内存地址,其中存储其他数据。
因此,指针是一个具有地址并可能保存地址的变量。
请注意,指针不一定总是保存地址。它可能保存非地址ID/句柄等。因此,将指针称为地址并不明智。
关于您的第二个问题:
指针算术适用于连续的内存块。如果p2 = p1 + 1
并且两个指针具有相同的类型,则p1
和p2
指向一个连续的内存块。因此,地址p1
和p2
保持相邻。
p2 == p1 + 1
,那么 p1
和 p2
指向 内存中相邻的对象。指针 p1
和 p2
本身可以在内存中的任何位置,甚至可能根本不在内存中。 - chqrlie除非编译器优化掉指针,否则它们是存储内存地址的整数。它们的长度取决于代码正在编译的计算机,但通常可以将它们视为int。
实际上,您可以通过使用printf()
打印存储在它们上面的实际数字来检查它们。
但请注意,type *
指针的增量/减量操作是通过sizeof(type)
完成的。使用此代码自行查看(已在Repl.it上进行测试):
#include <stdio.h>
int main() {
volatile int i1 = 1337;
volatile int i2 = 31337;
volatile double d1 = 1.337;
volatile double d2 = 31.337;
volatile int* pi = &i1;
volatile double* pd = &d1;
printf("ints: %d, %d\ndoubles: %f, %f\n", i1, i2, d1, d2);
printf("0x%X = %d\n", pi, *pi);
printf("0x%X = %d\n", pi-1, *(pi-1));
printf("Difference: %d\n",(long)(pi)-(long)(pi-1));
printf("0x%X = %f\n", pd, *pd);
printf("0x%X = %f\n", pd-1, *(pd-1));
printf("Difference: %d\n",(long)(pd)-(long)(pd-1));
}
ints: 1337, 31337
doubles: 1.337000, 31.337000
0xFAFF465C = 1337
0xFAFF4658 = 31337
Difference: 4
0xFAFF4650 = 1.337000
0xFAFF4648 = 31.337000
Difference: 8
&
和*
实际上是用于引用(“获取此变量的内存地址”)和取消引用(“获取此内存地址的内容”)的操作符。float*
强制转换为int*
来获取浮点数的IEEE 754二进制值:#include <iostream>
int main() {
float f = -9.5;
int* p = (int*)&f;
std::cout << "Binary contents:\n";
int i = sizeof(f)*8;
while(i) {
i--;
std::cout << ((*p & (1 << i))?1:0);
}
}
结果为:
Binary contents:
11000001000110000000000000000000
本例来源于https://pt.wikipedia.org/wiki/IEEE_754。 在任何转换器上检查。
你提供的特定示例:
例如,如果两个元素*p1和*p2具有属性p2 = p1 + 1或p1 = p2 + 1,那么它们是否仅在物理内存中相邻?
这将在没有平面地址空间的平台上失败,例如PIC。要访问PIC上的物理内存,您需要一个地址和一个银行号,但后者可以从外部信息(如特定源文件)派生。因此,在来自不同银行的指针上进行算术运算会产生意想不到的结果。
p1
和p2
的条件是如何与指针实际上是内存地址相矛盾的? - Amit Upadhyay