216

在有关 C 的著名来源中,在讨论运算符后给出了以下信息&

...有点不幸的是,术语[address of]仍然存在,因为它使那些不知道地址是什么的人感到困惑,并误导了那些知道地址的人:将指针视为地址通常会导致悲伤。 .

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

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

附言

作者后来说:……我会继续使用“地址”这个词,因为发明一个不同的[词]会更糟。

4

25 回答 25

155

C 标准没有定义指针在内部是什么以及它在内部如何工作。这是故意的,以免限制平台的数量,其中 C 可以实现为编译或解释语言。

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

于 2013-03-01T06:02:16.473 回答
63

我不确定您的来源,但您描述的语言类型来自 C 标准:

6.5.3.2 地址和间接运算符
[...]
3.一元 & 运算符产生其操作数的地址。[...]

所以...是的,指针指向内存地址。至少这就是 C 标准所暗示的意思。

说得更清楚一点,指针是一个保存某个地址的变量。使用一元运算符返回对象的地址(可以存储在指针中)。&

我可以将地址“42 Wallaby Way, Sydney”存储在一个变量中(该变量将是某种“指针”,但由于这不是内存地址,因此我们不能正确地称之为“指针”)。您的计算机有其内存桶的地址。指针存储地址的值(即指针存储值“42 Wallaby Way, Sydney”,它是一个地址)。

编辑:我想扩展 Alexey Frunze 的评论。

究竟什么是指针?让我们看一下C标准:

6.2.5 类型
[...]
20. [...]指针类型可以派生自函数类型或对象类型,称为
引用类型。指针类型描述了一个对象,其值提供对被引用类型实体的引用。从引用类型 T 派生的指针类型有时称为“指向 T 的指针”。从引用类型构造指针类型称为“指针类型派生”。指针类型是完整的对象类型。

本质上,指针存储一个值,该值提供对某个对象或函数的引用。有点儿。指针旨在存储提供对某个对象或函数的引用的值,但情况并非总是如此:

6.3.2.3 指针
[...]
5. 整数可以转换为任何指针类型。除非前面指定,结果是实现定义的,可能没有正确对齐,可能不指向引用类型的实体,并且可能是陷阱表示。

上面的引用说我们可以把一个整数变成一个指针。如果我们这样做(也就是说,如果我们将整数值填充到指针中,而不是对对象或函数的特定引用),那么指针“可能不指向引用类型的实体”(即它可能不提供对对象或函数的引用)。它可能会为我们提供其他东西。这是您可能在指针中粘贴某种句柄或 ID 的地方(即,指针不指向对象;它存储了一个表示某物的值,但该值可能不是地址)。

所以是的,正如 Alexey Frunze 所说,指针可能没有将地址存储到对象或函数。指针可能会存储某种“句柄”或 ID,您可以通过为指针分配一些任意整数值来做到这一点。这个句柄或 ID 代表什么取决于系统/环境/上下文。只要您的系统/实现可以理解该值,您就处于良好状态(但这取决于特定值和特定系统/实现)。

通常,指针存储对象或函数的地址。如果它不存储实际地址(到对象或函数),则结果是实现定义的(这意味着究竟发生了什么以及指针现在表示的内容取决于您的系统和实现,因此它可能是一个句柄或 ID一个特定的系统,但在另一个系统上使用相同的代码/值可能会使您的程序崩溃)。

结果比我想象的要长...

于 2013-03-01T05:57:42.250 回答
37

指针与变量

在这幅图片中,

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

于 2013-03-01T09:31:55.230 回答
37

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

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

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

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

特别是,指针具有类型。在大多数平台上,指针的类型在运行时没有影响,但它的影响超出了编译时的类型。Ifp是指向int( int *p;) 的指针,则p + 1指向后面的sizeof(int)字节整数p(假设p + 1仍然是有效指针)。如果q是指向与( )char相同地址的指针,则与 不同地址。如果将指针视为地址,那么指向同一位置的不同指针的“下一个地址”不同,这并不是很直观。pchar *q = p;q + 1p + 1

在某些环境中,可能有多个指针值具有不同的表示形式(内存中的不同位模式),它们指向内存中的同一位置。您可以将它们视为持有相同地址的不同指针,或者视为相同位置的不同地址——在这种情况下,比喻不清楚。运算符总是告诉你两个==操作数是否指向同一个位置,所以在这些环境中你可以p == qp不同q的位模式。

