如果不是内存地址,究竟什么是C指针?

时间:2013-03-01 05:48:40

标签: c pointers memory-address

在有关C的信誉良好的来源中,在讨论&运算符后给出了以下信息:

  

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

我读过的其他材料(来自同等声誉的来源,我会说)总是毫不掩饰地将指针和&运算符称为给出内存地址。 我很想继续寻找问题的实际情况,但是当信誉良好的消息来源不同意时,这很难。

现在我有点困惑 - 完全是指针,然后,如果不是内存地址?

P.S。

作者后来说: ......我将继续使用“地址”这个术语,因为要发明另一个 [term] 会更糟糕。

25 个答案:

答案 0 :(得分:144)

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

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

答案 1 :(得分:62)

我不确定你的来源,但你所描述的语言类型来自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一个特定的系统,但在另一个系统上使用相同的代码/值可能会使程序崩溃。)

最终比我想象的还要长......

答案 2 :(得分:39)

Pointer vs Variable

在这张图片中,

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

答案 3 :(得分:31)

将指针视为地址是近似。像所有近似值一样,它有时候很有用,但它也不准确,这意味着依赖它会导致麻烦。

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

  • 对象的地址,可以取消引用(如果p包含地址x,则表达式*p的值与{相同{1}});
  • 一个空指针,其中x就是一个例子;
  • 无效内容,它没有指向某个对象(如果NULL没有保留有效值,那么p可以执行任何操作( “未定义的行为”),使程序崩溃是一种相当普遍的可能性。)

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

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

在某些环境中,有可能具有多个指针值,这些指针值具有指向内存中相同位置的不同表示形式(内存中的不同位模式)。您可以将这些视为具有相同地址的不同指针,或同一位置的不同地址 - 在这种情况下隐喻并不清楚。 q + 1运算符始终会告诉您这两个操作数是否指向同一位置,因此在这些环境中,即使p + 1==具有不同的位模式,您也可以p == q

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

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

总而言之,只要记住

,将指针视为地址并不是太糟糕
  • 它只是有效的非空指针,是地址;
  • 您可以在同一位置拥有多个地址;
  • 你不能对地址进行算术运算,并且没有订单;
  • 指针也带有类型信息。
相反,反过来更加麻烦。 并非所有看起来像地址的内容都可以是指针。在任何指针深处的某处都表示为可以作为整数读取的位模式,你可以说这个整数是一个地址。但另一方面,并​​非每个整数都是指针。

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

然而,它远不止于此,因为当你将指针变成一个整数时,你就会陷入困境。这个问题的一个重要原因是优化编译器在微优化方面要比大多数程序员预期的好得多,因此他们对程序如何工作的心理模型是非常错误的。仅仅因为你有相同地址的指针并不意味着它们是等价的。例如,请考虑以下代码段:

int

您可能希望在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(大端)。但是在我的64位Linux PC上使用GCC 4.4:

1 = 1?

在这个简单的例子中,GCC对于warn us what's going wrong非常友好 - 在更复杂的例子中,编译器可能没有注意到。由于65536 = 1?的类型与$ 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? 的类型不同,因此更改p指向的内容不会影响&x指向的内容(在某些明确定义的例外之外)。因此,编译器可以自由地将p的值保留在寄存器中,而不会在&x更改时更新此寄存器。该程序取消引用指向同一地址的两个指针并获得两个不同的值!

这个例子的道德是,只要你保持在C语言的精确规则内,将(非空有效)指针视为地址就可以了。硬币的另一面是C语言的规则是错综复杂的,除非你知道幕后发生的事情,否则难以获得直观的感觉。引擎盖下发生的事情是,指针和地址之间的联系有点松散,既支持“异国情调”的处理器架构,又支持优化编译器。

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

答案 4 :(得分:19)

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

例如:

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*/

就是这样。就这么简单。

enter image description here

用于演示我所说的内容及其输出的程序在这里:

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

答案 5 :(得分:16)

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

根据所写的所有答案判断,有些人认为(1)地址必须是整数,(2)指针不需要虚拟而不是在规范中如此说。有了这些假设,那么明确的指针不一定包含地址。

