将C ++指针看作内存地址在多大程度上是可以接受的?

当你学习C ++时,或者至less当我通过C ++ Primer学到C ++时 ,指针被称为它们指向的元素的“内存地址”。 我想知道这在多大程度上是真的。

例如,两个元素*p1*p2是否具有属性p2 = p1 + 1p1 = p2 + 1 当且仅当它们在物理内存中是相邻的?

您应该将指针看作虚拟内存的地址:现代操作系统至less在物理内存和您看到的指针值之间放置一个抽象层。

至于你的最后陈述,你甚至不能在虚拟内存地址空间做这个假设。 指针运算只在连续内存块(如数组)内有效。 虽然允许(在C和C ++中)为指向数组(或标量)上的一个点的指针,但是引用这样一个指针的行为是不确定的。 在C和C ++语境下对物理内存中的邻接进行假设是毫无意义的。

一点也不。

C ++是计算机将执行的代码的抽象。 我们看到这个抽象在一些地方泄漏(例如需要存储的类成员引用),但是如果你编码到抽象而没有别的东西,那么总的来说你会更好。

指针是指针。 他们指向的东西。 他们会被实现为内存地址吗? 也许。 他们也可以被优化出来,或者(例如在指向成员的指针中),它们可能比简单的数字地址稍微复杂一些。

当你开始把指针看作是映射到内存中的地址的整数时,你会忘记,例如,它没有定义一个指针指向一个不存在的对象(你不能只是递增和递减指针,任何你喜欢的内存地址)。

正如许多答案已经提到的,他们不应该被认为是内存地址。 看看这些答案, 并在这里了解他们。 处理你的最后陈述

* p1和* p2具有属性p2 = p1 + 1或p1 = p2 + 1当且仅当它们在物理存储器中相邻时

只有在p1p2是相同types,或指向相同大小的types时才是正确的。

将指针视为内存地址绝对正确。 这正是我所使用过的所有编译器中都有的 – 对于许多不同的处理器体系结构,由许多不同的编译器生产者制造。

然而,编译器会做一些有趣的魔术,以帮助你,[至less在所有现代主stream处理器中]正常的内存地址都是字节地址,而你的指针指向的对象可能不是一个字节。 所以如果我们有T* ptr;ptr++会做((char*)ptr) + sizeof(T); 或者ptr + n((char*)ptr) + n*sizeof(T) 。 这也意味着你的p1 == p2 + 1需要p1p2是相同的typesT ,因为+1实际上是+sizeof(T)*1

上面的“指针是内存地址”有一个例外,那就是成员函数指针。 它们是“特殊”的,现在请忽略它们是如何实现的,足以说它们不是“只是内存地址”。

操作系统为您的程序提供了物理机的抽象(即,您的程序在虚拟机中运行)。 因此,您的程序无法访问您的计算机的任何物理资源,无论是CPU时间,内存等。 它只需要向操作系统索取这些资源。

在内存的情况下,您的程序在由操作系统定义的虚拟地址空间中工作。 这个地址空间有多个区域,比如堆栈,堆,代码等。指针的值代表这个虚拟地址空间中的地址。 实际上,连续地址的2个指针将指向这个地址空间中的连续位置。

但是,这个地址空间被操作系统分割成页面和段,根据需要将它们从内存中交换出来,所以你的指针可能指向或不指向连续的物理内存位置,并且在运行时不可能指出真实与否。 这也取决于操作系统用于分页和分段的策略。

底线是指针是内存地址。 但是,它们是虚拟内存空间中的地址,决定如何将其映射到物理内存空间取决于操作系统。

就您的程序而言,这不是一个问题。 这种抽象的一个原因是让程序相信他们是机器的唯一用户。 想象一下,如果您在编写程序时需要考虑由其他进程分配的内存,您将不得不经历的噩梦 – 您甚至不知道哪些进程将与您的进程同时运行。 而且,这是一个强制执行安全性的好方法:由于你的进程在两个不同的(虚拟)存储空间中运行,你的进程不能(至less不能)恶意地访问另一个进程的内存空间。

像其他variables一样,指针存储的数据可以是存储其他数据的内存地址。

所以,指针是一个有地址的variables,可以保存一个地址。

请注意, 指针总是保存一个地址是不必要的 。 它可以保存一个非地址ID /句柄等。因此,说指针作为地址不是一个明智的事情。


关于你的第二个问题:

