Я розтлумачу ваше запитання як два запитання: 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 р. Може бути нестійкою. Ще одна копія, можливо, з деякими тонкими відмінностями, є тут .)