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

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

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

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

P.S.

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


当前回答

指针只是另一个变量,用来保存内存位置的地址(通常是另一个变量的内存地址)。

其他回答

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

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

(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,具有安全的“胖指针”。

我相信你能找到更多。

你是对的,是理智的。通常,指针只是一个地址,因此您可以将其强制转换为整数并进行任何算术运算。

但有时指针只是地址的一部分。在一些体系结构上,指针被转换为一个增加了基数的地址或使用另一个CPU寄存器。

但是现在,在PC和ARM架构上,使用平面内存模型和原生编译的C语言,可以认为指针是指向一维可寻址RAM中某个位置的整数地址。

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

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

它说“因为它让那些不知道地址是什么的人感到困惑”——而且,这是真的:如果你知道地址是什么,你就不会困惑了。从理论上讲,指针是一个指向另一个变量的变量,实际上保存着一个地址,即它所指向的变量的地址。我不知道为什么要隐瞒这个事实,这又不是什么高深的科学。如果你理解了指针,你就离理解计算机的工作原理更近了一步。去吧!

以下是我过去是如何向一些困惑的人解释的: 指针有两个影响其行为的属性。它有一个值(在典型环境中)是一个内存地址,还有一个类型(告诉您它所指向的对象的类型和大小)。

例如,给定:

union {
    int i;
    char c;
} u;

你可以有三个不同的指针都指向同一个对象:

void *v = &u;
int *i = &u.i;
char *c = &u.c;

如果你比较这些指针的值,它们都是相等的:

v==i && i==c

但是,如果对每个指针加1,就会发现它们所指向的类型变得相关了。

i++;
c++;
// You can't perform arithmetic on a void pointer, so no v++
i != c

此时,变量i和c将具有不同的值,因为i++使i包含下一个可访问的整数的地址,而c++使c指向下一个可寻址的字符。通常,整数比字符占用更多的内存,所以在它们都加一之后,i的值将比c的值更大。