在关于C的一个有信誉的来源中,在讨论&操作符后给出了以下信息:

... 有点不幸的是,术语[地址的]仍然存在,因为它混淆了那些不知道地址是关于什么的人,并误导了那些知道地址的人:将指针视为地址通常会导致悲伤……

我读过的其他材料(来自同样有名望的来源,我想说)总是毫不掩饰地将指针和&操作符作为内存地址。我很愿意继续寻找事情的真相,但当有信誉的消息来源不同意时,这有点困难。

现在我有点困惑了——如果指针不是内存地址,那么它到底是什么?

P.S.

作者后来说:……不过,我将继续使用“地址”这个术语,因为发明一个不同的(术语)会更糟糕。


当前回答

C标准没有在内部定义指针是什么以及它在内部是如何工作的。这样做的目的是为了不限制平台的数量,在这些平台上,C可以作为编译或解释语言实现。

指针值可以是某种ID或句柄,也可以是几个ID的组合(对x86段和偏移量说你好),不一定是真正的内存地址。这个ID可以是任何东西,甚至是固定大小的文本字符串。非地址表示可能对C解释器特别有用。

其他回答

简单地说,指针实际上是分割机制的偏移部分,分割后转换为线性地址,分页后转换为物理地址。物理地址实际上是从ram中寻址的。

       Selector  +--------------+         +-----------+
      ---------->|              |         |           |
                 | Segmentation | ------->|  Paging   |
        Offset   |  Mechanism   |         | Mechanism |
      ---------->|              |         |           |
                 +--------------+         +-----------+
        Virtual                   Linear                Physical

简短的总结 (我也会把它放在顶部):

将指针视为地址通常是一个很好的学习工具,并且通常是普通数据类型指针的实际实现。

(1)但是在许多,也许是大多数编译器上,指向函数的指针不是地址,而是比地址大(通常是2倍,有时更多),或者实际上是指向内存中结构体的指针,而不是包含函数地址和常量池之类的东西。

(2)指向数据成员的指针和指向方法的指针通常更奇怪。

(3)遗留的x86代码的FAR和NEAR指针问题

(4)几个例子,最著名的是IBM AS/400,具有安全的“胖指针”。

我相信你能找到更多。

细节:

UMMPPHHH ! !到目前为止,许多答案都是相当典型的“程序员菜鸟”答案——但不是编译器菜鸟或硬件菜鸟。因为我假装是一个硬件弱项,并且经常与编译器弱项一起工作,让我抛出我的意见:

在许多(可能是大多数)C编译器中,指向类型为T的数据的指针实际上是T的地址。

很好。

但是,即使在许多这样的编译器上,某些指针也不是地址。你可以通过sizeof(ThePointer)来判断。

For example, pointers to functions are sometimes quite a lot bigger than ordinary addresses. Or, they may involve a level of indirection. This article provides one description, involving the Intel Itanium processor, but I have seen others. Typically, to call a function you must know not only the address of the function code, but also the address of the function's constant pool - a region of memory from which constants are loaded with a single load instruction, rather than the compiler having to generate a 64 bit constant out of several Load Immediate and Shift and OR instructions. So, rather than a single 64 bit address, you need 2 64 bit addresses. Some ABIs (Application Binary Interfaces) move this around as 128 bits, whereas others use a level of indirection, with the function pointer actually being the address of a function descriptor that contains the 2 actual addresses just mentioned. Which is better? Depends on your point of view: performance, code size, and some compatibility issues - often code assumes that a pointer can be cast to a long or a long long, but may also assume that the long long is exactly 64 bits. Such code may not be standards compliant, but nevertheless customers may want it to work.

我们中的许多人都对旧的英特尔x86分段架构有痛苦的记忆,有NEAR指针和FAR指针。值得庆幸的是,这些几乎已经灭绝了,所以只有一个快速的总结:在16位实模式中,实际的线性地址是

LinearAddress = SegmentRegister[SegNum].base << 4 + Offset

而在保护模式下,它可能是

LinearAddress = SegmentRegister[SegNum].base + offset

with the resulting address being checked against a limit set in the segment. Some programs used not really standard C/C++ FAR and NEAR pointer declarations, but many just said *T --- but there were compiler and linker switches so, for example, code pointers might be near pointers, just a 32 bit offset against whatever is in the CS (Code Segment) register, while the data pointers might be FAR pointers, specifying both a 16 bit segment number and a 32 bit offset for a 48 bit value. Now, both of these quantities are certainly related to the address, but since they aren't the same size, which of them is the address? Moreover, the segments also carried permissions - read-only, read-write, executable - in addition to stuff related to the actual address.