甚至在某些环境中,指针携带地址之外的其他信息,例如类型或权限信息。作为程序员,你可以轻松地度过你的生活,而不会遇到这些。

在某些环境中,不同类型的指针具有不同的表示形式。您可以将其视为具有不同表示形式的不同类型的地址。例如,一些架构有字节指针和字指针,或者对象指针和函数指针。

总而言之,只要您牢记,将指针视为地址并不算太糟糕

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

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

首先有一些众所周知的限制。例如,指定程序地址空间之外的位置的整数不能是有效指针。未对齐的地址不会为需要对齐的数据类型生成有效的指针;例如,在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)==4andsizeof(short)==2会打印1 = 1?(little-endian) 或65536 = 1?(big-endian)。但是在我的带有 GCC 4.4 的 64 位 Linux PC 上:

$ 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。该程序取消引用两个指向同一地址的指针并获得两个不同的值!

这个例子的寓意是,只要您遵守 C 语言的精确规则,就可以将(非空有效)指针视为地址。硬币的另一面是 C 语言的规则错综复杂,除非你知道幕后发生了什么,否则很难直观地感受到。底层发生的事情是指针和地址之间的联系有点松散,既支持“外来”处理器架构,也支持优化编译器。

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

于 2013-03-01T20:09:15.563 回答
19

指针是保持内存地址的变量,而不是地址本身。但是,您可以取消引用指针 - 并访问内存位置。

例如:

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;
}
于 2013-03-01T05:52:59.627 回答
16

很难准确地说出这些书的作者究竟是什么意思。指针是否包含地址取决于您如何定义地址以及如何定义指针。

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

然而,我们看到虽然 (2) 可能为真,但 (1) 可能不一定为真。根据@CornStalks 的回答,& 被称为运算符的地址这一事实如何解释?这是否意味着规范的作者打算让一个指针包含一个地址?

那么我们可以说,指针包含一个地址,但地址不必是整数吗?也许。

我认为所有这些都是胡言乱语的迂腐语义谈话。实际上完全没有价值。你能想到一个编译器生成代码的方式是指针的值不是地址吗?如果是这样,是什么?我也那么认为...

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

例如,

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

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

在这里,将指针视为地址通常会导致悲伤。编写错误是因为人们将指针视为地址,而这通常会导致悲伤

55555 可能不是指针,虽然它可能是地址,但 (int*)55555 是指针。55555+1 = 55556,但 (int*)55555+1 是 55559(就 sizeof(int) 而言的+/-差异)。

于 2013-03-01T07:04:12.373 回答
15

好吧,指针是表示内存位置的抽象。请注意,这句话并没有说将指针视为内存地址是错误的,它只是说它“通常会导致悲伤”。换句话说,它会导致你产生错误的期望。

最可能的悲痛来源当然是指针算术,这实际上是 C 的优势之一。如果指针是地址,您会期望指针算术是地址算术;但事实并非如此。例如,将 10 添加到地址应该会给您一个大 10 个寻址单位的地址;但是向指针添加 10 会将其增加 10 倍于它所指向的对象类型的大小(甚至不是实际大小,而是四舍五入到对齐边界)。在int *具有 32 位整数的普通架构上,向其添加 10 会将其增加 40 个寻址单元(字节)。经验丰富的 C 程序员都知道这一点,并将其用于各种好的用途,但您的作者显然不喜欢草率的隐喻。

还有一个额外的问题是指针的内容如何表示内存位置:正如许多答案所解释的那样,地址并不总是一个 int (或 long )。在某些架构中,地址是“段”加上偏移量。一个指针甚至可能只包含当前段的偏移量(“近”指针),它本身并不是唯一的内存地址。并且指针内容可能仅与硬件理解的内存地址有间接关系。但是引用的引用的作者甚至没有提到表示,所以我认为他们想到的是概念对等,而不是表示。

于 2013-03-01T20:30:44.860 回答
12

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

例如,给定:

union {
    int i;
    char c;
} u;

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

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

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

v==i && i==c

