Чому синтаксис C для масивів, покажчиків та функцій був розроблений таким чином?


16

Побачивши (і задавши!) Стільки питань, схожих на

Що int (*f)(int (*a)[5])означає C?

і навіть побачивши, що вони створили програму, яка допоможе людям зрозуміти синтаксис С, я не можу не здивуватися:

Чому синтаксис С був розроблений таким чином?

Наприклад, якби я розробляв покажчики, я переклав би "вказівник на 10-елементний масив покажчиків" на

int*[10]* p;

і ні

int* (*p)[10];

Я вважаю, що більшість людей погодиться набагато простіше.

Тож мені цікаво, чому не-інтуїтивний синтаксис? Чи була певна проблема, яку вирішує синтаксис (можливо, двозначність?), Про яку я не знаю?


2
Ви знаєте, що немає справжньої відповіді на це та подібні запитання. Правильно? Що ви отримаєте - це лише здогадки.
BЈовић

7
@ VJo - цілком може бути "реальна" (тобто об'єктивна) відповідь - автори мови та комітети зі стандартів так само чітко виправдали (або принаймні пояснили) багато цих рішень.
detly

Я не думаю, що запропонований вами синтаксис обов'язково є більш-менш інтуїтивним, ніж синтаксис C. C - це те, що воно є; як тільки ви дізнаєтесь про це, у вас більше не виникне таких запитань. Якщо ви цього не навчилися ... ну, можливо, це справжня проблема.
Калеб

1
@Caleb: Смішно, як ви так легко зробили висновок, тому що я дізнався це, і у мене все-таки виникло це питання ...
користувач541686

1
cdeclКоманда дуже зручна для декодування складних декларацій C. На веб-сайті cdecl.org також є веб-інтерфейс .
Кіт Томпсон

Відповіді:


16

Я розумію історію цього в тому, що вона базується на двох основних моментах ...

По-перше, автори мови воліли зробити синтаксис змінним-орієнтованим, а не типовим. Тобто, вони хотіли, щоб програміст подивився на декларацію і подумав, "якщо я напишу вираз *func(arg), це призведе до" int; якщо я напишу, *arg[N]я матиму "float", а не ", funcповинен бути вказівником на функцію, що приймає це" і повернення того ".

Запис C у Вікіпедії стверджує, що:

Ідея Рітчі полягала в тому, щоб оголосити ідентифікатори в контекстах, що нагадують їх використання: "декларація відображає використання".

... посилаючись на p122 K&R2, які, на жаль, мені не потрібно подавати, щоб знайти розширену для вас пропозицію.

По-друге, насправді дуже важко придумати синтаксис декларування, який є послідовним, коли ви маєте справу з довільними рівнями непрямості. Ваш приклад може спрацювати для висловлення типу, який ви придумали там, де він працює, але чи він масштабує функцію, що приймає вказівник на масив цих типів і повертає якусь іншу неприємну безлад? (Може, і є, але ви перевірили? Чи можете ви це довести? ).

Пам’ятайте, частина успіху C пояснюється тим, що компілятори написані для багатьох різних платформ, і тому, можливо, було б краще проігнорувати певну ступінь читабельності для полегшення запису компіляторів.

Сказавши це, я не знавець мовної граматики чи написання укладачів. Але я знаю достатньо, щоб знати, що можна багато чого знати;)


2
"полегшення запису компіляторів" ... за винятком C, що важко розібратися (лише на C ++).
Ян Худек

1
@JanHudec - Ну ... так. Це не водонепроникне твердження. Але хоча C неможливо проаналізувати як без контекстну граматику, як тільки одна людина придумала спосіб її розбору, це перестає бути важким кроком. І справа в тому, що вона була плодотворною в перші дні через те, що люди змогли легко вирвати компілятори, тому K&R, мабуть, досяг певного балансу. (В фільмі Річард Габріель сумно Зліт «гірше , тим краще» , він приймає як належне - і нарікає. - той факт , що це легко написати компілятор для нової платформи)
detly

