Я интерпретирую ваш вопрос как два вопроса: 1) почему ->вообще существует, и 2) почему .автоматически не разыменовывается указатель. Ответы на оба вопроса имеют исторические корни.
Почему вообще ->существует?
В одной из самых первых версий языка Си (которую я буду называть CRM для « Справочного руководства по Си », которая вышла с 6-м изданием Unix в мае 1975 года) оператор ->имел очень исключительное значение, а не синоним *и .комбинацию
Язык Си, описанный CRM, во многих отношениях сильно отличался от современного языка Си. В структуре CRM члены реализовали глобальную концепцию смещения байтов , которая может быть добавлена к любому значению адреса без ограничений типа. Т.е. все имена всех членов структуры имели независимое глобальное значение (и, следовательно, должны были быть уникальными). Например, вы могли бы объявить
struct S {
int a;
int b;
};
и name aбудет означать смещение 0, в то время как name bбудет означать смещение 2 (при условии, что intразмер размера 2 и заполнение отсутствует). Язык требовал, чтобы все члены всех структур в единице перевода либо имели уникальные имена, либо обозначали одно и то же значение смещения. Например, в той же единице перевода вы могли бы дополнительно объявить
struct X {
int a;
int x;
};
и это было бы хорошо, так как имя aбудет последовательно означать смещение 0. Но это дополнительное объявление
struct Y {
int b;
int a;
};
будет формально недействительным, поскольку он попытался «переопределить» 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 */
Первое назначение было истолковано компилятором как «принимать адрес 5, добавить смещение 2к нему и назначить 42к intзначению по полученному адресу». Т.е. выше будет назначить 42на intзначение по адресу 7. Обратите внимание, что это использование ->не заботилось о типе выражения в левой части. Левая часть интерпретировалась как числовой адрес rvalue (будь то указатель или целое число).
Этот вид обмана не было возможно с *и .комбинации. Вы не могли сделать
(*i).b = 42;
поскольку *iэто уже недопустимое выражение. *Оператор, так как она отделена от .налагает более строгие требования типа на его операнда. Для обеспечения возможности обойти это ограничение в CRM введен ->оператор, который не зависит от типа левого операнда.
Как отметил Кит в комментариях, это различие между комбинацией « +» ->и « CRM» означает то, что CRM называет «ослаблением требования» в 7.1.8: За исключением ослабления требования, относящегося к типу указателя, выражение в точности эквивалентно*.E1E1−>MOS(*E1).MOS
Позже в K & R C многие функции, первоначально описанные в CRM, были значительно переработаны. Идея «члена структуры как глобального идентификатора смещения» была полностью удалена. И функциональность ->оператора стала полностью идентична функциональности *и .комбинации.
Почему нельзя .разыменовать указатель автоматически?
Опять же, в CRM-версии языка левый операнд .оператора должен был быть lvalue . Это было единственное требование к этому операнду (и именно это отличало его от ->описанного выше). Обратите внимание, что CRM не требует, чтобы левый операнд .имел тип struct. Это просто требовало, чтобы это было lvalue, любое lvalue. Это означает, что в CRM-версии C вы можете написать такой код
struct S { int a, b; };
struct T { float x, y, z; };
struct T c;
c.b = 55;
В этом случае компилятор записывает 55в intзначение, расположенное со смещением в 2 байта в непрерывном блоке памяти, известном как c, даже если тип struct Tне имеет названного поля b. Компилятор не будет заботиться о фактическом типе cвообще. Все, о чем он заботился, это что- cто вроде lvalue: какой-то блок памяти с возможностью записи.
Теперь обратите внимание, что если вы сделали это
S *s;
...
s.b = 42;
код будет считаться действительным (так как sэто также именующее) и компилятор просто попытка записи данных в указатель sсам , в байтовое смещение 2. Излишне говорить, что такие вещи , как это легко может привести к перерасходу памяти, но язык не занимался такими вопросами.
Т.е. в этой версии языка предложенная вами идея об операторе перегрузки .для типов указателей не будет работать: оператор .уже имеет очень специфическое значение при использовании с указателями (с указателями lvalue или вообще с любыми lvalue). Это была очень странная функциональность, без сомнения. Но это было там в то время.
Конечно, эта странная функциональность не очень веская причина против введения перегруженного .оператора для указателей (как вы предложили) в переработанной версии C - K & R C. Но это не было сделано. Возможно, в то время в CRM-версии C был написан какой-то устаревший код, который нужно было поддерживать.
(URL-адрес Справочного руководства C 1975 года может быть нестабильным. Другая копия, возможно, с некоторыми незначительными различиями, находится здесь .)