但是,如果您增加每个指针,您会看到它们指向的类型变得相关。

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

此时变量ic将具有不同的值,因为i++导致i包含下一个可访问整数的地址,并c++导致c指向下一个可寻址字符。通常,整数比字符占用更多的内存,因此i最终会得到比c它们都增加后更大的值。

于 2013-03-02T19:15:34.603 回答
9

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

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

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

于 2013-03-01T05:54:22.480 回答
8

Mark Bessey 已经说过了,但这需要重新强调,直到理解为止。

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

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

正是类型的属性使得指针算术与整数算术有点不同。

指针不是变量的反例太多,无法忽略

  • fopen 返回一个 FILE 指针。(变量在哪里)

  • 堆栈指针或帧指针通常是不可寻址的寄存器

    *(int *)0x1231330 = 13;-- 将任意整数值转换为 pointer_of_integer 类型并写入/读取整数而无需引入变量

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

于 2013-03-03T21:14:18.703 回答
7

与 C 中的任何其他变量一样,指针基本上是位的集合,可以由一个或多个连接unsigned char值表示(与任何其他类型的 cariable 一样,sizeof(some_variable)将指示unsigned char值的数量)。指针与其他变量的不同之处在于,C 编译器会将指针中的位解释为以某种方式标识可能存储变量的位置。在 C 中,与其他一些语言不同,可以为多个变量请求空间,然后将指向该集合中任何值的指针转换为指向该集合中任何其他变量的指针。

许多编译器通过使用它们的位存储实际机器地址来实现指针,但这不是唯一可能的实现。一个实现可以保留一个数组——用户代码无法访问——列出程序正在使用的所有内存对象(变量集)的硬件地址和分配大小,并让每个指针包含一个数组索引与该索引的偏移量。这样的设计将允许系统不仅将代码限制为仅在其拥有的内存上操作,而且还确保指向一个内存项的指针不会意外转换为指向另一个内存项的指针(在使用硬件的系统中)地址,如果foobar是连续存储在内存中的 10 个项目的数组,则指向第 10 个项目的指针foo可能会指向 的第一项bar,但在每个“指针”都是对象 ID 和偏移量的系统中,如果代码尝试将指针索引到foo超出其分配范围的位置,系统可能会陷入陷阱)。这样的系统也有可能消除内存碎片问题,因为与任何指针相关的物理地址都可以移动。

请注意,虽然指针有些抽象,但它们还不够抽象,无法让完全符合标准的 C 编译器实现垃圾收集器。C 编译器指定每个变量(包括指针)都表示为unsigned char值序列。给定任何变量,可以将其分解为数字序列,然后将该数字序列转换回原始类型的变量。因此,一个程序可以calloc一些存储(接收指向它的指针),在那里存储一些东西,将指针分解为一系列字节,将它们显示在屏幕上,然后擦除对它们的所有引用。如果程序随后从键盘接受了一些数字,将这些数字重构为指针,然后尝试从该指针读取数据,并且如果用户输入的数字与程序之前显示的相同,则程序将被要求输出数据已存储在calloc'ed 内存中。由于无法想象计算机可以知道用户是否复制了所显示的数字,因此无法想象计算机可以知道将来是否会访问上述存储器。

于 2013-03-02T21:22:52.270 回答
6

指针是 C/C++ 中本机可用的变量类型,包含内存地址。像任何其他变量一样,它有自己的地址并占用内存(数量取决于平台)。

由于混淆,您将看到的一个问题是试图通过简单地按值传递指针来更改函数内的所指对象。这将在函数范围内制作指针的副本,并且对该新指针“指向”的位置的任何更改都不会更改调用该函数的范围内指针的所指对象。为了修改函数中的实际指针,通常会将指针传递给指针。

于 2013-03-01T05:50:54.500 回答
6

简要总结 (我也会放在顶部):

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

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

(2) 指向数据成员的指针和指向方法的指针往往更加陌生。

(3) 带有 FAR 和 NEAR 指针问题的旧 x86 代码

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

我相信你能找到更多。

细节:

嗯!!!!到目前为止,许多答案都是相当典型的“程序员小问题”答案——但不是编译器小问题或硬件小问题。由于我假装自己是一个硬件小精灵,并且经常与编译器小精灵一起工作,所以让我投入两分钱:

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