A more interesting example, IMHO, is (or, perhaps, was) the IBM AS/400 family. This computer was one of the first to implement an OS in C++. Pointers on this machime were typically 2X the actual address size - e.g. as this presentation says, 128 bit pointers, but the actual addresses were 48-64 bits, and, again, some extra info, what is called a capability, that provided permissions such as read, write, as well as a limit to prevent buffer overflow. Yes: you can do this compatibly with C/C++ -- and if this were ubiquitous, the Chinese PLA and slavic mafia would not be hacking into so many Western computer systems. But historically most C/C++ programming has neglected security for performance. Most interestingly, the AS400 family allowed the operating system to create secure pointers, that could be given to unprivileged code, but which the unprivileged code could not forge or tamper with. Again, security, and while standards compliant, much sloppy non-standards compliant C/C++ code will not work in such a secure system. Again, there are official standards, and there are de-facto standards.

现在,我将放下我的安全演讲,并提到指针(各种类型)通常不是真正地址的其他一些方式:指向数据成员的指针,指向成员函数方法的指针,以及它们的静态版本比普通地址更大。正如这篇文章所说:

有许多方法可以解决这个问题[与单继承和多继承以及虚拟继承有关的问题]。Visual Studio编译器决定如何处理它:指向多重继承类的成员函数的指针实际上是一个结构。” 他们接着说:“强制转换函数指针可以改变它的大小!”

从我对安全性的评论中,您可能会猜到,我曾经参与过C/ c++硬件/软件项目,在这些项目中,指针更像是一种能力,而不是原始地址。

我还可以继续,但我希望你们能明白。

简短的总结 (我也会把它放在顶部):

(0)将指针视为地址通常是一个很好的学习工具,并且通常是普通数据类型指针的实际实现。

(1)但是在许多,也许是大多数编译器上,指向函数的指针不是地址,而是比地址大(通常是2X,有时更多),或者实际上是指向内存中结构体的指针,而不是包含函数地址和常量池之类的东西。

(2)指向数据成员的指针和指向方法的指针通常更奇怪。

(3)遗留的x86代码的FAR和NEAR指针问题

(4)几个例子,最著名的是IBM AS/400,具有安全的“胖指针”。

我相信你能找到更多。

马克·贝西(Mark Bessey)已经说过了,但这一点需要再次强调,直到人们理解为止。

指针与变量的关系比与文字3的关系更大。

指针是一个值(地址)和类型(带有其他属性,如只读)的元组。类型(以及附加参数(如果有的话)可以进一步定义或限制上下文;如。__far ptr, __near ptr:地址的上下文是什么:堆栈,堆,线性地址,某处的偏移量,物理内存或其他。

正是type的属性使得指针算术与整数算术略有不同。

指针不是变量的反例太多了,不容忽视

fopen返回FILE指针。(变量在哪里) 堆栈指针或帧指针通常是不可寻址的寄存器 *(int *)0x1231330 = 13;——将任意整数值转换为pointer_of_integer类型,并在不引入变量的情况下写入/读取整数值

在c程序的生命周期中,会有许多其他没有地址的临时指针实例——因此它们不是变量,而是带有编译时相关类型的表达式/值。

很难确切地说出这些书的作者到底是什么意思。指针是否包含地址取决于如何定义地址和如何定义指针。

从所有的回答来看,有些人认为(1)地址必须是整数,(2)指针不需要是虚的,因为规范中没有这么说。根据这些假设,显然指针不一定包含地址。

然而,我们看到,虽然(2)可能是真的,(1)可能不一定是真的。根据@ corn秸秆的答案,&被称为操作符的地址,这是怎么回事?这是否意味着规范的作者希望指针包含地址?

我们可以说,指针包含一个地址,但地址不一定是整数?也许吧。

我认为所有这些都是废话连篇的迂腐语义学。实际上,这是毫无价值的。你能想到一个编译器以这样的方式生成代码,指针的值不是一个地址吗?如果有,是什么?我也是这么想的……

我认为这本书的作者(第一个摘录声称指针不一定只是地址)可能指的是指针自带固有类型信息这一事实。

例如,

 int x;
 int* y = &x;
 char* z = &x;

y和z都是指针,但y+1和z+1是不同的。如果它们是内存地址,难道这些表达式不会给你相同的值吗?

在这里,把指针当作地址的想法通常会导致悲伤。之所以会出现bug,是因为人们将指针视为地址,而这通常会导致不幸。

55555可能不是指针,尽管它可能是一个地址,但(int*)55555是一个指针。55555+1 = 55556,但是(int*)55555+1是55559(在sizeof(int)方面的+/-差异)。

指针只是另一个变量,它通常包含另一个变量的内存地址。指针是一个变量,它也有一个内存地址。