然而,我们看到虽然(2)可能是真的,(1)可能不一定是真的。什么使&amp;和&amp;根据@ 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)表示的差异)。

答案 6 :(得分:14)

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

最可能的悲伤来源当然是指针算术,,这实际上是C的优势之一。如果指针是一个地址,你会期望指针算法是地址算术;但事实并非如此。例如,向地址添加10应该为您提供一个大于10个寻址单元的地址;但是向指针添加10会使其增加10倍于它所指向的对象的大小(甚至不是实际大小,而是向上舍入到对齐边界)。在具有32位整数的普通体系结构上使用int *时,向其添加10将使其增加40个寻址单位(字节)。经验丰富的C程序员已经意识到这一点并与之共存,但是你的作者显然不喜欢草率的隐喻。

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

答案 7 :(得分:12)

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

例如,给定:

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包含下一个可访问整数的地址,{{1} 1}}使c++指向下一个可寻址的字符。通常情况下,整数占用的内存多于字符数,因此c在递增后的最终值将大于i

答案 8 :(得分:8)

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

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

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

答案 9 :(得分:8)

Mark Bessey已经说过了,但在理解之前需要再次强调。

指针与变量有很大关系,而不是文字3。

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

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

不是变量的指针的计数器示例太多而无法忽略

  • fopen返回一个FILE指针。 (这是变量)
  • 堆栈指针或帧指针通常是不可寻址的寄存器

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

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

答案 10 :(得分:7)

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

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

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

答案 11 :(得分:6)

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

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

答案 12 :(得分:6)

摘要 (我也会把它放在最上面):

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

(1)但是在许多(也许是大多数)编译器指向函数的指针不是地址,而是大于地址(通常是2x,有时更多),或实际上指向内存中的结构而不是包含函数的地址和像恒定池一样的东西。

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

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

(4)几个例子,最着名的是IBM AS / 400,带有安全&#34;胖指针&#34;。

我相信你能找到更多。

<强> DETAIL:

UMMPPHHH !!!!!到目前为止,许多答案都是相当典型的&#34;程序员weenie&#34;答案 - 但不是编译器weenie或硬件weenie。因为我假装是硬件weenie,并经常使用编译器weenies,让我投入我的两分钱:

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

精细。

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

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

我们中的许多人都对旧的Intel x86分段架构留下了痛苦的回忆,其中包括NEAR POINTER和远程指针。值得庆幸的是,现在几乎已经绝迹,所以只有一个快速的总结:在16位实模式中,实际的线性地址是

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

在保护模式下,它可能是

LinearAddress = SegmentRegister[SegNum].base + offset

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

一个更有趣的例子,恕我直言,是(或许是)IBM AS / 400系列。这台计算机是最早用C ++实现操作系统的计算机之一。这个机器上的指针通常是实际地址大小的2倍 - 例如正如this presentation所说,128位指针,但实际地址是48-64位,而且,一些额外的信息,即所谓的功能,提供读,写和限制等权限防止缓冲区溢出。是的:你可以与C / C ++兼容 - 如果这种情况无处不在,中国人民解放军和斯拉夫黑手党就不会侵入太多的西方计算机系统。但从历史上看,大多数C / C ++编程都忽略了性能安全性。最有趣的是,AS400系列允许操作系统创建安全指针,这些指针可以提供给非特权代码,但是无特权代码无法伪造或篡改。同样,安全性,并且在符合标准的情况下,非常草率的非标准兼容的C / C ++代码将无法在这样的安全系统中工作。同样,有官方标准,并且有事实上的标准。

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

  

有很多方法可以解决这个问题[与单一与多重传播和虚拟继承相关的问题]。以下是Visual Studio编译器决定如何处理它:指向乘法继承类的成员函数的指针实际上是一个结构。&#34;   然后他们继续说&#34;投射函数指针可以改变它的大小!&#34;。

正如您可能从我对安全性的指责中猜到的那样,我已经参与了C / C ++硬件/软件项目,其中指针被视为功能而不是原始地址。

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