美好的。

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

例如,指向函数的指针有时比普通地址大很多。或者,它们可能涉及一定程度的间接性。 本文提供了一种描述,涉及英特尔安腾处理器,但我见过其他的。通常,要调用函数,您不仅必须知道函数代码的地址,还必须知道函数常量池的地址 - 一个使用单个加载指令加载常量的内存区域,而不是编译器必须生成几个加载立即数和移位和或指令中的一个 64 位常量。因此,您需要 2 个 64 位地址,而不是单个 64 位地址。一些 ABI(应用程序二进制接口)将其移动为 128 位,而另一些则使用间接级别,函数指针实际上是包含刚刚提到的 2 个实际地址的函数描述符的地址。哪个更好?取决于您的观点:性能、代码大小、和一些兼容性问题 - 通常代码假定指针可以转换为 long 或 long long,但也可能假定 long long 正好是 64 位。这样的代码可能不符合标准,但客户可能希望它工作。

我们中的许多人都对旧的 Intel x86 分段架构有着痛苦的回忆,包括 NEAR POINTERs 和 FAR POINTERS。谢天谢地,这些现在几乎绝迹了,所以只是一个简单的总结:在 16 位实模式下,实际的线性地址是

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

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

LinearAddress = SegmentRegister[SegNum].base + offset

根据段中设置的限制检查结果地址。有些程序使用的不是真正标准的 C/C++ FAR 和 NEAR 指针声明,但许多程序只是说*T--- 但是有编译器和链接器开关,因此,例如,代码指针可能是近指针,只是相对于其中的任何内容的 32 位偏移量CS(代码段)寄存器,而数据指针可能是 FAR 指针,为 48 位值指定 16 位段号和 32 位偏移量。现在,这两个量肯定都与地址有关,但是由于它们的大小不同,所以它们中的哪一个是地址?此外,除了与实际地址相关的内容之外,这些段还带有权限——只读、读写、可执行。

一个更有趣的例子,恕我直言,是(或者,也许是)IBM AS/400 系列。这台计算机是最早使用 C++ 实现操作系统的计算机之一。此机器上的指针通常是实际地址大小的 2 倍 - 例如,本演示文稿说是 128 位指针,但实际地址是 48-64 位,还有一些额外的信息,即所谓的能力,它提供诸如读、写之类的权限,以及防止缓冲区溢出的限制。是的:你可以用 C/C++ 兼容地做到这一点——如果这无处不在,中国解放军和斯拉夫黑手党就不会侵入这么多西方计算机系统。但从历史上看,大多数 C/C++ 编程都忽略了性能安全性。最有趣的是,AS400 系列允许操作系统创建安全指针,这些指针可以提供给非特权代码,但非特权代码无法伪造或篡改。同样,安全性,虽然符合标准,但很多草率的不符合标准的 C/C++ 代码将无法在这样一个安全的系统中工作。再次,有官方标准,

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

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

正如您可能从我对(不)安全性的夸夸其谈中猜到的那样,我参与了 C/C++ 硬件/软件项目,在这些项目中,指针被视为一种能力而不是原始地址。

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

简要总结 (我也会放在顶部):

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

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

(2) 指向数据成员的指针和指向方法的指针往往更加陌生。

(3) 带有 FAR 和 NEAR 指针问题的旧 x86 代码

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

我相信你能找到更多。

于 2013-03-15T01:40:25.267 回答
4

你可以这样看。指针是表示可寻址内存空间中的地址的值。

于 2013-03-01T06:04:21.663 回答
4

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

于 2013-03-01T05:51:47.413 回答
3

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

于 2013-03-01T06:16:43.927 回答
3

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

从技术上讲,地址永远不是 C 中的值,因为 (ISO) C 中术语“值”的定义是:

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

(由我强调。)但是,C 中没有这样的“地址类型”。

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

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

其他一些事情是微妙的。作为 C 用户,首先应该知道什么object意思:

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

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

请注意,标准没有规范地定义“变量”(事实上,ISO C 在规范文本中从未将其用作名词)。然而,非正式地,我们将对象称为变量,就像其他一些语言一样。(但仍然不是那么准确,例如在 C++ 中,变量通常可以是引用类型,这不是对象。)短语“指针对象”或“指针变量”有时被视为如上所述的“指针值”,带有可能略有不同。(另外一组示例是“数组”。)

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

