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

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

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

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

P.S.

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


当前回答

在这幅图中,

Pointer_p是一个位于0x12345的指针,它指向0x34567的变量variable_v。

其他回答

把指针看作地址是一种近似。像所有的近似值一样,它有时足够有用,但也不准确,这意味着依赖它会带来麻烦。

指针就像一个地址,它指出在哪里可以找到一个对象。这种类比的一个直接限制是,并非所有指针都实际包含地址。NULL是一个指针,它不是地址。指针变量的内容实际上可以是以下三种类型之一:

对象的地址,可以被解引用(如果p包含x的地址,则表达式*p与x的值相同); 一个空指针,null是一个例子; 无效内容,不指向对象(如果p不持有有效值,则*p可以做任何事情(“未定义行为”),导致程序崩溃是相当常见的可能性)。

此外,更准确的说法是,一个指针(如果有效且非空)包含一个地址:指针指出在哪里可以找到一个对象,但还有更多与之相关的信息。

In particular, a pointer has a type. On most platforms, the type of the pointer has no influence at runtime, but it has an influence that goes beyond the type at compile time. If p is a pointer to int (int *p;), then p + 1 points to an integer which is sizeof(int) bytes after p (assuming p + 1 is still a valid pointer). If q is a pointer to char that points to the same address as p (char *q = p;), then q + 1 is not the same address as p + 1. If you think of pointer as addresses, it is not very intuitive that the “next address” is different for different pointers to the same location.

It is possible in some environments to have multiple pointer values with different representations (different bit patterns in memory) that point to the same location in memory. You can think of these as different pointers holding the same address, or as different addresses for the same location — the metaphor isn't clear in this case. The == operator always tells you whether the two operands are pointing to the same location, so on these environments you can have p == q even though p and q have different bit patterns.

甚至在某些环境中,指针携带除地址以外的其他信息,例如类型或权限信息。作为一名程序员,你很容易在生活中不会遇到这些问题。

在某些环境中,不同类型的指针具有不同的表示形式。你可以把它想象成不同类型的地址有不同的表示。例如,一些体系结构有字节指针和字指针,或者对象指针和函数指针。

总而言之,只要记住这一点,将指针视为地址并不太糟糕

它只有有效的,非空的地址指针; 同一个位置可以有多个地址; 你不能对地址进行算术运算,地址上也没有顺序; 指针还携带类型信息。

反过来就麻烦多了。并不是所有看起来像地址的东西都可以是指针。在深层的某个地方,任何指针都表示为可以作为整数读取的位模式,并且您可以说这个整数是一个地址。但反过来说,不是每个整数都是指针。

首先有一些众所周知的限制;例如,在程序地址空间之外指定位置的整数不能是有效指针。未对齐的地址不能为需要对齐的数据类型创建有效指针;例如,在int需要4字节对齐的平台上,0x7654321不能是有效的int*值。

然而,它远远不止于此,因为当您将指针设置为整数时,您就会遇到很多麻烦。这个问题的很大一部分是优化编译器在微优化方面比大多数程序员预期的要好得多,因此他们对程序如何工作的思维模型是严重错误的。仅仅因为指针具有相同的地址并不意味着它们是等价的。例如,考虑下面的代码片段:

unsigned int x = 0;
unsigned short *p = (unsigned short*)&x;
p[0] = 1;
printf("%u = %u\n", x, *p);

您可能会期望,在sizeof(int)==4和sizeof(short)==2的普通机器上,这要么打印1 = 1?(little-endian)还是65536 = 1?(大端)。但在我的64位Linux PC上,GCC 4.4:

$ c99 -O2 -Wall a.c && ./a.out 
a.c: In function ‘main’:
a.c:6: warning: dereferencing pointer ‘p’ does break strict-aliasing rules
a.c:5: note: initialized from here
0 = 1?

