C语言中“.”与“->”有什么区别?

知乎 转载

转载几篇知乎上我自己的回答,因为不喜欢知乎的排版,所以在博客里重新排版一遍。

原问题:C语言中“.”与“->”有什么区别?

除了表达形式有些不同,功能可以说完全一样阿。那为何又要构造两个功能一样的运算符? 效率有差异?可是现在编译器优化都那么强了,如果真是这样岂不是有些多此一举


刚刚翻了下书,说早期的C实现无法用结构直接当作参数在函数间传递,只能用指向结构的指针在函数间进行传递!我想这应该也是最直观的原因吧。

我的回答

首先 a->b 的含义是 (*a).b ,所以他们是不同的,不过的确 -> 可以用 * . 实现,不需要单独一个运算符。 嗯,我这是说现代的标准化的 C 语义上来说, -> 可以用 * . 的组合实现。

早期的 C 有一段时间的语义和现代的 C 的语义不太一样。

稍微有点汇编的基础的同学可能知道,在机器码和汇编的角度来看,不存在变量,不存在 struct 这种东西,只存在寄存器和一个叫做内存的大数组。

所以变量,是 C 对内存地址的一个抽象,它代表了一个位置。举个例子,C 里面我们写:

a = b

其实在汇编的角度来看更像是

*A = *B

其中 A 和 B 各是两个内存地址,是指针。

好,以上是基本背景。

基于这个背景我们讨论一下 struct 是什么,以及 struct 的成员是什么。 假设我们有

struct Point {
        int x;
        int y;
};
struct Point p;
struct Point *pp = &p;

从现代语义上讲 p 就是一个结构体对象, x y 各是其成员,嗯。

从汇编的语义上讲, p 是一个不完整的地址,或者说,半个地址,再或者说,一个指向的东西是虚构出来的地址。而 x y 各是在 Point 结构中的地址偏移量。也就是说,必须有 p x 或者 p y 同时出现,才形成一个完整的地址,单独的一个 p 没有意义。

早期的 C 就是在这样的模型上建立的。所以对早期的 C 而言, *pp 没有意义,你取得了一个 struct ,而这个 struct 不能塞在任何一个寄存器里,编译器和 CPU 都无法表达这个东西。

这时候只有 p.x p.y 有意义,它们有真实的地址。

早期的 C 就是这样一个看起来怪异的语义,而它更贴近机器的表达。 所以对早期的 C 而言,以下的代码是对的:

p.x = 1;
int *a;
a = &(p.x);

而以下代码是错的:

(*pp).x = 1;

因为作为这个赋值的目标地址表达式的一部分, *pp ,这个中间结果没法直译到机器码。

所以对早期的 C 而言,对 pp 解引用的操作,必须和取成员的偏移的操作,这两者紧密结合起来变成一个单独的操作,其结果才有意义。

所以早期的 C 就发明了 -> ,表示这两个操作紧密结合的操作。于是才能写:

pp->x = 1;

嗯,这就是它存在的历史原因。 而这个历史原因现在已经不重要了,现代的符合标准的 C 编译器都知道 (*pp).x pp->x 是等价的了。

说句题外话, C++ 里面还发明了 .* ->* 这两个运算符(注意 ->* 不是单独的 -> * 并排放的意思),关于为什么要发明这两个运算符,而不能直接说 a ->* b 的意思就是 a ->(*b) ,这个就作为课堂作业吧。

请用你的 GitHub 账户登录并在这篇文章的 Issue 页下留言
comments powered by Disqus