ISO C11 6.5.2.3

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

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

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

它反映了委员会的意见:地址不是一元运算符返回的指针值&

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

ISO C11 6.6

9地址常量是空指针、指向指定静态存储持续时间对象的左值指针或指向函数指示符的指针

ISO C++11 5.19

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

(最近的 C++ 标准草案使用了另一种措辞,所以没有这个问题。)

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

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

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

于 2014-11-09T07:42:49.707 回答
3

在了解指针之前,我们需要了解对象。对象是存在并具有称为地址的位置说明符的实体。指针只是一个变量,就像任何其他变量一样,Cpointer的内容被解释为支持以下操作的对象的地址。

+ : 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`

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

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

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

于 2013-03-01T20:39:21.077 回答
3

AC 指针与内存地址非常相似,但抽象掉了与机器相关的细节,以及一些低级指令集中没有的特性。

例如,C 指针的类型相对丰富。如果您通过结构数组递增指针,它会很好地从一个结构跳转到另一个结构。

指针受转换规则的约束并提供编译时类型检查。

有一个特殊的“空指针”值,它在源代码级别是可移植的,但其表示可能不同。如果将值为零的整数常量分配给指针,则该指针将采用空指针值。如果您以这种方式初始化指针,则同上。

指针可以用作布尔变量:如果它不是 null 则测试 true,如果它为 null 则测试 false。

在机器语言中,如果空指针是一个有趣的地址,例如 0xFFFFFFFF,那么您可能必须对该值进行显式测试。C 对你隐瞒。即使空指针是 0xFFFFFFFF,您也可以使用if (ptr != 0) { /* not null! */}.

使用破坏类型系统的指针会导致未定义的行为,而机器语言中的类似代码可能会被很好地定义。汇编器将汇编您编写的指令,但 C 编译器将基于您没有做错任何事情的假设进行优化。如果一个float *p指针指向一个long n变量并被*p = 0.0执行,编译器不需要处理这个。随后的使用n将不需要读取浮点值的位模式,但也许,这将是一个优化的访问,它基于n尚未触及的“严格别名”假设!也就是说,假设程序表现良好,因此p不应该指向n.

在 C 中,指向代码的指针和指向数据的指针是不同的,但在许多体系结构中,地址是相同的。可以开发具有“胖”指针的 C 编译器,即使目标体系结构没有。胖指针意味着指针不仅仅是机器地址,还包含其他信息,例如有关被指向对象大小的信息,用于边界检查。可移植编写的程序将很容易移植到此类编译器。

所以你可以看到,机器地址和C指针之间存在很多语义差异。

于 2013-03-02T08:50:46.793 回答
2

仔细想想,我认为这是一个语义问题。我不认为作者是对的,因为 C 标准将指针称为持有指向被引用对象的地址,正如其他人在这里已经提到的那样。但是,地址!=内存地址。根据 C 标准,地址实际上可以是任何东西,尽管它最终会导致内存地址,指针本身可以是 id、偏移量 + 选择器(x86),只要它可以描述(映射后)任何内存,它实际上可以是任何东西地址在可寻址空间。

于 2013-03-01T21:27:09.307 回答
2

它说“因为它让那些不知道地址是什么的人感到困惑”——而且,这是真的:如果你知道地址是什么,你就不会感到困惑。理论上,指针是一个指向另一个变量的变量,实际上保存了一个地址,也就是它所指向的变量的地址。我不知道为什么要隐瞒这个事实,这不是一门火箭科学。如果您了解指针,您将更接近了解计算机的工作原理。前进!

于 2013-03-01T17:05:29.647 回答
1

由于我在其他答案中没有看到的不同指针类型,C 或 C++ 指针与简单内存地址不同的另一种方式(尽管考虑到它们的总大小,我可能忽略了它)。但这可能是最重要的一个,因为即使是有经验的 C/C++ 程序员也会绊倒它:

编译器可能会假设不兼容类型的指针不指向相同的地址,即使它们明确指向,这可能会产生简单的指针==地址模型无法实现的行为。考虑以下代码(假设sizeof(int) = 2*sizeof(short)):

unsigned int i = 0;
unsigned short* p = (unsigned short*)&i;
p[0]=p[1]=1;

if (i == 2 + (unsigned short)(-1))
{
  // you'd expect this to execute, but it need not
}

if (i == 0)
{
  // you'd expect this not to execute, but it actually may do so
}

请注意, 有一个例外char*,因此可以使用操作值char*(尽管不是很便携)。

于 2013-07-14T21:03:07.107 回答
0

指针值一个地址。指针变量可以存储地址的对象。这是真的,因为这是标准定义的指针。告诉 C 新手很重要,因为 C 新手通常不清楚指针和它所指向的东西之间的区别(也就是说,他们不知道信封和建筑物之间的区别)。地址的概念(每个对象都有一个地址,这是指针存储的)很重要,因为它可以解决这个问题。

但是,该标准在特定的抽象级别上进行讨论。作者所说的那些“知道地址是什么”的人,但对于 C 语言不熟悉的人,一定已经了解了不同抽象级别的地址——也许是通过编程汇编语言。不能保证 C 实现使用与 CPU 操作码使用的地址相同的表示形式(在本文中称为“存储地址”),这些人已经知道了。

他接着谈到“完全合理的地址操纵”。就 C 标准而言,基本上没有“完全合理的地址操作”之类的东西。加法是在指针上定义的,基本上就是这样。当然,您可以将指针转换为整数,执行一些按位或算术运算,然后将其转换回来。这不能保证按标准工作,因此在编写该代码之前,您最好了解您的特定 C 实现如何表示指针并执行该转换。它可能使用您期望的地址表示,但这不是您的错,因为您没有阅读手册。这不是混淆,而是不正确的编程过程;-)

简而言之,C 使用了比作者更抽象的地址概念。

作者的地址概念当然也不是最底层的词。对于跨多个芯片的虚拟内存映射和物理 RAM 寻址,您告诉 CPU 是您要访问的“存储地址”的数字基本上与您想要的数据在硬件中的实际位置无关。这是所有的间接层和表示层,但作者选择了一个特权。如果您在谈论 C 时要这样做,请选择 C ​​级别的特权

就我个人而言,我认为作者的评论并没有那么有帮助,除非是在将 C 语言介绍给汇编程序员的上下文中。对于那些来自高级语言的人来说,说指针值不是地址当然没有帮助。承认复杂性要好得多,而不是说 CPU 垄断了地址是什么,因此 C 指针值“不是”地址。它们是地址,但它们可能用与他所指的地址不同的语言写成。我认为,在 C 的上下文中将这两件事区分为“地址”和“商店地址”就足够了。

于 2014-02-08T11:16:19.233 回答
0

快速总结:AC 地址是一个值,通常表示为具有特定类型的机器级内存地址。

不合格的词“指针”是模棱两可的。C 有指针对象(变量)、指针类型、指针表达式和指针

使用“指针”这个词来表示“指针对象”是很常见的,这可能会导致一些混淆——这就是为什么我尝试将“指针”用作形容词而不是名词的原因。

至少在某些情况下,C 标准使用“指针”一词来表示“指针值”。例如,malloc的描述说它“返回空指针或指向分配空间的指针”。

那么C中的地址是什么?它是一个指针值,即某个特定指针类型的值。(除了空指针值不一定称为“地址”,因为它不是任何东西的地址)。

标准对一元运算符的描述&说它“产生其操作数的地址”。在 C 标准之外,“地址”一词通常用于指代(物理或虚拟)内存地址,通常大小为一个字(无论给定系统上的“字”是什么)。

AC“地址”通常实现为机器地址——就像 Cint值通常实现为机器字一样。但是一个 C 地址(指针值)不仅仅是一个机器地址。它是一个通常表示为机器地址的值,它是一个具有某种特定类型的值。

于 2013-07-15T01:40:32.440 回答
0

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

       Selector  +--------------+         +-----------+
      ---------->|              |         |           |
                 | Segmentation | ------->|  Paging   |
        Offset   |  Mechanism   |         | Mechanism |
      ---------->|              |         |           |
                 +--------------+         +-----------+
        Virtual                   Linear                Physical
于 2015-09-06T14:41:29.713 回答