在这个简单的例子中,GCC会提醒我们哪里出了问题——在更复杂的例子中,编译器可能不会注意到。由于p与&x的类型不同,改变p指向的对象不会影响&x指向的对象(除了一些定义良好的异常)。因此,编译器可以自由地将x的值保存在寄存器中,而不会在*p更改时更新该寄存器。程序解引用两个指向相同地址的指针,得到两个不同的值!

The moral of this example is that thinking of a (non-null valid) pointer as an address is fine, as long as you stay within the precise rules of the C language. The flip side of the coin is that the rules of the C language are intricate, and difficult to get an intuitive feeling for unless you know what happens under the hood. And what happens under the hood is that the tie between pointers and addresses is somewhat loose, both to support “exotic” processor architectures and to support optimizing compilers.

因此,可以将指针作为地址作为理解的第一步,但不要过于遵循这种直觉。

指针是一个保存内存地址的变量,而不是地址本身。但是,您可以解除对指针的引用-并访问内存位置。

例如:

int q = 10; /*say q is at address 0x10203040*/
int *p = &q; /*means let p contain the address of q, which is 0x10203040*/
*p = 20; /*set whatever is at the address pointed by "p" as 20*/

就是这样。就是这么简单。

一个演示我所说内容的程序,其输出如下:

http://ideone.com/rcSUsb

程序:

#include <stdio.h>

int main(int argc, char *argv[])
{
  /* POINTER AS AN ADDRESS */
  int q = 10;
  int *p = &q;

  printf("address of q is %p\n", (void *)&q);
  printf("p contains %p\n", (void *)p);

  p = NULL;
  printf("NULL p now contains %p\n", (void *)p);
  return 0;
}

在理解指针之前,我们需要先理解对象。对象是存在的实体,具有一个称为地址的位置说明符。指针与C语言中的其他变量一样,是一个类型为指针的变量,其内容被解释为支持以下操作的对象的地址。

+ : A variable of type integer (usually called offset) can be added to yield a new pointer
- : A variable of type integer (usually called offset) can be subtracted to yield a new pointer
  : A variable of type pointer can be subtracted to yield an integer (usually called offset)
* : De-referencing. Retrieve the value of the variable (called address) and map to the object the address refers to.
++: It's just `+= 1`
--: It's just `-= 1`

指针是根据它当前引用的对象类型进行分类的。唯一重要的信息是物体的大小。

任何对象都支持& (address of)操作,该操作将对象的位置说明符(地址)作为指针对象类型检索。这将减少围绕命名的混乱,因为调用&作为对象的操作而不是作为结果类型为对象类型的指针的指针是有意义的。

注意:在整个解释中,我省略了内存的概念。

A pointer, like any other variable in C, is fundamentally a collection of bits which may be represented by one or more concatenated unsigned char values (as with any other type of cariable, sizeof(some_variable) will indicate the number of unsigned char values). What makes a pointer different from other variables is that a C compiler will interpret the bits in a pointer as identifying, somehow, a place where a variable may be stored. In C, unlike some other languages, it is possible to request space for multiple variables, and then convert a pointer to any value in that set into a pointer to any other variable within that set.

Many compilers implement pointers by using their bits store actual machine addresses, but that is not the only possible implementation. An implementation could keep one array--not accessible to user code--listing the hardware address and allocated size of all of the memory objects (sets of variables) which a program was using, and have each pointer contain an index into an array along with an offset from that index. Such a design would allow a system to not only restrict code to only operating upon memory that it owned, but also ensure that a pointer to one memory item could not be accidentally converted into a pointer to another memory item (in a system that uses hardware addresses, if foo and bar are arrays of 10 items that are stored consecutively in memory, a pointer to the "eleventh" item of foo might instead point to the first item of bar, but in a system where each "pointer" is an object ID and an offset, the system could trap if code tried to index a pointer to foo beyond its allocated range). It would also be possible for such a system to eliminate memory-fragmentation problems, since the physical addresses associated with any pointers could be moved around.