Я щасливий, що мене виправили з цього приводу - я не знаю все так багато про розбір і граматику. Я більше переходжу до висновку з історичного факту.
грудня 11

12

Багато диваків мови С можна пояснити тим, як працювали комп’ютери, коли він був розроблений. Об'єм пам'яті було дуже обмежений, тому було дуже важливо мінімізувати розмір самих файлів вихідного коду . Практика програмування ще в 70-х та 80-х роках полягала в тому, щоб вихідний код містив якомога менше символів, і бажано, щоб не було зайвих коментарів до вихідного коду.

Це, звичайно, смішно сьогодні, на жорстких дисках є досить багато необмеженого місця для зберігання. Але це є причиною того, що C має такий дивний синтаксис взагалі.


Що стосується конкретно вказівників масиву, то другим прикладом повинен бути int (*p)[10];(так, синтаксис дуже заплутаний). Я, можливо, прочитав би це як "int pointer to array of ten" ... що має сенс дещо. Якби не дужки, компілятор інтерпретував би це як масив з десяти покажчиків, що дало б декларації зовсім інше значення.

Оскільки вказівники масиву та покажчики функцій мають досить незрозумілий синтаксис на С, то розумне робити - це набрати непридатність. Можливо, так:

Неясний приклад:

int func (int (*arr_ptr)[10])
{
  return 0;
}

int main()
{
  int array[10];
  int (*arr_ptr)[10]  = &array;
  int (*func_ptr)(int(*)[10]) = &func;

  func_ptr(arr_ptr);
}

Неясний, еквівалентний приклад:

typedef int array_t[10];
typedef int (*funcptr_t)(array_t*);


int func (array_t* arr_ptr)
{
  return 0;
}

int main()
{
  int        array[10];
  array_t*   arr_ptr  = &array; /* non-obscure array pointer */
  funcptr_t  func_ptr = &func;  /* non-obscure function pointer */

  func_ptr(arr_ptr);
}

Речі можуть стати ще більш незрозумілими, якщо ви маєте справу з масивами функціональних покажчиків. Або найзрозуміліший з них: функції, що повертають покажчики функцій (м'яко корисні). Якщо ви не використовуєте typedefs для таких речей, ви швидко з’їдете з розуму.


Ага, нарешті розумна відповідь. :-) Мені цікаво, як конкретний синтаксис насправді зменшить розмір вихідного коду, але так чи інакше це правдоподібна ідея та має сенс. Спасибі. +1
користувач541686

Я б сказав, що це менше про розмір вихідного коду і більше про написання компілятора, але, безумовно, +1 для "typedef away the weirdness". Моє психічне здоров'я різко покращилося в той день, коли я зрозумів, що можу це зробити.
detly

2
[Цитування потрібне] у річці розміру вихідного коду. Я ніколи не чув про таке обмеження (хоча, можливо, це щось "всі знають").
Шон Макміллан

1
Добре, що я кодував програми в 70-х роках в COBOL, Assembler, CORAL і PL / 1 на IBM, DEC і XEROX, і я ніколи не стикався з обмеженням розміру вихідного коду. Обмеження щодо розміру масиву, розміру виконавчого файлу, розміру імені програми, але ніколи не розмір вихідного коду.
Джеймс Андерсон

1
@Sean McMillan: Я не думаю, що розмір вихідного коду був обмеженням (врахуйте, що в той час багатослівні мови, як Pascal, були досить популярними). І навіть якби це було так, я думаю, було б дуже легко проаналізувати вихідний код і замінити довгі ключові слова короткими однобайтовими кодами (як, наприклад, деякі базові інтерпретатори, що раніше). Тож я вважаю, що аргумент "C нетривалий, оскільки він був винайдений у період, коли було менше пам'яті", трохи слабким.
Джорджіо

7

Це досить просто: int *pозначає, що *pце int; int a[5]означає, що a[i]є int.

int (*f)(int (*a)[5])

Значить, що *fє функцією, *aце масив з п'яти цілих чисел, так fце функція, що приймає покажчик на масив з п’яти цілих чисел і повертає int. Однак в C не корисно передавати вказівник на масив.

