Синтаксис указателя в C: почему * применяется только к первой переменной?
Следующее объявление в C:
int* a, b;
объявит a
как тип int*
и b
как тип int
. Я хорошо знаю эту ловушку, но я хочу знать, почему она работает именно так. Почему он также не объявляет b
как int*
, как это ожидало бы большинство людей? Другими словами, почему *
применяется к имени переменной, а не к типу?
Конечно, вы могли бы написать это так, чтобы быть более совместимым с тем, как это работает:
int *a, b;
Тем не менее, я и все, с кем я говорил, с точки зрения типа "указатель на int", а не a, являются указателем на некоторые данные, а тип данных - "int".
Было ли это просто плохое решение дизайнеров C, или есть какая-то веская причина, почему он разбирается таким образом? Я уверен, что на этот вопрос был дан ответ, но я не могу найти его, используя поиск.
Ответы
Ответ 1
Здесь есть веб-страница "Развитие языка C" , в которой говорится: "Синтаксис этих объявлений отражает наблюдение, что i, * pi и ** ppi, все дают тип int при использовании в выражении." Найдите это предложение на странице, чтобы найти соответствующий раздел, в котором говорится об этом вопросе.
Ответ 2
C декларации были написаны таким образом, чтобы использовать "зеркала объявлений". Вот почему вы объявляете массивы следующим образом:
int a[10];
Было ли у вас правило, которое вы предлагаете, где оно всегда
type identifier, identifier, identifier, ... ;
... тогда массивы логически должны быть объявлены следующим образом:
int[10] a;
что прекрасно, но не отражает, как вы используете a
. Обратите внимание, что это тоже относится к функциям - мы объявляем такие функции:
void foo(int a, char *b);
а не
void(int a, char* b) foo;
В общем, правило "использование зеркал объявлений" означает, что вам нужно запомнить только один набор правил ассоциативности, которые применяются к обоим операторам, таким как *
, []
и ()
, когда вы используете значение, и соответствующие токены в объявлениях типа *
, []
и ()
.
После некоторого дальнейшего размышления, я думаю, также стоит отметить, что орфографический "указатель на int" как "int*
" является следствием использования зеркал использования объявлений в любом случае. Если вы собираетесь использовать другой стиль объявления, вероятно, было бы разумнее записать "указатель на int" как "&int
" или что-то совершенно другое, например "@int
".
Ответ 3
Возможно, существует дополнительная историческая причина, но я всегда понимал ее так:
Одно объявление, один тип.
Если a, b, c и d должны быть одного типа:
int a, b, c, d;
Тогда все в строке должно быть целым.
int a, *b, **c, ***d;
4 целых числа:
Это может быть связано и с приоритетом оператора, или, возможно, это было в какой-то момент в прошлом.
Ответ 4
*
изменяет имя переменной, а не спецификатор типа. Это в основном из-за того, как анализируется *
. Возьмите эти утверждения:
char* x;
char *x;
Эти утверждения эквивалентны. Оператор *
должен находиться между спецификатором типа и именем переменной (он рассматривается как оператор инфикса), но он может идти по обеим сторонам пространства. Учитывая это, декларация
int* a, b;
не будет указывать указатель b
, потому что рядом с ним нет *
. *
работает только с объектами по обе стороны от него.
Также подумайте об этом так: когда вы пишете объявление int x;
, вы указываете, что x
является целым числом. Если y
является указателем на целое число, то *y
является целым числом. Когда вы пишете int *y;
, вы указываете, что *y
является целым числом (которое вы хотите). В заявлении char a, *b, ***c;
вы указываете, что переменная a
, разыменованное значение b
и трехкратно разыменованное значение c
имеют тип char
. Объявление переменных таким образом делает использование оператора звезды (почти) согласующимся с разыменованием.
Я согласен с тем, что было бы разумнее, если бы это было наоборот. Чтобы избежать этой ловушки, я сделал себе правило всегда декларировать указатели на одной линии.
Ответ 5
Я предполагаю, что это связано с полным синтаксисом объявления для модификаторов типов:
int x[20], y;
int (*fp)(), z;
В этих примерах гораздо более очевидно, что модификаторы влияют только на одно из объявлений. Предполагается, что однажды K & R решил разработать модификаторы таким образом, он чувствовал себя "правильным", чтобы модификаторы влияли только на одно объявление.
С другой стороны, я бы рекомендовал ограничиться только одной переменной для объявления:
int *x;
int y;
Ответ 6
Потому что если утверждение
int* a, b;
должны были объявить b
как указатель, тогда у вас не было бы способа объявить
int* a;
int b;
на одной строке.
С другой стороны, вы можете сделать
int*a, *b;
чтобы получить то, что вы хотите.
Подумайте об этом так: так, как сейчас, это все еще самый сжатый и пока единственный способ сделать это. Это то, что C в основном о:)
Ответ 7
Рассмотрим объявление:
int *a[10];
int (*b)[10];
Первый представляет собой массив из десяти указателей на целые числа, второй - указатель на массив из десяти целых чисел.
Теперь, если * был присоединен к объявлению типа, это не было бы синтаксически корректным для размещения скобок между ними. Таким образом, вам нужно будет найти другой способ разграничения между двумя формами.
Ответ 8
Я могу только догадываться, почему нужно было бы повторить *
для каждого имени переменной, упомянутого в строке, чтобы сделать их все указатели. Возможно, это связано с решением о согласованности языка? Позвольте мне проиллюстрировать это на примере:
Предположим, что у вас есть функция foo
, объявленная следующим образом:
int foo() { ... }
Предположим также, что вы хотите объявить два указателя на эту функцию:
int (*fooptr1, fooptr2)();
// this doesn't work; and even if it did, what would the syntax possibly
// look like to initializing them to function foo?
// int (*fooptr1 = foo, fooptr2 = foo)() ?
int (*fooptr1)() = foo, (*fooptr2)() = foo;
// this works.
В этом случае вам просто нужно повторить объявление всего типа для обеих переменных, и вы не ошибетесь, потому что нет другого способа сделать это (учитывая синтаксис объявления C как таковой).
Теперь, может быть, подумалось, если есть случаи, когда объявление типа должно повторяться для всех переменных, возможно, это должен быть просто общий случай.
(Не забывайте, что я просто угадываю здесь.)