指针运算对于连续的内存块是有效的。 如果p2 = p1 + 1并且两个指针都是相同的types,那么p1p2指向一个连续的内存块。 所以,地址p1p2是彼此相邻的。

我认为这个答案有正确的想法,但术语不好。 C指针提供的是抽象的完全相反。

抽象提供了一个相对容易理解和推理的心理模型,即使硬件更复杂,难以理解或难以推理。

C指针是相反的。 即使真正的硬件通常比较简单和容易推理,它们也会考虑硬件的可能的困难。 它们限制了你的推理,使得最复杂的硬件最复杂的部分可以被允许,不pipe手头的硬件有多简单。

C ++指针添加了一个C不包括的东西。 它允许比较相同types的所有指针,即使它们不在同一个数组中。 这就允许多一点智力模型,即使它不能完美匹配硬件。

不知何故,这里的答案没有提到一个特定的指针家族 – 也就是指向成员的指针。 这些肯定不是内存地址。

除非指针被编译器优化,否则它们是存储内存地址的整数。 它们的长度取决于正在编译代码的机器,但通常可以将它们视为整数。

实际上,你可以通过printf()打印存储在其上的实际数字来检查。

但是,请注意,该type *指针递增/递减操作是由sizeof(type) 。 用自己的代码查看(在Repl.it上在线testing):

 #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)); } 

所有的variables和指针都被声明为volatile,所以编译器不会优化它们。 另外请注意,我使用了递减,因为variables被放置在函数堆栈中。

输出是:

 ints: 1337, 31337 doubles: 1.337000, 31.337000 0xFAFF465C = 1337 0xFAFF4658 = 31337 Difference: 4 0xFAFF4650 = 1.337000 0xFAFF4648 = 31.337000 Difference: 8 

请注意,此代码可能无法在所有编译器上使用,特别是如果它们不以相同的顺序存储variables的话。 但是,重要的是,指针值可以被实际读取和打印,并且可以根据指针所引用的variables的大小递减一个指针值。

还要注意, &*是实际的操作符(“获取这个variables的内存地址”) 和解引用 (“获取这个内存地址的内容”)。

这也可以用于很酷的技巧,比如获取浮点数的IEEE 754二进制值,通过将float*int*

 #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的示例。; 检查任何转换器。

指针是内存地址,但不应该假设它们反映了物理地址。 当你看到像0x00ffb500这样的地址时,这些逻辑地址将被MMU转换成相应的物理地址。 这是最可能的情况,因为虚拟内存是最为扩展的内存pipe理系统,但可能有系统直接pipe理物理地址

你给的具体例子是:

例如,两个元素* p1和* p2是否具有属性p2 = p1 + 1或p1 = p2 + 1当且仅当它们在物理内存中是相邻的?

在没有平面地址空间的平台(如PIC)上会失败。 要访问PIC上的物理内存,您需要一个地址和一个银行编号,但后者可能来自外部信息,如特定的源文件。 所以,对来自不同银行的指针进行算术会产生意想不到的结果。

根据C ++ 14标准,[expr.unary.op] / 3:

一元&运算符的结果是一个指向其操作数的指针。 操作数应该是一个左值或者是一个合格的id。 如果操作数是一个qualified-id,它命名某类C的types为T的非静态成员m ,则结果的types为“指向T类的C类成员”,并且是一个指定C::m 。 否则,如果expression式的types是T ,则结果具有types“指向T的指针”,并且是指定对象的地址或指向函数的指针的prvalue。 [注意:特别是, “cv T ”types的对象的地址是“cv T指针” ,具有相同的cv限定。 – 注意]

因此,这清楚而明确地指出对象types的指针(即T * ,其中T不是函数types)保存地址。


“地址”由[intro.memory] ​​/ 1定义:

可用于C ++程序的内存由一个或多个连续字节序列组成。 每个字节都有一个唯一的地址。

所以地址可以是任何用来唯一标识内存的特定字节的东西。

注意:在C ++标准术语中, 内存仅指正在使用的空间。 这并不意味着物理内存或虚拟内存,或者类似的东西。 内存是一个不相交的分配集合。


重要的是要记住,虽然唯一识别内存中每个字节的一种可能的方式是为物理或虚拟内存的每个字节分配一个唯一的整数,但这不是唯一可能的方式。

为避免编写不可移植的代码,最好避免假定地址与整数相同。 算术指针的规则不同于整数算术规则。 同样,我们也不会说5.0f1084227584是一样的,尽pipe它们在内存中具有相同的位表示(在IEEE754下)。