C декларації дуже рідко ускладнюються.

Також можна уточнити за допомогою typedefs:

typedef int vec5[5];
int (*f)(vec5 *a);

4
Вибачте, якщо це звучить грубо (я не маю на увазі, що це було), але я думаю, ви пропустили всю точку питання ...: \
user541686

2
@Mehrdad: Я не можу сказати вам, що було в думці Керніган і Річі; Я розповів вам логіку синтаксису. Я не знаю про більшість людей, але не думаю, що запропонований вами синтаксис зрозуміліший.
кевін клайн

Я погоджуюся - незвично бачити таку складну декларацію.
Калеб

Конструкція C декларації передує typedef, const, volatile, а також можливість форматувати речі всередині оголошень. Багато прикрих неоднозначностей синтаксису декларування (наприклад, чи int const *p, *q;має прив'язуватися constдо типу або декларації ) не могло виникнути мовою, як було створено спочатку. Я хотів би, щоб мова додала двокрапку між типом і декларацією, але дозволила б її опущення при використанні вбудованих типів "зарезервоване слово" без класифікаторів. Сенс int: const *p,*q;і int const *: p,*q;було б зрозумілим.
supercat

3

Я думаю, що ви повинні вважати * [] операторами, які приєднані до змінної. * пишеться перед змінною, [] після.

Давайте прочитаємо вираз типу

int* (*p)[10];

Отже, найпотаємніший елемент - p, змінна

p

означає: p - змінна.

Перед змінною є *, оператор * завжди ставиться перед виразом, на який він посилається, отже,

(*p)

означає: змінна p - покажчик. Без () оператор [] праворуч би мав вищий пріоритет, тобто

**p[]

буде розбиратися як

*(*(p[]))

Наступний крок - []: оскільки більше немає (), [], отже, має перевагу над зовнішнім *

(*p)[]

означає: (змінна p - вказівник) на масив. Тоді у нас є другий *:

* (*p)[]

означає: ((змінна p - вказівник) на масив) покажчиків

Нарешті, у вас є оператор int (ім'я типу), який має найнижчий пріоритет:

int* (*p)[]

означає: (((змінна p - вказівник) на масив) покажчиків) на ціле число.

Отже вся система заснована на виразах типів з операторами, і кожен оператор має свої правила пріоритетності. Це дозволяє визначити дуже складні типи.


0

Це не так важко, коли ти починаєш думати, і мова С ніколи не була дуже легкою мовою. І int*[10]* pнасправді не простіше, ніж int* (*p)[10] І який тип k був биint*[10]* p, k;


2
k був би невдалим переглядом коду, я можу розібратися з тим, що буде робити компілятор, я навіть можу непокоїтись, але я не можу розробити те, що планував програміст - провал ............
mattnz

і чому k не вдалося б переглядати код?
Даїній

1
тому що код нечитабельний і неможливий. Код невірно виправляти, очевидно правильний і, ймовірно, залишатиметься правильним за умови обслуговування. Те, що вам потрібно запитати, що буде тип k, - це ознака, що код не відповідає цим основним вимогам.
mattnz

1
По суті, в одному рядку є 3 (в даному випадку) оголошення змінних різних типів, наприклад, int * p, int i [10] та int k. Це неприпустимо. Кілька заяв одного типу є прийнятним за умови, що змінні мають певну форму взаємозв'язку, наприклад, int ширина, висота, глибина; Майте на увазі, що багато людей програмують за допомогою int * p, тому що я в 'int * p, i;'.
mattnz

1
Що @mattnz намагається сказати, це те, що ви можете бути настільки розумними, як хочете, але все безглуздо, коли ваш намір не очевидний і / або ваш код погано написаний / нечитабельний. Цей матеріал часто призводить до порушення роботи коду та витраченого часу. Плюс, pointer to intі intнавіть не є однотипними, тому їх слід декларувати окремо. Період. Послухайте чоловіка. У нього є причина, що має 18 кп.
Бреден Кращий
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.