在一个关于c的著名来源中,在讨论&运算符后给出了以下信息:
... It's a bit unfortunate that the terminology [address of] remains, because it confuses those who don't know what addresses are about, and misleads those who do: thinking about pointers as if they were addresses usually leads to grief...
我读过的其他材料(我会说,来自同样有声望的来源)总是毫不掩饰地提到指针和&操作符作为内存地址。我很想继续调查这件事的实际情况,但当有信誉的消息来源不同意时,这有点困难。
现在我有点困惑了——指针到底是什么,如果不是内存地址呢?
附笔。
作者后来说:·········································
- 指针是保存地址的变量。它也有自己的地址。这是指针和数组之间的基本区别。数组实际上是一个地址(也就是说,它的地址就是它本身)。
- @whozcraig确实如此,但引言特别强调,&不会返回实际的内存地址。那么它会返回什么呢?
- programmers.stackexchange.com/questions/17898/…
- 你方报价的"可靠来源"是什么?
- 指针没有地址,指针是具有地址的"指针变量"的值。但这很好,所以我们有指针作为指针值,指针作为指针变量。还有什么能让新手更困惑的吗?
- @exebook指针有一个地址,因为指针也是一个保存地址的变量。
- @真的吗?指针没有地址?mkk.int *p = NULL, **pp = &p;太多了。
- @《C书》的作者迈克·巴纳汉。也可以在publications.gbdirect.co.uk/c_book/chapter5/pointers.html上找到。
- @whozcraig@null怎么样?空值也是指针,但它是变量吗?(char*)55555怎么样?这是指针吗?它有地址吗?
- @exebook空值不是指针。从技术上讲,它甚至不是一个地址。
- @whozcraig为什么他们称它为"空指针异常"?
- @是的,我认为作者应该退后一步,从柱子上爬下来。他继续使用他之前刚攻击过的术语的逻辑是可笑的。就定义而言:c99 6.5.3.2,p3:一元运算符生成其操作数的地址。"返回的地址类型特定于应用它的变量,但标准使用该语言并非错误。
- @你很困惑。你所有的例子都有一个地址。地点和方式,这是一个不同的讨论。是的,指针有地址。
- 作者本可以通过剖析Exebook在这个问题上的评论,来解释地址和包含地址的变量(即指针)之间的混淆。
- 为什么每个答案都假设内存地址是整数…它在哪里说这需要是真的?指针中有一些更深刻的东西,使得它不需要是一个整数,而不是一个地址。
- 最终的声誉来源是语言标准,而不是半源于语言标准和半源于作者的书。我很难学会这一点,几乎犯了我所能犯的每一个错误,慢慢地建立了一个C的心理模型,这个模型与标准描述的模型有些接近,最后用标准的模型替换了这个模型。
- 我希望编写语言标准,以便新手可以使用它们学习语言。现在,你通常只能通过阅读标准重新学习你已经知道的语言。
- @thang人认为pointer=integer是因为它通常是这样的(x86 Linux和Windows"教"我们),因为人们喜欢泛化,因为人们不太了解语言标准,而且他们在完全不同的平台上几乎没有经验。这些人可能会假设指向数据的指针和指向函数的指针可以相互转换,数据可以作为代码执行,代码可以作为数据访问。虽然在冯·纽曼体系结构(具有1个地址空间)上可能是这样,但在哈佛体系结构(具有代码和数据空间)上未必是这样。
- @Alexeyfrunze,问题是为什么每个答案都假设一个内存地址是一个整数,而你的答案是人们认为指针=整数,因为它经常出现。你看到这里怎么了吗?您是否隐式地假设GRICEAN的指针=内存地址…
- @Exebook标准不适用于新手(尤其是完整的新手)。他们不应该提供温和的介绍和大量的例子。他们正式地定义了一些东西,这样专业人员就可以正确地实现它。
- @我想我已经在回答和评论中阐明了我的观点。
- 堆栈指针也是指针,但没有地址。实际上,指针是一个"用它的地址引用某事物的概念"。
- 空不是有效地址,但空可以是指针变量的值。
- 有几个人已经声明指针是变量。其他人不同意(@aki-stack pointer;en.wikipedia.org/wiki/pointer_28computer_programming%29"a data type")。有人能引用权威的参考资料吗?谢谢。
- @whozcraig,任何支持初始注释的引用(特别是,指针必须是变量的想法)?只是想知道这是一些人的观点还是一个既定的事实。例如,man 3 fopen()说函数返回一个文件指针,但显然它不返回变量。手册页错误吗?
- @kutschkem空是某些操作系统(或缺少操作系统)上的有效地址。仅仅因为您所使用的操作系统在尝试读/写空值时会给您一个访问冲突,并不意味着它永远都不是一个有效的地址。
- 按照扎克的标准,延迟null是未定义的行为,在C++中至少:StasOfFuff.com /Quass/4365636/C-Null引用。将0作为有效指针似乎是一个非常糟糕的决定。
- @ciscoipphone当你去掉操作系统,只需要有一个地址空间,即代表所有指针值的位数大小,比如在xilinx microblaze中,把东西放在该空间的什么位置都不重要,但是如果你在addre中没有映射到该位置,空值可以被定义为该平台上的0xffffffff。因此,ss space 0可以是有效指针,因为null不必是0。
- C中的指针实现详细信息可能重复
- @我想你现在可以接受一个答案了。这个问题已经开放了近两个月,受到了足够的关注。
- "数组和指针的C++(和C)概念是机器的内存和地址的直接表示,没有开销。"B. Stroustrup
C标准没有定义指针在内部是什么以及如何在内部工作。这是为了不限制平台的数量,C可以作为编译或解释语言实现。
指针值可以是某种ID或句柄,也可以是多个ID的组合(对x86段和偏移量说你好),不一定是实际的内存地址。这个ID可以是任何东西,甚至是一个固定大小的文本字符串。非地址表示对于C解释器可能特别有用。
- 能解释一下处理程序/ID吗?我认为这将有助于我的理解。我到现在才知道这些条件!
- 没什么好解释的。每个变量在内存中都有自己的地址。但是你不必把他们的地址存储在指向他们的指针中。相反,您可以将变量从1编号为任意值,并将该数字存储在指针中。根据语言标准,这是完全合法的,只要实现知道如何将这些数字转换为地址,以及如何使用这些数字和标准要求的所有其他事情进行指针算术。
- 听起来不错,但这又如何:int i=555,*p= and;i;p++;//这是有效的C和有效C++,只在指针为Adress时才起作用。
- 我想在x86上添加一个内存地址,它由一个段选择器和一个偏移量组成,因此将指针表示为段:偏移量仍在使用内存地址。
- @很好,但是这样的一对值不再是简单的单整数地址。
- @Alexeyfrunze,它不是一个整数,但它仍然是一个地址。你突然从哪里得到"整型地址"?它也不是土豆,但我不在乎它不是土豆。
- @是的,这是一个x86地址。
- @thang如何存储选择器和偏移量?它仍然是一个整数值,对吗?虽然它不直接表示地址
- @华伦天奴,你可能会说它是两个整数,因为它不表示整数的排序属性。否则,您可以说任何数据都是整数,尽管它非常大。一个5 MB的文件包含一个非常大的整数…
- C标准可能没有提到指针是什么,但实际上指针是包含绝对地址的整数,或者是包含虚拟地址的整数,以防CPU/OS支持这个概念。指针的其他实现不存在,也不可能存在。因此,将指针视为"模糊实体"对任何人都没有帮助,当然对初学者也没有帮助。
- @伦丁我同意最后一句话在理解这个概念方面。但是,可能/不可能完全是模糊的,这就是整点,指针按照标准是模糊的。显然,在gcc中,以32位或64位模式为目标的x86 CPU并不是模糊的。
- 那么,谁是比C标准更高的权威,认为C标准是错误的,同时我重申了它对指针的处理?为投反对票而感到羞耻。
- 正如我在对另一个答案的评论中所写,在某些情况下,您应该忽略C标准,因为它太通用了。这是一个这样的例子,另一个例子是有符号数字的实现。在现实世界中,相信指针是"模糊的实体"对你没有好处,在你的计算机商店里要求一个"符号和放大CPU"对你也没有好处。
- 我们必须知道,ISO对一个标准进行了限制,即它可能不赞成一种现有技术在市场上领先于另一种技术,它必须是公正的,而不是偏袒某个特定的公司。所以iso c不能站在小端和大端,它不能说明二的补码是通用的,因为70年代的一些Hobo计算机有一个补码,等等。但是,我们这些在ISO之外的现实世界中的人类应该保持清醒的态度。C标准不是圣书。
- @当我了解我的平台和编译器时,我可以忽略标准的一般性和不适用性。然而,最初的问题是一般性的,所以在回答标准时不能忽略它。
- 显然,OP是一个初学者,而不是一个忙于发明一些新的、革命性的指针实现的计算机科学家。因此,为了教育学,我认为C标准应该被忽略。
- @Exebook:它是有效的,但是访问*P将是未定义的行为。在标准中没有任何东西说*P将指向任何有用的东西;这就像访问一个越界数组。
- @伦丁,你不需要成为革命家或科学家。假设您想在物理的16位机器上模拟32位机器,并使用磁盘存储将64kb的RAM扩展到4GB,并将32位指针作为偏移量实现到一个巨大的文件中。这些指针不是真正的内存地址。
- @实际上,它们不是,或者是虚拟地址,与C程序员的行为方式相同,或者是非标准扩展地址,"远指针"。这种系统的编译器实现必须在C程序的物理地址和指针变量之间进行转换。因为如果指针的(内容)不表现为地址,那么指针算术和间接寻址将失败,C程序将变得无用。
- @Alexeyfrunze这个ID实际上被称为逻辑地址。对的?
- 如果理论上它可以是任何东西,指针算术是如何工作的?像somePointer + 1或pointerA - pointerB一样,afaik标准规定指针的差必须是int,不是吗?
- @Lundin,指针的其他实现确实存在,尽管它们在流行的现代平台上并不常见。请参阅c-faq.com/null/machexamp.html。
- @安德烈,编译器在减去指针时必须修正对象大小(它必须给出元素数的差异,而不是字节数),所以我们已经知道指针算术不是整数算术。
- 我还没有看到有一点是,如果您试图使用指针,就像它们是地址一样,使用C标准没有定义的方式,应用优化时编译器可能会产生奇怪的结果。例如,假设您有struct { int a[4], b; } x;。虽然b必须紧跟在结构中a之后,但优化器可以得出结论,*(a+4)不是对b的引用,因为*(x.a+4)不是引用b的有效方式。因此,像x.b = 3; *(x.a+4) = 5; printf("%d", x.b);这样的代码可能会打印"3",即使指针只是地址,它也可能是"5"。
- 我见过的最好的例子是符号化Lisp机器的C实现(大约在1990年)。每个C对象被实现为一个lisp数组,指针被实现为一对数组和一个索引。由于Lisp的数组边界检查,您永远不能从一个对象溢出到另一个对象。
- 就在你认为你很清楚C中的一个概念的时候,答案就出现了,或者是标准中的一个概念,它挑战并摧毁了你认为正确的一切。一个人在学习完标准之前永远不能完全学习C。+先生,非常准确的理解和开明的灵魂:—)
- @EricPostischil您的示例说明的是,该语言不强制编译器正确地识别此类滥用,因此它可能使用基于静态分析的值,或者从用于加载x.b的寄存器中使用值,而不重新加载内存地址(除非a和b是易失性的)-所有这些与是否"指针"完全无关。[是]只是地址"。
我不确定你的来源,但是你描述的语言类型来自C标准:
6.5.3.2 Address and indirection operators
[...]
3. The unary & operator yields the address of its operand. [...]
所以…是的,指针指向内存地址。至少这就是C标准所暗示的意思。
更清楚地说,指针是一个保存某个地址值的变量。对象的地址(可以存储在指针中)由一元&运算符返回。
我可以将地址"42 Wallaby Way,Sydney"存储在一个变量中(该变量可能是某种"指针",但因为它不是内存地址,所以我们不能正确地称之为"指针")。你的计算机有存储桶的地址。指针存储地址值(即指针存储地址值"42 Wallaby Way,Sydney")。
编辑:我想扩展亚历克赛·弗伦兹的评论。
指针到底是什么?让我们看看C标准:
6.2.5 Types
[...]
20. [...]
A pointer type may be derived from a function type or an object type, called the referenced type. A pointer type describes an object whose value provides a reference to an entity of the referenced type. A pointer type derived from the referenced type T is sometimes called ‘‘pointer to T’’. The construction of a pointer type from a referenced type is called ‘‘pointer type derivation’’. A pointer type is a complete object type.
从本质上讲,指针存储的值提供了对某个对象或函数的引用。有点。指针用于存储提供对某些对象或函数的引用的值,但情况并非总是这样:
6.3.2.3 Pointers
[...]
5. An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation.
上面的引号说我们可以把一个整数变成一个指针。如果我们这样做(也就是说,如果我们将一个整数值填充到一个指针中而不是对一个对象或函数的特定引用),那么指针"可能不会指向一个引用类型的实体"(也就是说,它可能不会提供对一个对象或函数的引用)。它可能会给我们提供其他的东西。这是一个你可以在指针中插入某种句柄或ID的地方(也就是说,指针不是指向一个对象;它存储了一个表示某个东西的值,但这个值可能不是地址)。
是的,正如AlexeyFrunze所说,指针可能没有存储对象或函数的地址。指针可能存储了某种"句柄"或ID,您可以通过为指针分配任意整数值来实现这一点。此句柄或ID表示的内容取决于系统/环境/上下文。只要您的系统/实现能够理解价值,您就处于良好的状态(但这取决于具体的价值和具体的系统/实现)。
通常,指针存储对象或函数的地址。如果它没有存储一个实际地址(对象或函数),结果是由实现定义的(也就是说,指针现在所表示的内容和发生的情况取决于您的系统和实现,因此它可能是特定系统上的句柄或ID,但在另一个系统上使用相同的代码/值可能会使您的程序崩溃)。
结果比我想象的要长…
- 在C解释器中,指针可以保存非地址ID/句柄等。
- C语言不是标准描述的C语言。它是"解释C",它是不同的动物,据我所知是非标准化的。
- @Alexeyfrunze:我在你的评论中添加了一个(冗长的)扩展。请随意批评。
- @Exebook本标准不限于编译C。
- 好东西。比我高1。
- 不过,假装指针可以存储某种类型的"id"有意义吗?因为在ISO 9899之外的寒冷严酷的现实中,不存在这样的实现。指针被实现为一个变量,包含一个原始整数格式的地址,在现实世界中100%的现有计算机系统上。有些情况下,您应该忽略标准,因为它是不必要的通用标准。
- "伦丁布拉沃!让我们更忽略标准!就好像我们还没有足够地忽略它,也没有因为它而生产出有缺陷的和糟糕的可移植软件。另外,请不要说原来的问题是一般性的,因此需要一般性的回答。
- @Alexeyfrunze我实际上是标准遵从性的坚定信仰者,只要有机会,我就会宣扬这一点。但是你不能盲目地接受标准中的所有东西,你需要用理性的思维和问题,如果它适用于现实世界。我在阅读和解释无聊的技术标准方面有丰富的经验,每一个标准都适用。如果您可以得出这样的结论:标准说了些什么,但在现实世界中它没有意义,那么您可以在文档中解决这个问题,而忽略标准。任何第三方通知机构审查您的产品都将接受。
- @伦丁,你觉得雷内萨D10V的笔记怎么样?您认为这些是原始整数格式的地址还是D10V不存在?
- 当其他人说一个指针可能是一个句柄或者地址以外的其他东西时,他们不仅仅意味着你可以通过向指针中投射一个整数来将数据强制转换成指针。它们意味着编译器可能正在使用内存地址以外的东西来实现指针。在带有DEC的ABI的alpha处理器上,函数指针不是函数的地址,而是函数描述符的地址,描述符包含函数的地址和一些函数参数的数据。关键是C标准非常灵活。
- @samueledwinward有很多奇怪的情况,程序不能直接寻址物理内存,原因多种多样,而是使用一些虚拟寻址方案。在亚历克赛的回答下面看到我的最新评论。
- @Lundin:在现实世界中,指针被实现为100%现有计算机系统上的整数地址的断言是错误的。计算机存在字寻址和段偏移寻址。编译器仍然存在,支持近指针和远指针。存在PDP-11计算机,带有RSX-11和任务生成器及其覆盖,其中指针必须标识从磁盘加载函数所需的信息。如果对象不在内存中,指针不能具有对象的内存地址!
- @塞缪尔,它们看起来仍然是整数,它们仍然指向内存地址。只是一些指向字节的地址和一些指向字的地址。我不确定伦丁的说法,但这不是一个反例。我曾经用过一个微处理器,它有完全独立的数据和代码存储器,每个都从0x0000开始。您无法交换指针,但它们仍然是整数。D10V似乎是相同的方式…您不能交换指针,但它们仍然是整数。
- @Mindor先生,如果你愿意,浮点数也是整数。但是说我可以有两个指向同一段内存的指针,它们都是整数,并且这些整数的值不相同…在那一点上说它们是整数有什么意义?
- @萨缪尔德温沃德,因为他们仍然是。我并不是说它们是整数这一事实在你看来是有用的,只是你举的一个系统例子,在这个系统中,整数值的解释因指针类型的不同而不同,并不能反驳Lundin的说法,即指针是指在所有实现中指向一个地址的整数(顺便说一句,我不支持)。
- @Mindor先生,技术上不是,imo,因为内存位置(或内存字)的内容本身不是整数,而是一个字节序列,其整数(或指针或fp数字)的解释不是绝对的,而是取决于您对其执行的操作。例如,以某个数字I/O卡的内存映射的8位寄存器为例,假设单个位代表一些外部线路的状态。把内存位置的内容称为整数有意义吗?每一个比特都是完全独立的,那么它们如何解释为一个数字呢?
- @Alexeyfrunze似乎很难与标准引号争论,一元运算符生成其操作数的地址。如果操作数的类型为"类型",则结果的类型为"指向类型的指针"。这意味着存储在指针中的值就是被指向的对象的地址。指针中不能存储"非地址"。您所指的C解释器将以文本形式或其他形式表示其地址。
- @你想说什么?
在这张照片中,
指针_p是一个指针,位于0x12345,指向变量_v,位于0x34567。
- +1解释它保存了什么的最好方法是通过图片。好的!
- 这不仅不涉及与指针相反的地址概念,而且还完整地遗漏了地址不仅仅是整数的点。
- -1,这就解释了指针是什么。这不是问题所在——你把问题涉及的所有复杂问题都抛到一边了。
- 对不起的。我只是想弄清楚什么是指针
- 我同意@gilles的观点。这个被修改了很多,这说明了问题所在。人是白痴。这个答案根本不能解决这个问题。
把指针看作地址是一种近似值。像所有的近似法一样,它有时足够有用,但也不精确,这意味着依赖它会导致麻烦。好的。
指针就像一个地址,它指示在哪里找到一个对象。这种类比的一个直接限制是并非所有指针都实际包含地址。NULL是一个指针,它不是地址。指针变量的内容实际上可以是以下三种类型之一:好的。
- 可以取消引用的对象的地址(如果p包含x的地址,则表达式*p与x的值相同);
- 空指针,以NULL为例;
- 无效内容,它不指向对象(如果p不包含有效值,那么*p可以做任何事情("未定义的行为"),使程序崩溃是很常见的可能性)。
此外,更准确的说法是指针(如果有效和非空)包含一个地址:指针指示在何处查找对象,但有更多的信息绑定到它。好的。
特别是指针有一个类型。在大多数平台上,指针的类型在运行时没有影响,但它的影响在编译时超出了类型。如果p是指向int(int *p;)的指针,那么p + 1指向一个整数,该整数是p之后的sizeof(int)字节(假设p + 1仍然是有效指针)。如果q是指向与p相同地址(char *q = p;的char的指针,那么q + 1与p + 1不是同一地址。如果您将指针视为地址,那么对于同一位置的不同指针,"下一个地址"是不同的,这是不太直观的。好的。
在某些环境中,可能有多个指针值具有不同的表示形式(内存中的不同位模式),它们指向内存中的相同位置。你可以把它们看作是持有相同地址的不同指针,或者是同一位置的不同地址——在这种情况下,这个比喻并不清楚。==运算符总是告诉您两个操作数是否指向同一位置,因此在这些环境中,即使p和q具有不同的位模式,也可以使用p == q。好的。
甚至在有些环境中,指针携带地址之外的其他信息,例如类型或权限信息。作为一个程序员,你可以很容易地度过你的一生而不必遇到这些。好的。
在某些环境中,不同类型的指针具有不同的表示形式。您可以将其视为具有不同表示形式的不同类型的地址。例如,一些架构有字节指针和字指针,或者对象指针和函数指针。好的。
总之,只要你记住了,把指针看作地址就不会太糟糕了。好的。
- 它是唯一有效的非空指针,即地址;
- 同一地点可以有多个地址;
- 你不能在地址上做算术,而且地址上没有顺序;
- 指针还包含类型信息。
换个方向走更麻烦。不是所有看起来像地址的东西都可以是指针。在某个较深的地方,任何指针都表示为一个位模式,可以作为一个整数读取,您可以说这个整数是一个地址。但反过来说,不是每个整数都是指针。好的。
首先有一些众所周知的限制;例如,指定程序地址空间之外位置的整数不能是有效的指针。未对齐的地址不会为需要对齐的数据类型生成有效指针;例如,在int需要4字节对齐的平台上,0x7654321不能是有效的int*值。好的。
然而,它远远超出了这个范围,因为当你把一个指针变成一个整数时,你就陷入了一个麻烦的世界。这一问题的很大一部分是,优化编译器在微优化方面比大多数程序员所期望的要好得多,因此他们关于程序如何工作的心理模型是完全错误的。仅仅因为你有相同地址的指针并不意味着它们是等价的。例如,考虑以下代码段:好的。
1 2 3 4 5
| unsigned int x = 0;
unsigned short *p = (unsigned short*)&x ;
p [0] = 1;
printf("%u = %u
", x , *p ); |
在运行sizeof(int)==4和sizeof(short)==2的铣床时,您可能会期望打印EDOCX1(小端)或EDOCX1(大端)。但在我的64位Linux PC上,使用GCC4.4:好的。
1 2 3 4 5
| $ 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语言的规则是复杂的,除非你知道引擎盖下发生了什么,否则很难获得直观的感觉。在这种情况下,指针和地址之间的联系有些松散,既支持"异域"处理器架构,也支持优化编译器。好的。
所以,把指点作为你理解的第一步,但不要太过依赖直觉。好的。好啊。
- + 1。其他的答案似乎忽略了指针带有类型信息。这比地址/id/任何讨论都要重要得多。
- +1.关于类型信息的优秀点。我不确定编译器示例是否正确…例如,当p尚未初始化时,似乎不太可能保证*p = 3成功。
- @拉尔斯,你说得对,谢谢,我是怎么写的?我用一个例子来代替它,这个例子甚至可以在我的电脑上展示出令人惊讶的行为。
- 嗯,空值是((void*)0)。?
- 在许多实现中,空不是空指针。它甚至不是一个指针。#define NULL(1312-1200-112ull)是一个非常好的NULL定义。空必须是计算为空指针常量的宏,在某些情况下,空指针常量将自动转换为空指针。
- @gnaser729空指针是指针。NULL不是,但对于这里所要求的详细程度来说,这是一个不相关的干扰。即使对于日常编程来说,NULL可能被实现为一个不说"指针"的东西,这一事实并不经常出现(主要是将NULL传递给一个变量函数——但即使在那里,如果您不进行强制转换,您已经假设所有指针类型都具有相同的表示)。
- 这就回答了这个问题。指针不是地址;它是地址的语言抽象。
- 空比较等于零。这与(void*)0不同。
指针是保存内存地址的变量,而不是地址本身。但是,您可以取消对指针的引用,并访问内存位置。
例如:
1 2 3
| 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
程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #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
", (void *)&q );
printf("p contains %p
", (void *)p );
p = NULL ;
printf("NULL p now contains %p
", (void *)p );
return 0;
} |
- 它会使人更加困惑。爱丽丝,你能看见一只猫吗?不,我只能看到猫的微笑。所以说指针是一个地址,或者说指针是一个保存地址的变量,或者说指针是一个概念的名称,这个概念指的是一个地址的概念,那么写书人在混淆neeewbies方面能走多远呢?
- @对那些老练的指点人来说,这很简单。也许一张照片会有帮助?
- 指针不一定包含地址。在C解释器中,它可能是其他的东西,某种ID/句柄。
- "label"或变量名是一个编译器/汇编程序,在机器级别不存在,所以我认为它不应该出现在内存中。
- "指针是保存内存地址的变量,而不是地址本身"-真的吗?linux.die.net/man/3/fopen说fopen()返回一个文件指针。fopen()是否返回变量?
- 不,没那么简单。首先,空指针不包含地址。更重要的是,您可以有两个指针,它们具有相同的地址,但行为不同(我的答案将更详细)。
- @Larsh是指针地址还是保存地址的变量?使用fopen(),假设从中返回了一个地址,您将地址放在哪里?在不将返回的"地址"放入FILE *对象的情况下,可以使用fopen()的返回值吗?
- @指针变量可以包含指针值。如果需要多次使用fopen的结果(对于fopen),则只需将该结果存储到变量中即可(对于fopen)。
- @Aniket:返回指针值。如果需要,可以将其放入指针变量中,或者直接传递给fclose()。你通常会把它放在一个变量中,但你不必这样做。
很难说清楚那些书的作者到底是什么意思。指针是否包含地址取决于如何定义地址以及如何定义指针。
从所有的答案来看,有些人认为(1)一个地址必须是一个整数,(2)指针不需要虚拟的,不需要在规范中这样说。有了这些假设,显然指针不一定包含地址。
然而,我们看到,虽然(2)可能是真的,(1)可能不一定是真的。那么,根据@cornstaks的答案,将&;称为操作员地址这一事实怎么解释呢?这是否意味着规范的作者希望指针包含地址?
所以我们可以说,指针包含一个地址,但地址不必是整数吗?也许吧。
我认为这一切都是在回避学究式的语义谈话。实际上,这是毫无价值的。你能想到一个编译器,它以一种指针值不是地址的方式生成代码吗?如果是这样,什么?我就是这么想的…
我认为这本书的作者(声称指针不一定只是地址的第一个摘录)可能指的是指针带有固有类型信息的事实。
例如,
1 2 3
| int x;
int* y = &x;
char* z = &x; |
Y和Z都是指针,但Y+1和Z+1是不同的。如果它们是内存地址,这些表达式是否会给您相同的值?
而在这里,人们对指针的思考,就好像它们是地址,通常会导致悲伤。写错误是因为人们把指针当作地址来考虑,这通常会导致悲伤。
55555可能不是指针,尽管它可能是地址,但(int*)55555是指针。55555+1=55556,但(int*)55555+1为55559(+/-尺寸差(int))。
- +1表示指针算术与地址算术不同。
- 在16位8086的情况下,存储器地址由段基+偏移量(两个16位)描述。在内存中有许多段基+偏移量的组合提供相同的地址。这个far指针不仅仅是"整数"。
- @我不明白你为什么发表这个评论。该问题已在其他答案下作为评论进行了讨论。几乎所有其他答案都假定地址=整数,而任何非整数的东西都不是地址。我简单地指出这一点,并注意到它可能是正确的,也可能不是正确的。我在回答中的全部观点是,这是不相关的。这都是学究式的,主要的问题并没有在其他答案中得到解决。
- @唐,"指针==地址"的想法是错误的。每个人和他们最喜欢的姑妈都继续这样说,这不符合事实。
- @沃恩布兰德,你为什么在我的帖子下发表这样的评论?我没说是对是错。事实上,在某些场景/假设中是正确的,但并非总是如此。让我再总结一下这篇文章的要点(第二次)。我在回答中的全部观点是,这是不相关的。这都是学究式的,主要的问题并没有在其他答案中得到解决。更合适的做法是对那些确实声称pointer==address或address==integer的答案进行注释。请参阅我在Alexey的帖子中关于segment:offset的评论。
- 顺便说一句,有很多答案都是错误的,不准确的,不完整的,完全没有把握的,这是我无法控制的。而且,他们的投票结果也超出了我的控制。在写这个答案之前,我对他们进行了评论,以澄清这个问题。
型
指针是表示内存位置的抽象。注意,这句话并没有说把指针当作记忆地址是错误的,它只是说它"通常会导致悲伤"。换句话说,它会导致你有错误的期望。
最可能的悲伤来源当然是指针算术,这实际上是C的优势之一。如果指针是一个地址,您会期望指针算术是地址算术,但它不是。例如,向一个地址添加10应该会给您一个更大10个寻址单元的地址;但是向指针添加10会使它增加10倍于它指向的对象的大小(甚至不是实际大小,而是四舍五入到对齐边界)。在32位整数的普通体系结构上使用int *时,向其添加10将使其增加40个寻址单元(字节)。经验丰富的C程序员知道这一点并与之共存,但您的作者显然不喜欢草率的隐喻。
还有一个关于指针内容如何表示内存位置的附加问题:正如许多答案所解释的那样,地址并不总是int(或long)。在某些体系结构中,地址是"段"加上偏移量。指针甚至可能只包含当前段("near"指针)的偏移量,该段本身不是唯一的内存地址。而指针内容可能与硬件理解的内存地址只有间接关系。但引用的这段话的作者甚至没有提到表象,所以我认为这是概念上的对等,而不是他们心目中的表象。
以下是我过去对一些困惑的人的解释:指针有两个影响其行为的属性。它有一个值(在典型环境中)是内存地址,还有一个类型,它告诉您它指向的对象的类型和大小。
例如,给定:
1 2 3 4
| union {
int i;
char c;
} u; |
可以有三个不同的指针指向同一对象:
1 2 3
| void *v = &u;
int *i = &u.i;
char *c = &u.c; |
如果比较这些指针的值,它们都是相等的:
但是,如果您增加每个指针,您将看到它们指向的类型变得相关。
1 2 3 4
| i++;
c++;
// You can't perform arithmetic on a void pointer, so no v++
i != c |
此时,变量i和c的值不同,因为i++使i包含下一个可访问整数的地址,而c++使c指向下一个可寻址字符。通常,整数比字符占用更多的内存,因此,当两个整数都递增后,i的值将大于c。
- 型标志很好的例子+1。
- 型+1谢谢。有了指针,价值和类型是不可分割的,就像一个人可以把人的身体和灵魂分开一样。
- 型i == c格式错误(如果存在从一种类型到另一种类型的隐式转换,则只能比较指向不同类型的指针)。此外,使用强制转换修复此问题意味着您已经应用了转换,然后转换是否更改值仍有争议。(您可以断言它没有,但这只是断言您试图用这个例子证明的相同的事情)。
你是对的,神志清醒。通常,指针只是一个地址,所以您可以将其转换为整数并进行任何运算。
但有时指针只是地址的一部分。在某些体系结构中,指针被转换成一个地址,加上基地址或使用另一个CPU寄存器。
但是现在,在带有平面内存模型和C语言本机编译的PC和ARM体系结构中,可以认为指针是指向一维可寻址RAM中某个位置的整数地址。
- PC.…平面记忆模型?什么是选择器?
- 里特当下一个体系结构发生变化时,可能是单独的代码和数据空间,或者有人回到了古老的段体系结构(这对安全性有很大意义,甚至可能添加一些键到段号+偏移量来检查权限),您可爱的"指针只是整数"崩溃了。
马克·贝西已经说过了,但这需要重新强调,直到理解。
指针与变量的关系与文本3无关。
指针是一个值(地址)和类型(具有附加属性,如只读)的元组。类型(以及附加参数,如果有的话)可以进一步定义或限制上下文;例如__far ptr, __near ptr:地址的上下文是什么:堆栈、堆、线性地址、某处偏移量、物理内存或什么。
类型的属性使指针算术与整数算术稍有不同。
非变量指针的计数器示例太多,无法忽略
在C程序的生命周期中,还有许多其他的临时指针实例没有地址,因此它们不是变量,而是具有编译时关联类型的表达式/值。
指针与C中的任何其他变量一样,从根本上来说是一组位,可以由一个或多个连接的unsigned char值表示(与任何其他类型的cariable一样,sizeof(some_variable)将指示unsigned char值的数目)。使指针与其他变量不同的是,C编译器将解释指针中的位,以某种方式标识变量可能存储的位置。在C语言中,与其他语言不同,它可以为多个变量请求空间,然后将指向该集中任何值的指针转换为指向该集中任何其他变量的指针。
许多编译器通过使用其位存储实际的机器地址来实现指针,但这并不是唯一可能的实现。一个实现可以让一个数组(用户代码无法访问)列出程序正在使用的所有内存对象(变量集)的硬件地址和分配的大小,并让每个指针在数组中包含一个索引以及该索引的偏移量。这样的设计不仅允许系统将代码限制为仅在其拥有的内存上运行,还可以确保指向一个内存项的指针不会意外转换为指向另一个内存项的指针(在使用硬件地址的系统中,如果foo和bar是由10个存储项组成的数组,那么实际上,在内存中,指向foo的"第十一"项的指针可能会指向bar的第一项,但在每个"指针"都是对象ID和偏移量的系统中,如果代码试图索引超出其分配范围的指向foo的指针,则系统可能会陷阱。这样的系统也可以消除内存碎片问题,因为与任何指针关联的物理地址都可以移动。
注意,虽然指针有些抽象,但它们不够抽象,不足以允许完全符合标准的C编译器实现垃圾收集器。C编译器指定每个变量(包括指针)都表示为一个unsigned char值的序列。对于任何变量,都可以将其分解为数字序列,然后将该数字序列转换回原始类型的变量。因此,一个程序可以存储一些存储器(接收指向它的指针),在那里存储一些东西,将指针分解成一系列字节,在屏幕上显示这些字节,然后删除对它们的所有引用。如果程序随后接受键盘上的一些数字,将这些数字重新组合成一个指针,然后尝试从该指针读取数据,如果用户输入的数字与程序先前显示的数字相同,程序将需要输出存储在calloc内存中的数据。由于计算机不可能知道用户是否复制了显示的数字,因此计算机不可能知道将来是否可以访问上述内存。
- 型在很大的开销下,也许您可以检测到任何可能"泄漏"其数值的指针值的使用,并固定分配,以便垃圾收集器不会收集或重新定位它(当然,除非明确调用free)。结果实现是否有用是另一回事,因为它的收集能力可能太有限,但您至少可以称它为垃圾收集器:-)指针分配和算术不会"泄漏"该值,但必须检查对未知来源的char*的任何访问。
- @史蒂文杰索普:我认为这样的设计比无用更糟糕,因为代码不可能知道需要释放哪些指针。假定任何看起来像指针的东西都是过于保守的垃圾收集器,但通常看起来像指针但不是指针的东西都有可能发生变化,从而避免了"永久性"内存泄漏。任何看起来像要分解指向字节的指针的操作都将永久冻结指针,这是内存泄漏的一个保证方法。
- 我认为它无论如何都会失败,因为性能原因——如果你想让你的代码运行得慢一点,因为每一个访问都被检查过,那么就不要用C来写它;-)我对C程序员的创造力比你有更高的希望,因为我认为尽管不方便,但避免不必要地固定分配可能是不合理的。不管怎样,C++为了解决这个问题,定义了"安全派生指针",因此我们知道如果我们想把C指针的抽象性提高到支持合理有效的垃圾收集的水平,该怎么做。
- @stevejessop:为了使GC系统有用,它应该能够可靠地释放没有调用free的内存,或者防止对被释放对象的任何引用成为对活动对象的引用[即使使用需要显式寿命管理的资源,GC仍然可以有效地执行后一个功能];当n变大时,如果n个对象被不必要地钉住的概率同时接近零,那么有时错误地认为对象具有对它们的实时引用的GC系统可能是可用的。除非有人愿意标记编译器错误…
- 对于代码是有效的C++,但是编译器不能证明指针永远不能转换成一个不可识别的形式,我不知道如何避免一个实际上从不使用指针作为整数的程序可能被错误地认为是这样做的风险。
- 为了使GC系统有用,它必须能够通过仔细地处理对象,收集程序员确保可收集的对象的子集。因为它非常有用,在Java GC有用的情况下,它甚至对不小心处理的对象也是如此;-有许多程序可以简单地确保对于某些实体类型,它的对象的地址永远不会"泄露",但是编译器很难(或不可能)知道程序员是这样做的。有压力的
- @ ScVIEJSOP:C++中可能有一种GC形式,其中每个GC对象封装了一些基元和一些GC引用,其中所有不在GC对象中保存的GC引用必须保存在生命周期管理对象中(每个"外部引用"对象)将被认为是GC根;不能调用"OUT"的析构函数。ide"gc引用将泄漏该对象以及由此直接或间接引用的所有对象]。我不知道的是,一个人有多好地避免发表像foo->ref1 = bar->ref2(其中foo和bar都是OutsideGcRef)。
- …需要构造一个OutsideGcRef来暂时保存引用。理想情况下,除非GC,否则bar->ref2的类型将不可构造,但可以转换为OutsideGcRef,并且将重写赋值运算符,以便直接复制引用而不必首先创建OutsideGcRef。
小结(我也会放在上面):好的。
(0)将指针视为地址通常是一个很好的学习工具,并且通常是指向普通数据类型的指针的实际实现。好的。
(1)但在许多情况下,也许大多数情况下,编译器指向函数的指针不是地址,而是大于地址(通常为2x,有时更大),或者实际上是指向内存中结构的指针,而不是包含函数地址和常量池之类的内容的指针。好的。
(2)指向数据成员的指针和指向方法的指针通常更为陌生。好的。
(3)具有远指针和近指针问题的传统x86代码好的。
(4)几个例子,最显著的是IBMAS/400,带有安全的"胖指针"。好的。
我相信你能找到更多。好的。
细节:好的。
啊!!!!!!!到目前为止,许多答案都是典型的"程序员weenie"答案,但不是编译器weenie或硬件weenie。因为我假装是一个硬件工程师,而且经常和编译器工程师一起工作,所以让我把我的两分钱投进去:好的。
在许多C编译器上,指向T类型数据的指针实际上是T的地址。好的。
好的。好的。
但是,即使在许多编译器上,某些指针也不是地址。你可以通过观察sizeof(ThePointer)来判断。好的。
例如,指向函数的指针有时比普通地址大得多。或者,他们可能涉及一个间接的层面。本文提供了一个涉及IntelItanium处理器的描述,但我见过其他的描述。通常,要调用一个函数,不仅必须知道函数代码的地址,还必须知道函数常量池的地址——一个内存区域,从中用一条加载指令加载常量,而不是编译器必须从几个立即加载、移位和或指令中生成64位常量。因此,您需要2个64位地址,而不是单个64位地址。一些ABI(应用程序二进制接口)将其移动为128位,而另一些则使用间接级别,函数指针实际上是包含刚才提到的2个实际地址的函数描述符的地址。哪个更好?取决于您的观点:性能、代码大小和一些兼容性问题-通常,代码假定指针可以转换为长或长,但也可以假定长正好是64位。这种代码可能不符合标准,但客户可能希望它工作。好的。
我们中的许多人对旧的Intelx86分段体系结构有着痛苦的记忆,它们有近指针和远指针。值得庆幸的是,到目前为止,这些都几乎灭绝了,所以只是一个简短的总结:在16位实模式中,实际的线性地址是好的。
1
| LinearAddress = SegmentRegister[SegNum].base << 4 + Offset |
而在保护模式下,它可能是好的。
1
| LinearAddress = SegmentRegister[SegNum].base + offset |
根据段中设置的限制检查结果地址。有些程序并不是真正的标准C/C++ +远近指针声明,但很多人只是说EDCOX1〔0〕-但是有编译器和链接器开关,例如,代码指针可能在指针附近,只对CS(代码段)寄存器中的任何一个32位偏移,而数据指针可能是远指针,指定一个16位的段号和一个48位值的32位偏移量。现在,这两个数量肯定与地址有关,但是由于它们的大小不同,地址是哪个?此外,除了与实际地址相关的内容外,这些段还具有只读、读写、可执行等权限。好的。
一个更有趣的例子,imho,是(或者可能是)IBMAS/400系列。这台计算机是第一个在C++中实现操作系统的计算机之一。这个机器上的指针通常是实际地址大小的2倍——例如,正如本演示所说,128位指针,但实际地址是48-64位,而且,还有一些额外的信息,即所谓的功能,提供了读、写等权限以及防止缓冲区溢出的限制。是的:你可以兼容C/C++做这件事,如果这是无处不在的,中国解放军和斯拉夫黑手党不会侵入这么多的西方计算机系统。但是历史上大多数C/C++编程忽略了性能的安全性。最有趣的是,AS400系列允许操作系统创建安全指针,该指针可以提供给非特权代码,但非特权代码不能伪造或篡改。同样,安全性,同时符合标准,许多草率的非标准兼容C/C++代码在这样的安全系统中是行不通的。同样,有官方标准,也有事实上的标准。好的。
现在,我将不再讨论安全性的Soapbox,并介绍一些指针(各种类型)通常不是真正的地址的其他方法:指向数据成员的指针、指向成员函数方法的指针,以及它们的静态版本比普通地址大。正如这篇文章所说:好的。
There are many ways of solving this [problems related to single versus multiple inheitance, and virtual inheritance]. Here's how the Visual Studio compiler decides to handle it: A pointer to a member function of a multiply-inherited class is really a structure."
And they go on to say"Casting a function pointer can change its size!".
Ok.
正如你可以猜到的那样,我参与了C/C++硬件/软件项目,其中一个指针被处理得更像是一个能力而不是原始地址。好的。
我可以继续说,但我希望你能明白。好的。
小结(我也会放在上面):好的。
(0)将指针视为地址通常是一个很好的学习工具,并且通常是指向普通数据类型的指针的实际实现。好的。
(1)但在许多情况下,也许大多数情况下,编译器指向函数的指针不是地址,而是大于地址(通常为2x,有时更大),或者实际上是指向内存中结构的指针,而不是包含函数地址和常量池之类的内容的指针。好的。
(2)指向数据成员的指针和指向方法的指针通常更为陌生。好的。
(3)具有远指针和近指针问题的传统x86代码好的。
(4)几个例子,最显著的是IBMAS/400,带有安全的"胖指针"。好的。
我相信你能找到更多。好的。好啊。
- 在16位实模式LinearAddress = SegmentRegister.Selector * 16 + Offset中(注乘以16,而不是移位16)。在保护模式下,LinearAddress = SegmentRegister.base + offset(没有任何形式的乘法;段基存储在gdt/ldt中,并按原样缓存在段寄存器中)。
- 关于实模式,你是正确的。我已经调整了我的职位。
- 您对段基也正确。我记错了。它是一个段限,可以选择乘以4K,当它把一个段描述符从内存装入段寄存器时,只需要硬件对段基进行译码。
指针是一种变量类型,它在C/C++中是可用的,并且包含内存地址。与任何其他变量一样,它有自己的地址并占用内存(数量是平台特定的)。
由于混淆,您将看到的一个问题是,试图通过简单地按值传递指针来更改函数中的引用。这将复制函数作用域中的指针,对新指针"points"的任何更改都不会更改调用函数作用域中指针的引用。为了修改函数中的实际指针,通常会将指针传递给指针。
- 通常,它是一个句柄/id。通常,它是一个普通的地址。
- 我调整了我的答案,使之更符合维基百科中手柄的定义。我喜欢将指针作为句柄的特定实例来引用,因为句柄可能只是对指针的引用。
指针只是另一个变量,用于保存内存位置的地址(通常是另一个变量的内存地址)。
- 所以,指针实际上是一个内存地址?你不同意作者的观点吗?只是想理解。
- 指针的主要功能是指向某个东西。如何准确地实现这一点,以及是否有一个真正的地址,还没有被定义。指针可以只是一个ID/句柄,而不是一个真正的地址。
你可以这样看。指针是表示可寻址内存空间中地址的值。
- 指针不一定要包含实际的内存地址。看看我的答案和下面的评论。
- 什么。。。。指向堆栈上第一个变量的指针未打印0。它打印堆栈框架的顶部(或底部),这取决于它是如何实现的。
- @对于第一个变量,顶部和底部是相同的。在这个堆栈的例子中,顶部或底部的地址是什么?
- @华伦天奴,你为什么不试试……显然你没有试过。
- @谢谢你是对的,我做了一些很糟糕的假设,我的辩护是5点到这里。
- 这在技术上是正确的-所有对象都有一个位表示,可以解释为一个整数-但不是很有帮助。您可以有不同的地址指向同一个位置。指针上的算术与对应整数上的算术不同。在许多情况下,具有相同地址的不同类型的指针是不等效的。
地址用于将固定大小的存储器(通常是每个字节)标识为整数。这被精确地称为字节地址,ISO C也使用它。可以有一些其他的方法来构造地址,例如每个位的地址。然而,通常只使用字节地址,我们通常省略"byte"。
从技术上讲,地址永远不是C中的值,因为(iso)C中术语"值"的定义是:
precise meaning of the contents of an object when interpreted as having a specific type
(我强调)但是,在C语言中没有这样的"地址类型"。
指针不相同。指针是C语言中的一种类型。有几种不同的指针类型。它们不一定遵守相同的语言规则,例如,++对int*与char*类型值的影响。
C中的值可以是指针类型。这称为指针值。显然,指针值不是C语言中的指针。但我们习惯于将它们混合在一起,因为在C中不太可能是含糊不清的:如果我们将表达式p称为"指针",它只是一个指针值,而不是类型,因为C中的命名类型不是由表达式表示的,而是由类型名或typedef名称表示的。
还有一些事情很微妙。作为C用户,首先应该知道object的含义:
region of data storage in the execution environment, the contents of which can represent
values
对象是表示特定类型的值的实体。指针是对象类型。因此,如果我们声明int* p;,那么p表示"指针类型的对象",或"指针对象"。
注:本标准没有规范定义的"变量"(事实上,ISO C在规范性文本中从未将其用作名词)。然而,非正式地,我们称对象为变量,就像其他语言一样。(但仍然不是那么精确,例如在C++中,变量可以是标准的引用类型,而不是对象。)短语"指针对象"或"指针变量"有时被视为"指针值",上面可能有微小的差别。(还有一组示例是"array"。)
由于指针是一种类型,地址在C中实际上是"无类型的",所以指针值大致"包含"一个地址。指针类型的表达式可以生成一个地址,例如
ISO C115.5.2.3
3 The unary & operator yields the address of its operand.
注:该措词由WG14/N1256引入,即ISO C99:TC3。在C99中有
3 The unary & operator returns the address of its operand.
它反映了委员会的意见:地址不是一元&运算符返回的指针值。
尽管有上述措辞,但即使在标准中也存在一些混乱。
ISO C11 6.6
9 An address constant is a null pointer, a pointer to an lvalue designating an object of static
storage duration, or a pointer to a function designator
ISO C++ 11 5.19
3 ... An address
constant expression is a prvalue core constant expression of pointer type that evaluates to the address of an
object with static storage duration, to the address of a function, or to a null pointer value, or a prvalue core
constant expression of type std::nullptr_t. ...
(最近的C++标准草案使用了另一个措辞,所以没有这个问题。)
实际上,C中的"地址常量"和C++中的"地址常量表达式"都是指针类型的常量表达式(或者至少是C++ 11中的"指针类型")。
在C和C++中,构造一元EDCOX1〔7〕算子称为"地址";同样地,在C++ 11中引入EDCOX1〔9〕。
这些命名可能会带来误解。结果表达式是指针类型的,因此可以解释为:结果包含/生成地址,而不是地址。
型
在理解指针之前,我们需要理解对象。对象是存在的实体,具有一个名为地址的位置说明符。指针只是一个变量,与C中其他任何变量一样,它的类型为pointer,其内容被解释为支持以下操作的对象的地址。
1 2 3 4 5 6
| + : 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),它将对象的位置说明符(address)检索为指针对象类型。这应该减少围绕术语的混淆,因为调用&作为对象的操作而不是其结果类型是对象类型指针的指针是有意义的。
注:在这个解释中,我遗漏了记忆的概念。
- 我喜欢你对一般系统中一般指针的抽象现实的解释。但是,也许讨论记忆会有帮助。事实上,就我自己而言,我知道这会…!我认为讨论这种联系对于理解全局是非常有帮助的。+ 1无论如何:
- @D0RMLIFE:在其他涵盖全局的答案中,你有足够的解释。我只是想给出一个数学抽象的解释作为另一种观点。另外,如果将&称为'address of'(地址为'as),则会产生较少的混乱,因为后者更依赖于对象,而不是指针本身。`
- 没有冒犯,但我会自己决定什么是充分的解释。一本教科书不足以充分解释数据结构和内存分配。;)…不管怎样,你的回答仍然是有帮助的,即使它不是新颖的。
- 没有内存概念处理指针是没有意义的。如果对象存在时没有内存,那么它必须在一个没有地址的地方——例如在寄存器中。要使用"amp;",必须先假定内存。
指针只是另一个变量,通常可以包含另一个变量的内存地址。指针也是一个变量,它也有一个内存地址。
- 不一定是地址。顺便问一句,在发布答案之前,您是否阅读了现有的答案和评论?
C指针与内存地址非常相似,但是它抽象出了与机器相关的细节,以及一些在低级指令集中找不到的特性。
例如,C指针的类型相对丰富。如果您通过一个结构数组增加一个指针,它会很好地从一个结构跳到另一个结构。
指针受转换规则的约束,并提供编译时类型检查。
有一个特殊的"空指针"值,在源代码级别是可移植的,但其表示形式可能不同。如果将值为零的整型常量赋给指针,则该指针将接受空指针值。如果用这种方式初始化一个指针,也是一样。
指针可以用作布尔变量:如果它不是空值,则测试为真;如果它是空值,则测试为假。
在机器语言中,如果空指针是一个有趣的地址,比如0xffffffff,那么您可能需要对该值进行显式测试。C对你隐瞒。即使空指针是0xffffffff,也可以使用if (ptr != 0) { /* not null! */}测试它。
使用破坏类型系统的指针会导致未定义的行为,而机器语言中的类似代码可能定义得很好。汇编程序将汇编您编写的指令,但C编译器将根据您没有做错任何事情的假设进行优化。如果一个float *p指针指向一个long n变量,并且执行*p = 0.0时,编译器不需要处理这个问题。随后使用n将不需要读取浮点值的位模式,但可能是一种基于"严格混叠"假设的优化访问,即n未被触摸!也就是说,假设程序运行良好,因此p不应指向n。
在C语言中,指向代码的指针和指向数据的指针是不同的,但是在许多体系结构中,地址是相同的。可以开发具有"fat"指针的C编译器,即使目标体系结构没有。胖指针意味着指针不仅仅是机器地址,还包含其他信息,例如关于被指向对象大小的信息,用于边界检查。可移植的程序将很容易移植到这样的编译器。
所以你可以看到,机器地址和C指针之间有很多语义上的区别。
- 空指针并不像你想象的那样在所有平台上工作-请看我上面对ciscoiphone的回复。空==0是一个假设,它只适用于基于x86的平台。传统认为新平台应该与x86相匹配,但特别是在嵌入式世界,情况并非如此。编辑:同样,C没有从硬件中抽象指针方式的值-"ptr!=0"不能作为空测试在空平台上工作!= 0。
- dx-mon,这对于标准C来说是完全错误的。空值被定义为0,它们可以在语句中互换使用。硬件中的空指针表示是否都是0位与源代码中的空指针表示方式无关。
- @我恐怕你没有掌握正确的事实。在C语言中,无论空指针是否为空地址,整型常量表达式都充当空指针常量。如果您知道一个c编译器,其中ptr != 0不是空测试,请公开它的身份(但在这样做之前,请向供应商发送一份错误报告)。
- 我知道你在说什么,但是你对空指针的评论是不连贯的,因为你混淆了指针和内存地址——正是问题中引用的引号建议避免的!正确的语句:c将空指针定义为零,而不管偏移量为零的内存地址是否合法。
- "如果float*p指针指向一个long n变量,并且执行了*p=0.0,则不需要编译器来处理这个问题。"您的意思是什么?别名问题源于(非全知)优化;它们与指向变量的类型或实际的地址指针关系无关。
- 型@请给我亚历克西斯的章节。C没有将空指针定义为零。C将零(或值为零的任何整型常量表达式)定义为表示空指针常量的语法。faqs.org/faqs/c-faq/faq(第5节)。
- 型@Alexis是的,别名是通过以下方式连接到类型的。C禁止使用类型为punned aliasing的大多数实例,这允许进行积极的优化。如果一个类型为int的对象被分配给,编译器不必担心分配可能修改任何类型为double的对象(这种情况只能通过某种别名发生)。当然,当类型相同时,也可以进行别名,例如,别名数组。这里有不同的问题,但是相关的。
- 型@卡兹,你说得对,我的公式过于简单化了:它不是关于空指针"是什么",意思是它的内部存储表示。你可以把一个空指针设置为零,也就是空指针常数,它比较等于零,就C而言,它是零——实际上,我认为你的答案几乎是相同的,但从另一个角度来看。(换言之,我不是说你实际上是错的,但我会得出一个非常不同的区别)。
- 型@Alexis空指针不等于零。它比较等于一个空指针常量,该常量在编译时只由一个整数零表示。空指针不等于值为零的int类型变量。这种比较需要诊断。所以,就C而言,空指针不是零。
- 型@那么,在这种情况下,你会如何在嵌入式系统上取消引用内存地址0?是否将long设置为0转换为指针?我想你会发现,如果你在GCC的xilinx版本中尝试任何一种funk,空值将被设置为对平台更为敏感的值,比如0xffffffff,因为它通常是一个未使用的,因此在微数据库的处理器内存地址映射中是无效的地址。由于中断向量系统的存在,内存地址零实际上在微博客上是非常重要的,不能访问它听起来像是一个即时的"脚中枪"。
- 型@dx-mon取消引用内存地址0在可移植C之外。通常情况下,您会这样做:TYPE *ptr = (TYPE *) 0,然后是*ptr。这取决于与地址零相对应的空指针。但是,这种依赖关系对于您的嵌入式系统是正确的。无论如何,代码只能移植到那个系统,所以一切都很酷。如果(TYPE *) 0生成了地址0x7FFFFFFF或其他地址,那么您将处于一个不同的系统中,在该系统中有一个解决方法,比如使用一个非常数零:int zero = 0; TYPE *p = (TYPE *) zero。
它说"因为它混淆了那些不知道地址是关于什么的人"——而且,它是真的:如果你知道地址是关于什么的,你就不会混淆。从理论上讲,指针是指向另一个变量的变量,实际上拥有一个地址,即它指向的变量的地址。我不知道为什么要隐瞒这个事实,这不是火箭科学。如果你理解指针,你将更进一步了解计算机是如何工作的。前进!
型
来想想,我认为这是语义学的问题。我不认为作者是对的,因为C标准将指针引用为保存引用对象的地址,正如其他人在这里已经提到的那样。但是,地址!=内存地址。根据C标准,地址实际上可以是任何东西,尽管它最终会导致一个内存地址,指针本身可以是一个ID,一个偏移量+选择器(x86),只要它能够描述(映射之后)可寻址空间中的任何内存地址,就可以是任何东西。
- 指针保存地址(如果为空,则不保存)。但这与它的地址大不相同:例如,在许多情况下,指向同一地址但具有不同类型的两个指针是不等效的。
- @Gilles如果你看到"存在",比如int i=5->i是5,那么指针就是地址yes。而且,空值也有一个地址。通常是一个无效的写地址(但不一定,请参见x86实模式),但地址也同样无效。对于空值实际上只有两个要求:保证比较不等于指向实际对象的指针,并且任何两个空指针比较都相等。
- 相反,空指针保证不等于任何对象的地址。取消对空指针的引用是未定义的行为。说"指针就是地址"的一个大问题是它们的工作方式不同。如果p是指针,那么p+1并不总是地址增加1。
- 请再次阅读评论,it's guaranteed to compare unequal to a pointer to an actual object。至于指针算法,我看不到点,指针的值仍然是一个地址,即使"+"操作不一定要加一个字节。
另一种方法是C或C++指针与简单的内存地址不同,这是因为我在其他答案中没有看到不同的指针类型(AltHurf,考虑到它们的总大小,我可能忽略了它)。但它可能是最重要的一个,因为即使是有经验的C/C++程序员也可以跳过它:
编译器可能会假定不兼容类型的指针不会指向同一个地址,即使它们确实指向同一个地址,这可能会给出简单的指针==地址模型无法实现的行为。考虑以下代码(假设sizeof(int) = 2*sizeof(short)):
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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*操纵值是可能的(尽管不是非常可移植的)。
快速摘要:C地址是一个值,通常表示为具有特定类型的机器级内存地址。
非限定词"指针"不明确。C有指针对象(变量)、指针类型、指针表达式和指针值。
用"指针"这个词来表示"指针对象"是很常见的,这会导致一些混乱——这就是为什么我试图用"指针"作为形容词而不是名词的原因。
至少在某些情况下,C标准使用"指针"一词来表示"指针值"。例如,malloc的描述说它"返回一个空指针或一个指向分配空间的指针"。
那么C中的地址是什么?它是一个指针值,即某个特定指针类型的值。(除了空指针值不一定被称为"地址",因为它不是任何内容的地址)。
标准对一元&运算符的描述是"生成操作数的地址"。在C标准之外,"地址"一词通常用于指(物理或虚拟)内存地址,通常是一个大小的字(无论给定系统上的"字"是什么)。
C"地址"通常作为机器地址实现——就像C int值通常作为机器字实现一样。但是C地址(指针值)不仅仅是一个机器地址。它是一个通常表示为机器地址的值,它是一个具有特定类型的值。
指针值是地址。指针变量是可以存储地址的对象。这是真的,因为这就是标准定义的指针。告诉C新手很重要,因为C新手通常不清楚指针和它指向的东西之间的区别(也就是说,他们不知道信封和建筑物之间的区别)。地址的概念(每个对象都有一个地址,这就是指针存储的内容)很重要,因为它对地址进行排序。
然而,标准在特定的抽象层次上进行讨论。作者谈到的那些人"知道地址是关于什么的",但是对于C来说是新手,他们一定是在不同的抽象层次上学习了地址——也许是通过编程汇编语言。不保证C实现使用与CPU操作码使用相同的地址表示(在本文中称为"存储地址"),这些人已经知道。
他接着谈到"完全合理的地址操纵"。就C标准而言,基本上没有"完全合理的地址操作"。加法是在指针上定义的,基本上就是它。当然,您可以将指针转换为整数,执行一些按位或算术运算,然后再将其转换回来。标准并不能保证这一点,所以在编写代码之前,您最好了解特定的C实现如何表示指针并执行转换。它可能使用了您期望的地址表示,但这不是您的错,因为您没有阅读手册。这不是混淆,这是不正确的编程过程;-)
简而言之,C使用的地址概念比作者更抽象。
作者的地址概念当然也不是这个问题的最低级词汇。对于跨多个芯片的虚拟内存映射和物理RAM寻址,您告诉CPU的数字是您想要访问的"存储地址",基本上与您想要的数据在硬件中的实际位置无关。这是所有间接和代表层,但作者选择了一个特权。如果你要在谈论C的时候这样做,选择C级别作为特权!
就我个人而言,我认为作者的评论没有那么有帮助,除了在将C介绍给汇编程序员的上下文中。对于那些来自更高级别语言的人来说,指针值不是地址当然没有帮助。承认这种复杂性要比说CPU在说地址是什么方面具有垄断性要好得多,因此C指针值"不是"地址。它们是地址,但它们可以用不同于他所指的地址的语言书写。我认为,在C的上下文中将这两个词区分为"地址"和"商店地址"就足够了。
简单地说,指针实际上是分割机制的偏移部分,分割后转换为线性地址,然后在分页后转换为物理地址。物理地址实际上是从RAM中寻址的。
1 2 3 4 5 6 7
| Selector +--------------+ +-----------+
---------->| | | |
| Segmentation | ------->| Paging |
Offset | Mechanism | | Mechanism |
---------->| | | |
+--------------+ +-----------+
Virtual Linear Physical |