Note that while pointers are somewhat abstract, they're not quite abstract enough to allow a fully-standards-compliant C compiler to implement a garbage collector. The C compiler specifies that every variable, including pointers, is represented as a sequence of unsigned char values. Given any variable, one can decompose it into a sequence of numbers and later convert that sequence of numbers back into a variable of the original type. Consequently, it would be possible for a program to calloc some storage (receiving a pointer to it), store something there, decompose the pointer into a series of bytes, display those on the screen, and then erase all reference to them. If the program then accepted some numbers from the keyboard, reconstituted those to a pointer, and then tried to read data from that pointer, and if user entered the same numbers that the program had earlier displayed, the program would be required to output the data that had been stored in the calloc'ed memory. Since there is no conceivable way the computer could know whether the user had made a copy of the numbers that were displayed, there would be no conceivable may the computer could know whether the aforementioned memory might ever be accessed in future.

地址用于标识一个固定大小的存储空间,通常为每个字节,作为一个整数。这被精确地称为字节地址,它也被ISO c使用。可以有一些其他方法来构造地址,例如为每一位。然而,只有字节地址是如此经常使用,我们通常省略“字节”。

从技术上讲,一个地址在C中从来都不是一个值,因为在(ISO) C中术语“值”的定义是:

对象的内容在解释为具有特定类型时的精确含义

(我强调了一下。)然而,在C语言中没有这样的“地址类型”。

指针不一样。指针是C语言中的一种类型。有几种不同的指针类型。它们不一定遵守相同的语言规则集,例如++对int*类型值和char*类型值的影响。

C语言中的值可以是指针类型。这叫做指针值。需要明确的是,指针值在C语言中不是指针。但是我们习惯把它们混在一起,因为在C语言中,它不太可能是模棱两可的:如果我们把表达式p称为“指针”,它只是一个指针值,而不是一个类型,因为C语言中的命名类型不是由表达式表示,而是由type-name或typedef-name表示。

其他一些事情是微妙的。作为C语言的使用者,首先要知道object是什么意思:

数据存储在执行环境中的区域,其中的内容可以表示 值

对象是表示特定类型的值的实体。指针是一种对象类型。因此,如果我们声明int* p;,则p表示“指针类型的对象”,或“指针对象”。

Note there is no "variable" normatively defined by the standard (in fact it is never being used as a noun by ISO C in normative text). However, informally, we call an object a variable, as some other language does. (But still not so exactly, e.g. in C++ a variable can be of reference type normatively, which is not an object.) The phrases "pointer object" or "pointer variable" are sometimes treated like "pointer value" as above, with a probable slight difference. (One more set of examples is "array".)

由于指针是一种类型,而地址在C语言中实际上是“无类型的”,因此指针值大致“包含”一个地址。指针类型的表达式可以产生一个地址,例如。

Iso c11 6.5.2.3

一元&操作符产生其操作数的地址。

请注意,这个措辞是由WG14/N1256引入的,即ISO C99:TC3。在C99中有

一元&操作符返回其操作数的地址。

它反映了委员会的观点:地址不是由一元操作符&返回的指针值。

尽管有上述措辞,但即使在标准上也存在一些混乱。

Iso c11 6.6

地址常量是一个空指针,一个指向左值的指针,该左值指定一个static对象 存储持续时间,或指向函数指示符的指针

Iso c++ 11 5.19

3.一个地址 常量表达式是指针类型的prvalue核心常量表达式,计算结果为对象的地址 具有静态存储持续时间的对象,转换为函数的地址、空指针值或prvalue核心 类型std::nullptr_t. ...的常量表达式

(最近的c++标准草案使用了另一种措辞,所以不存在这个问题。)

实际上,C中的“地址常量”和c++中的“地址常量表达式”都是指针类型的常量表达式(或者至少从c++ 11开始是“类指针”类型)。

内置的一元&运算符在C和c++中被称为“address-of”;类似地,std::addressof是在c++ 11中引入的。

这些命名可能会带来误解。结果表达式是指针类型的,所以它们被解释为:结果包含/产生一个地址,而不是一个地址。