摘要 (我也会把它放在最上面):

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

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

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

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

(4)几个例子,最着名的是IBM AS / 400,带有安全&#34;胖指针&#34;。

我相信你能找到更多。

答案 13 :(得分:4)

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

答案 14 :(得分:4)

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

答案 15 :(得分:3)

C指针非常类似于内存地址,但是抽象了与机器相关的细节,以及在较低级别指令集中找不到的一些功能。

例如,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指针之间存在许多语义差异。

答案 16 :(得分:3)

在理解指针之前,我们需要了解对象。对象是存在的实体,并且具有称为地址的位置说明符。指针只是C中任何其他变量的变量,其类型称为pointer,其内容被解释为支持以下操作的对象的地址。

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

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

任何对象都支持一个操作&(地址),它将对象的位置说明符(地址)作为指针对象类型进行检索。这应该可以减少围绕命名法的混淆,因为将&称为对象的操作而不是指针,其结果类型是对象类型的指针。

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

答案 17 :(得分:3)

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

答案 18 :(得分:3)

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

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

  

当解释为具有特定类型

时,对象内容的精确含义

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

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

C中的值可以是指针类型。这称为指针值。要清楚,指针值不是C语言中的指针。但我们习惯于将它们混合在一起,因为在C中它不可能是模糊的:如果我们将表达式p称为“指针”,它只是一个指针值而不是一个类型,因为一个命名C中的类型不是由表达式表示,而是由类型名称 typedef-name 表示。

其他一些事情都是微妙的。作为C用户,首先应该知道object的含义:

  

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

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

注意标准规范性地定义了 no “变量”(事实上它在规范性文本中从未被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 ... 地址   常量表达式是指针类型的prvalue核心常量表达式,其计算结果为地址   具有静态存储持续时间的对象,函数的地址,或空指针值或prvalue核心   类型std::nullptr_t的常量表达式。 ...

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

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

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

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

答案 19 :(得分:2)

来考虑一下,我认为这是一个语义问题。我不认为作者是对的,因为C标准将指针称为持有引用对象的地址,正如其他人已在此处提到的那样。但是,地址!=内存地址。一个地址可以是任何符合C标准的东西,虽然它最终会导致一个内存地址,指针本身可以是一个id,一个偏移+选择器(x86),实际上只要它可以描述(映射后)可寻址空间中的任何内存地址。

答案 20 :(得分:2)

它说“因为它让那些不知道地址是什么的人感到困惑” - 而且,这也是事实:如果你了解地址是什么,你就不会感到困惑。从理论上讲,指针是一个指向另一个的变量,实际上是一个地址,它指向的变量的地址。我不知道为什么隐藏这个事实,它不是火箭科学。如果您了解指针,您将更接近了解计算机的工作方式。来吧!

答案 21 :(得分: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*来操纵值(虽然不是很便携)。

答案 22 :(得分:0)

快速摘要:C地址是一个值,通常表示为具有特定类型的计算机级内存地址。

不合格的单词“指针”含糊不清。 C具有指针对象(变量),指针类型,指针表达式和指针

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

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

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

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

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

答案 23 :(得分:0)

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

然而,该标准在特定的抽象层次上进行讨论。那些作者谈到谁“知道地址是什么”,但谁是C的新手,必然要在不同的抽象层次上学习地址 - 也许是通过编程汇编语言。无法保证C实现使用与CPU操作码使用相同的地址表示(在本段中称为“存储地址”),这些人已经知道。

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

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

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

就个人而言,除了在向汇编程序员介绍C语言的情况下,我认为作者的评论并不是很有用。对于来自更高级语言的人来说,指针值不是地址肯定没有帮助。承认复杂性要好得多,而不是说CPU具有垄断说明地址是什么,因此C指针值“不是”地址。它们是地址,但它们可能用与其所指地址不同的语言编写。我认为,将C语境中的两件事区分为“地址”和“商店地址”就足够了。

答案 24 :(得分:0)

简单地说,指针实际上是分段机制的一部分,它在分段后转换为线性地址,然后在分页后转换为物理地址。物理地址实际上是从你公司解决的。

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