【问题标题】:Real thing about "->" and "."关于“->”和“.”的真实情况
【发布时间】:2010-05-26 19:10:38
【问题描述】:

我一直想知道编译器如何看待指向结构的指针(假设在 C 中)和结构本身的真正区别是什么。

struct person p;
struct person *pp;

pp->age,我总是想象编译器会这样做:“结构中 pp 的值 + 属性“年龄”的偏移量”。

但是person.p 的作用是什么?这几乎是一样的。对我“程序员”来说, p 不是内存地址,它就像“结构本身”,但当然这不是编译器处理它的方式。

我的猜测是它更多的是语法上的东西,编译器总是做(&p)->age

我说的对吗?

【问题讨论】:

  • 不是为了劫持这个问题,但作为一名学生,我很好奇在哪个领域可以了解这类事情。编译器设计? (我的意思是,显然我学到了 (*p).q 与我的 c++ 类中的 p->q 相同)
  • 通常是低级计算机系统课程(处理内存分配、堆栈帧等的那种)。
  • 谢谢 - 我才大二,所以我相信我们最终会到达那里
  • +1 有趣/好问题。
  • 几种语言在两种情况下都使用一种语法。有人知道为什么 C 不知道吗?

标签: c compiler-construction struct


【解决方案1】:

p->q 本质上是(*p).q 的语法糖,因为它取消引用指针p,然后转到其中的正确字段q。它为一个非常常见的情况(指向结构的指针)节省了输入。

本质上,-> 做了 两个 引用(指针取消引用,字段取消引用),而 . 只做了一个(字段取消引用)。

由于多重解引用的因素,-> 不能被编译器完全替换为静态地址,并且总是至少包含地址计算(指针可以在运行时动态改变,因此位置也会改变) ,而在某些情况下,. 操作可以被编译器替换为访问固定地址(因为基结构的地址也可以固定)。

【讨论】:

  • 好的。但我的问题是编译器的“p”到底是什么?它也是一个地址,不是吗?但对我来说,程序员就是“结构本身”
  • 假设p 是一个局部变量,它是堆栈中的一个偏移量。
  • 只是不能同意称它为“糖”之类的东西,它一直是 C 语言中最让我烦恼的语法!会称之为“捷径”,一个丑陋的.. +1 很好的解释
  • pp 是“存储变量 pp 的内存中包含的值”。 *pp 是“包含在某个其他内存中的值,该值从存储变量 pp 的内存中包含的地址开始”
  • @Fabiano PS:“句法糖”是一个非常标准的术语(Dijkstra 说它会导致“分号癌”)。请记住,有些人不喜欢糖(你真的更喜欢(*p). 而不是p->?)。
【解决方案2】:

更新(见 cmets):

您的想法是正确的,但是仅针对全局变量和静态变量有一个重要区别:当编译器看到p.age 表示全局或静态变量时,它可以在编译时替换它时间,带有结构中age 字段的确切地址。

相比之下,pp->age必须编译为“pp 的值 + age 字段的偏移量”,因为 pp 的值可以在运行时改变。

【讨论】:

  • 如果在堆栈上声明它在编译时不知道确切的地址,只知道堆栈指针的偏移量。
  • 当它看到 p.age 时,它​​知道结构内的 offset,但不知道(除非它是一个全局变量)结构的绝对地址。跨度>
  • 致所有投反对票的人:谢谢!我以前从未见过:)
【解决方案3】:

即使从“编译器的角度”来看,这两个语句也不等价。语句p.age 转换为p 的地址+ age 的偏移量,而pp->age 转换为pp 中包含的地址 + age 的偏移量。

地址变量和地址包含在(指针)变量是非常不同的东西。

假设年龄的偏移量是5。如果p是一个结构,它的地址可能是100,所以p.age引用地址105。

但是如果pp是一个指向结构的指针,它的地址可能是100,但是存储在地址100的值不是person结构的开头,它是一个指针。因此,地址 100 处的值(包含在pp 中的地址)可能是例如 250。在这种情况下,pp->age 引用地址 255,而不是 105。

【讨论】:

  • 换句话说,从p.age 读取概念上需要从p 的(已知)位置加上age 的偏移量读取一次内存。而从 pp->age 读取在概念上需要两次内存读取 - 一次从 pp 的位置读取,然后从第一次读取给出的位置加上 age 的偏移量进行第二次读取。
【解决方案4】:

由于 p 是一个局部(自动)变量,它存储在堆栈中。因此,编译器根据堆栈指针 (SP) 或帧指针(FP 或 BP,在其存在的架构中)的偏移量来访问它。相比之下,*p 指的是[通常]在堆中分配的内存地址,因此不使用堆栈寄存器。

【讨论】:

  • 您可以通过地址将堆栈分配的结构传递给子程序:在这种情况下,子程序接收到的指针指向堆栈上的对象。
  • @ChrisW:好点子。当然,编译器仍然将结构作为直接地址访问(不使用 SP)。
【解决方案5】:

这是我一直问自己的一个问题。

v.xmember 运算符,only 对结构有效。 v->x指针成员运算符,对结构指针有效。

既然只需要一个,为什么还要有两个不同的运算符呢?例如,只能使用. 运算符;编译器总是知道v的类型,所以它知道该怎么做:v.x如果v是一个结构,(*v).x如果v是一个结构指针。

我有三个理论:

  • K&R 的暂时性短视(我希望这是错误的理论)
  • 使编译器的工作更轻松(考虑到 C 的构思时间,这是一个实用的理论:)
  • 可读性(我更喜欢哪种理论)

很遗憾,我不知道哪一个(如果有的话)是真的。

【讨论】:

    【解决方案6】:

    在这两种情况下,结构及其成员都由

    寻址

    地址(人)+偏移(年龄)

    将 p 与存储在堆栈内存中的结构一起使用可为编译器提供更多选项来优化内存使用。如果没有使用其他任何东西,它只能存储年龄,而不是整个结构 - 这使得使用上述函数进行寻址失败(我认为读取结构的地址会停止这种优化)。
    堆栈上的结构可能根本没有内存地址。如果结构足够小并且只存在很短的时间,则可以将其映射到某些处理器寄存器(与上述读取地址的优化相同)。

    简短的回答:当编译器不优化时,你是对的。只要编译器开始优化,就只保证 c 标准指定的内容

    编辑:删除了“pp->”的有缺陷的堆栈/堆位置,因为指向的结构可以在堆和堆栈上。

    【讨论】:

    • 变量的位置与用于访问它的寻址模式无关。使用pp->,结构可以是任何地方,堆、堆栈、“全局”。使用p.,除了堆内存之外,结构也可以在上述任何一种中(因为你不能直接在那里声明变量)。我总是喜欢把指针想象成箭头,指向内存单元的数组。以类比为例,这似乎很容易,而且没有那么大的错误。
    • @Donal Fellows 你说得对“pp->”我只考虑了最简单的用例。如果年龄是一个结构,你可以用 pp->age.days 来解决它,所以“。”对内存位置也没有多说。
    • 确实如此。区别与位置无关,而与您是否拥有事物的名称(即指向它的指针)或事物本身(存储单元/结构/数组/……)有关;在康德哲学中sich”)。
    猜你喜欢
    • 2016-06-27
    • 2011-03-30
    • 2015-12-03
    • 2019-08-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-25
    • 2012-03-12
    相关资源
    最近更新 更多