Я розтлумачу ваше запитання як два запитання: 1) чому це ->взагалі існує, і 2) чому .автоматично не відтягує вказівник. Відповіді на обидва запитання мають історичне коріння.
Чому ->навіть існує?
В одній із найперших версій мови C (яку я буду називати CRM для " Довідкового посібника C ", що вийшла з 6-м виданням Unix у травні 1975 року) оператор ->мав дуже ексклюзивне значення, не синонім *і .комбінацію
Мова C, описана CRM, багато в чому відрізнялася від сучасної C. Члени структури CRM реалізували глобальну концепцію зміщення байтів , яку можна було б додати до будь-якої адреси адреси без обмежень типу. Тобто всі імена всіх членів структури мали незалежне глобальне значення (і, отже, повинні були бути унікальними). Наприклад, ви можете заявити
struct S {
int a;
int b;
};
а ім'я aозначатиме зміщення 0, тоді як ім'я 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. Зауважте, що це використання ->не стосувалося типу виразу зліва. Ліва частина інтерпретується як числова адреса рецензії (будь то вказівник або ціле число).
Цей вид обману не було можливо з *і .комбінації. Ти не міг цього зробити
(*i).b = 42;
оскільки *iце вже недійсний вираз. *Оператор, так як вона відокремлена від .накладає більш суворі вимоги типу на його операнда. Для забезпечення можливості обійти це обмеження CRM представив ->оператора, який не залежить від типу лівого операнда.
Як зазначав Кіт у коментарях, ця різниця між комбінацією ->та *+ .- це те, що CRM посилається на "послаблення вимоги" в 7.1.8: За винятком послаблення вимоги, що E1має тип вказівника, вираз E1−>MOSточно еквівалентний(*E1).MOS
Пізніше в K&R C багато функцій, спочатку описаних у CRM, були значно перероблені. Ідея "члена структури як глобального ідентифікатора зміщення" була повністю вилучена. А функціональність ->оператора стала повністю ідентичною функціональності *та .комбінації.
Чому не можна .автоматично перенаправити покажчик?
Знову ж таки, у CRM-версії мови лівий операнд .оператора повинен був бути значенням . Це була єдина вимога, що висувалася до цього операнда (і це відрізняло його від ->, як було пояснено вище). Зауважте, що CRM не вимагає, щоб лівий операнд .має тип структура. Це просто вимагало, щоб це було значення, будь-яке значення. Це означає, що у 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було значення: якийсь блок пам'яті, що можна записати.
Тепер зауважте, що якщо ви це зробили
S *s;
...
s.b = 42;
код буде вважатися дійсним (так як sце також іменує) і компілятор просто спроба запису даних в покажчик sсам , в байтовое зміщення 2. Зайве говорити, що такі речі , як це легко може привести до перевитрати пам'яті, але мова не стосувався себе подібними питаннями.
Тобто в цій версії мови запропонована вами ідея щодо оператора перевантаження .для типів вказівника не спрацює: оператор .вже мав дуже специфічне значення при використанні вказівників (з покажчиками lvalue або взагалі з будь-якими значеннями). Це був дуже дивний функціонал, без сумніву. Але це було в той час.
Звичайно, ця дивна функціональність не є дуже вагомою причиною проти введення перевантаженого .оператора для покажчиків (як ви запропонували) у переробленій версії C - K&R C. Але цього не було зроблено. Можливо, на той час був якийсь спадковий код, написаний у CRM-версії C, який потрібно було підтримати.
(URL-адреса довідкового посібника 1975 р. Може бути нестійкою. Ще одна копія, можливо, з деякими тонкими відмінностями, є тут .)