Ask*_*aga 253 c pointers dereference
dot(.)运算符用于访问结构的成员,而->C中的箭头运算符()用于访问由有问题的指针引用的结构的成员.
指针本身没有任何可以使用点运算符访问的成员(它实际上只是一个描述虚拟内存中位置的数字,因此它没有任何成员).因此,如果我们只是将点运算符定义为在指针上使用指针(编译时afaik编译器已知的信息)时自动取消引用指针,则不会产生歧义.
那么为什么语言创建者决定通过添加这个看似不必要的运算符来使事情变得更复杂?什么是重大的设计决定?
AnT*_*AnT 346
我将你的问题解释为两个问题:1)为什么->甚至存在,2)为什么.不自动取消引用指针.这两个问题的答案都有历史根源.
为什么->甚至存在?
在C语言的最初版本之一(我将其称为" C参考手册 "的CRM ,它与1975年5月的第6版Unix一起提供)中,运算符->具有非常独特的含义,而不是同义词*和.组合
CRM描述的C语言在很多方面与现代C语言截然不同.在CRM结构中,成员实现了字节偏移的全局概念,可以将其添加到任何地址值,而不受类型限制.即所有结构成员的所有名称都具有独立的全局含义(因此,必须是唯一的).例如,您可以声明
struct S {
int a;
int b;
};
Run Code Online (Sandbox Code Playgroud)
并且name a代表偏移量0,而name b代表偏移量2(假设int类型大小为2且没有填充).语言要求翻译单元中所有结构的所有成员都具有唯一的名称或代表相同的偏移值.例如,在同一个翻译单元中,您可以另外声明
struct X {
int a;
int x;
};
Run Code Online (Sandbox Code Playgroud)
那没关系,因为这个名字a一直代表偏移0.但是这个额外的声明
struct Y {
int b;
int a;
};
Run Code Online (Sandbox Code Playgroud)
将正式无效,因为它试图"重新定义" a为偏移量2和b偏移量0.
这就是->运算符的用武之地.由于每个结构成员名称都有自己的自足全局含义,因此语言支持这些表达式
int i = 5;
i->b = 42; /* Write 42 into `int` at address 7 */
100->a = 0; /* Write 0 into `int` at address 100 */
Run Code Online (Sandbox Code Playgroud)
第一分配是由编译器解释为"取地址5,偏移添加2到它并分配42给int在所得到的地址值".即上面将分配42给int地址的值7.请注意,这种使用->并不关心左侧表达式的类型.左侧被解释为右值数字地址(无论是指针还是整数).
这种欺骗是不可能*和.组合.你做不到
(*i).b = 42;
Run Code Online (Sandbox Code Playgroud)
因为*i已经是一个无效的表达.由于*运算符是独立的.,因此对其操作数施加了更严格的类型要求.为了提供解决此限制的能力,CRM引入了->运算符,该运算符独立于左侧操作数的类型.
正如Keith在评论中指出的那样,->和*+ .组合之间的区别是CRM在7.1.8中所指的"放宽要求":除了放宽E1指针类型的要求外,表达式E1?>MOS完全等同于(*E1).MOS
后来,在K&R C中,最初在CRM中描述的许多功能都进行了重大修改.完全删除了"struct member as global offset identifier"的想法.功能,和->运营商成为了的功能完全一致*和.组合.
为什么不能.自动取消引用指针?
同样,在该语言的CRM版本中,.运算符的左操作数必须是左值.这是对该操作数施加的唯一要求(->正如上面所解释的那样,这使得它与之不同).请注意,CRM 不要求左操作数.具有结构类型.它只需要它是一个左值,任何左值.这意味着在C的CRM版本中,您可以编写这样的代码
struct S { int a, b; };
struct T { float x, y, z; };
struct T c;
c.b = 55;
Run Code Online (Sandbox Code Playgroud)
在这种情况下,即使类型没有命名字段,编译器也会在连续内存块中写入位于字节偏移量2 55的int值.编译器根本不关心实际的类型.所有它关心的是这是一个左值:某种可写的内存块.cstruct Tbcc
现在请注意,如果你这样做了
S *s;
...
s.b = 42;
Run Code Online (Sandbox Code Playgroud)
代码被认为是有效的(因为s它也是一个左值),编译器只是尝试将数据写入指针s本身,在字节偏移量2.不用说,这样的事情很容易导致内存溢出,但语言并不关心这些事情.
即在该语言版本中,您提出的关于重载操作符.指针类型的提议不起作用:.当与指针一起使用时,操作符已经具有非常特定的含义(使用左值指针或任何左值).毫无疑问,这是非常奇怪的功能.但它当时就在那里.
当然,这个奇怪的功能并不是一个非常强大的理由来反对.在C-K&R C的返工版本中引入指针的重载操作符(如你所建议的那样)但是它还没有完成.也许当时有一些必须支持的CRM版C编写的遗留代码.
(1975 C参考手册的URL可能不稳定.另一个副本,可能有一些细微差别,在这里.)
eff*_*ffe 41
除了历史(好的和已经报告的)原因之外,运算符优先级也有一点问题:点运算符比star运算符具有更高的优先级,所以如果你有包含struct的指针包含指向struct的指针...这两个是等价的:
(*(*(*a).b).c).d
a->b->c->d
Run Code Online (Sandbox Code Playgroud)
但第二个显然更具可读性.箭头运算符具有最高优先级(就像点一样)并且从左到右关联.我认为这比使用点运算符更容易指向struct和struct,因为我们知道表达式中的类型而不必查看声明,甚至可能在另一个文件中.
muk*_*nda 19
C也做得很好,没有做出任何模棱两可的事情.
当然,点可以重载以表示两者,但是箭头确保程序员知道他正在操作指针,就像编译器不会让你混合两个不兼容的类型一样.