当你学习C ++时,或者至少当我通过 C ++ Primer 学习它时,指针被称为"内存地址"他们指出的元素。我想知道这是真实的程度。
例如,两个元素*p1
和*p2
属性p2 = p1 + 1
或p1 = p2 + 1
当且仅当它们在物理上相邻时存储器?
答案 0 :(得分:32)
您应该将指针视为虚拟内存的地址:现代消费者操作系统和运行时环境在物理内存和您看到的指针值之间至少放置一层抽象。
至于你的最终陈述,即使在虚拟内存地址空间中也无法做出这样的假设。指针算法仅在连续内存(如数组)的块内有效。虽然允许(在C和C ++中)指定一个指向一个数组(或标量)之后的点的指针,但引用这样一个指针的行为是未定义的。在C和C ++环境下假设物理内存中的邻接是没有意义的。
答案 1 :(得分:16)
完全没有。
C ++是对计算机将执行的代码的抽象。我们在一些地方看到这种抽象泄漏(例如,类成员引用需要存储),但一般来说,如果你对抽象进行编码而没有其他任何东西,你会更好。
指针是指针。他们指向事物。它们会被实现为内存地址吗?也许。它们也可以被优化,或者(例如指向成员的指针)它们可能比简单的数字地址更复杂。
当你开始将指针想象为映射到内存中地址的整数时,你会开始忘记它的未定义以保存指向不对称的对象的指针。存在(你不能只是毫不犹豫地增加和减少指针到你喜欢的任何内存地址。)
答案 2 :(得分:11)
正如许多答案已经提到的那样,它们不应被视为内存地址。查看这些答案and here以了解它们。处理你的上一个陈述
* p1和* p2具有属性p2 = p1 + 1或p1 = p2 + 1当且仅当它们在物理内存中相邻时
仅在p1
和p2
属于同一类型或指向相同大小的类型时才正确。
答案 3 :(得分:5)
绝对正确地将指针视为内存地址。这就是我所使用的所有编译器中的内容 - 用于许多不同的处理器架构,由许多不同的编译器生产商制造。
然而,编译器做了一些有趣的魔术,以帮助你实现正常的内存地址[至少在所有现代主流处理器中]是字节地址,而指针引用的对象可能不是一个字节。因此,如果我们有T* ptr;
,则ptr++
将((char*)ptr) + sizeof(T);
或ptr + n
为((char*)ptr) + n*sizeof(T)
。这也意味着您的p1 == p2 + 1
要求p1
和p2
属于同一类型T
,因为+1
实际上是+sizeof(T)*1
。< / p>
以上&#34;有一个例外;指针是内存地址&#34;,这是成员函数指针。它们是特殊的#34;现在,请忽略它们的实际实现方式,足以说它们不是&#34;只是内存地址&#34;。
答案 4 :(得分:5)
操作系统为您的程序提供物理机的抽象(即您的程序在虚拟机中运行)。因此,您的程序无法访问您计算机的任何物理资源,无论是CPU时间,内存等;它只需向操作系统询问这些资源。
对于内存,您的程序在由操作系统定义的虚拟地址空间中工作。此地址空间具有多个区域,例如堆栈,堆,代码等。指针的值表示此虚拟地址空间中的地址。实际上,连续地址的2个指针将指向此地址空间中的连续位置。
但是,这个地址空间被操作系统分成页面和段,这些页面和段根据需要从内存中交换出来,因此您的指针可能指向或不指向连续的物理内存位置,并且在运行时无法分辨如果这是真的。这还取决于操作系统用于分页和分段的策略。
底线是指针是内存地址。但是,它们是虚拟内存空间中的地址,由操作系统决定如何将其映射到物理内存空间。
就您的计划而言,这不是问题。这种抽象的一个原因是让程序相信它们是机器的唯一用户。想象一下,如果您在编写程序时需要考虑其他进程分配的内存,那么您必须经历的噩梦 - 您甚至不知道哪些进程将与您的进程同时运行。此外,这是一种强制执行安全性的好方法:您的进程不能(至少不应该)恶意访问另一个进程的内存空间,因为它们在2个不同的(虚拟)内存空间中运行。
答案 5 :(得分:4)
与其他变量一样,指针存储一个数据,该数据可以是存储其他数据的存储器地址。
因此,指针是一个具有地址并可能保存地址的变量。
请注意,it is not necessary that a pointer always holds an address。它可能包含非地址ID /句柄等。因此,将指针作为地址说不是明智之举。
关于你的第二个问题:
Pointer arithmetic对连续的内存块有效。如果p2 = p1 + 1
和两个指针的类型相同,则p1
和p2
指向一个连续的内存块。因此,地址p1
和p2
保持彼此相邻。
答案 6 :(得分:4)
我认为this answer有正确的想法但是术语很差。 C指针提供的与抽象完全相反。
抽象提供了一种相对容易理解和推理的心理模型,即使硬件更复杂,难以理解或更难以推理。
C指针与此相反。即使真正的硬件通常更简单,更容易推理,它们也会考虑硬件的可能的困难。它们将你的推理限制在最复杂硬件最复杂部分的联合所允许的范围内,而不管手头的硬件实际上有多么简单。
C ++指针添加了一个C不包含的东西。它允许比较相同类型的所有指针的顺序,即使它们不在同一个数组中。这允许更多的心理模型,即使它不完美匹配硬件。
答案 7 :(得分:1)
在某种程度上,这里的答案没有提到一个特定的指针族 - 即指向成员的指针。那些肯定不是内存地址。
答案 8 :(得分:1)
除非指针由编译器优化,否则它们是存储内存地址的整数。它们的长度取决于编译代码的机器,但它们可以通常被视为整数。
事实上,您可以通过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));
}
所有变量和指针都被声明为volatile,因此编译器无法优化它们。还要注意我使用了减量,因为变量放在函数堆栈中。
输出结果为:
ints: 1337, 31337
doubles: 1.337000, 31.337000
0xFAFF465C = 1337
0xFAFF4658 = 31337
Difference: 4
0xFAFF4650 = 1.337000
0xFAFF4648 = 31.337000
Difference: 8
请注意,此代码可能无法在所有编译器上运行,特别是如果它们不以相同的顺序存储变量。然而,重要的是指针值实际上可以被读取和打印,并且一个的递减可以/将根据指针引用的变量的大小递减。
另请注意,&
和*
是参考的实际运算符(&#34;获取此变量的内存地址&#34;)和取消引用(&#34;获取此内存地址的内容&#34;)。
通过将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的示例。查看任何转换器。
答案 9 :(得分:0)
指针是内存地址,但您不应该假设它们反映了物理地址。当您看到0x00ffb500
这样的地址时,这些地址是MMU将转换为相应物理地址的逻辑地址。这是最可能的情况,因为虚拟内存是最扩展的内存管理系统,但可能存在直接管理物理地址的系统
答案 10 :(得分:0)
您提供的具体示例:
例如,当且仅当它们在物理内存中相邻时,两个元素* p1和* p2是否具有属性p2 = p1 + 1或p1 = p2 + 1?
会在没有平面地址空间的平台上失败,例如PIC。要访问PIC上的物理内存,您需要一个地址和一个库号,但后者可能来自外部信息,如特定的源文件。因此,对来自不同银行的指针进行算术会产生意想不到的结果。
答案 11 :(得分:0)
根据C ++ 14标准,[expr.unary.op] / 3:
一元
&
运算符的结果是指向其操作数的指针。操作数应为左值或限定ID。如果操作数是一个qualified-id,命名类型为m
的某个类C
的非静态成员T
,则结果的类型为“指向类C
成员的指针类型T
“,是指定C::m
的prvalue。否则,如果表达式的类型为T
,则结果具有“指向T的指针”,并且是prvalue ,它是指定对象的地址或指向指定函数的指针。 [注意:特别是,类型为“cvT
”的对象的地址是“指向cvT
”的指针,具有相同的cv资格。 - 后注]
所以这清楚而明确地说明了指向对象类型的指针(即T *
,其中T
不是函数类型)保存地址。
&#34;地址&#34;由[intro.memory] / 1:
定义C ++程序可用的内存由一个或多个连续字节序列组成。每个字节都有一个唯一的地址。
因此,地址可以是用于唯一标识特定内存字节的任何地址。
注意:在C ++标准术语中, memory 仅指正在使用的空间。它并不意味着物理内存,虚拟内存或类似内容。内存是一组不相交的分配。
重要的是要记住,虽然唯一标识内存中每个字节的一种可能方法是为物理或虚拟内存的每个字节分配一个唯一的整数,但这不是唯一可行的方法。
为避免编写非可移植代码,最好避免假设地址与整数相同。无论如何,指针的算术规则与整数的算术规则不同。同样,我们不会说5.0f
与1084227584
相同,即使它们在内存中具有相同的位表示(在IEEE754下)。