Синтаксис указателя в C: почему * применяется только к первой переменной? - PullRequest
22 голосов
/ 16 июля 2010

Следующее объявление в C:

int* a, b;

объявит a как тип int* и b как тип int.Я хорошо знаю эту ловушку, но я хочу знать, , почему работает именно так.Почему он также не объявляет b как int*, как большинство людей интуитивно ожидают?Другими словами, почему * применяется к имени переменной, а не к типу?

Конечно, вы могли бы написать это таким образом, чтобы быть более совместимым с тем, как на самом деле работает:

int *a, b;

Однако я и все, о ком я говорил, мыслям в терминах a, имеют тип «указатель на int» , а не a - указатель на некоторые данныеи тип этих данных "int" .

Было ли это просто плохим решением проектировщиков C или есть какая-то веская причина, почему они анализируются таким образом?Я уверен, что на этот вопрос уже был дан ответ, но я не могу найти его с помощью поиска.

Ответы [ 8 ]

31 голосов
/ 16 июля 2010

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».

17 голосов
/ 16 июля 2010

Есть веб-страница на Разработка языка C , которая говорит: «Синтаксис этих объявлений отражает наблюдение, что все i, * pi и ** ppi выдают тип int при использовании в выражение." Найдите это предложение на странице, чтобы найти соответствующий раздел, в котором говорится об этом вопросе.

9 голосов
/ 16 июля 2010

Может быть дополнительная историческая причина, но я всегда понимал это так:

Одно объявление, один тип.

Если a, b, c и d должны бытьздесь тот же тип:

int a, b, c, d;

Тогда все в строке также должно быть целым числом.

int a, *b, **c, ***d;

4 целых числа:

  1. a
  2. * b
  3. ** c
  4. *** d

Это может быть также связано с приоритетом оператора или могло иметь местов какой-то момент в прошлом.

2 голосов
/ 16 июля 2010

Я предполагаю, что это связано с полным синтаксисом объявления для модификаторов типов:

int x[20], y;
int (*fp)(), z;

В этих примерах гораздо более очевидно, что модификаторы влияют только на одно из объявлений. Можно предположить, что после того, как K & R решила разработать модификаторы таким образом, было бы правильно, чтобы модификаторы влияли только на одно объявление.

Кстати, я бы рекомендовал ограничить себя одной переменной для каждого объявления:

int *x;
int y;
2 голосов
/ 16 июля 2010

* изменяет имя переменной, а не спецификатор типа.Это в основном из-за способа анализа *.Возьмите эти утверждения:

char*  x;
char  *x;

Эти утверждения эквивалентны.Оператор * должен находиться между спецификатором типа и именем переменной (он обрабатывается как инфиксный оператор), но он может идти с любой стороны пробела.Учитывая это, объявление

int*  a, b;

не сделает b указателем, поскольку рядом с ним нет *.* работает только с объектами по обе стороны от него.

Также подумайте об этом следующим образом: когда вы пишете объявление int x;, вы указываете, что x является целым числом.Если y является указателем на целое число, тогда *y является целым числом.Когда вы пишете int *y;, вы указываете, что *y является целым числом (что вам нужно).В операторе char a, *b, ***c; вы указываете, что переменная a, значение разыменования b и значение трижды разыменования c имеют тип char.Объявление переменных таким образом делает использование звездообразного оператора (почти) совместимым с разыменованием.

Я согласен, что было бы более разумно, если бы было наоборот.Чтобы избежать этой ловушки, я взял за правило всегда объявлять указатели в строке.

1 голос
/ 16 июля 2010

Рассмотрим объявление:

int *a[10];
int (*b)[10];

Первый - это массив из десяти указателей на целые числа, второй - указатель на массив из десяти целых чисел.

Теперь, если *был присоединен к объявлению типа, было бы неправильно использовать скобки между ними.Поэтому вам нужно найти другой способ провести различие между этими двумя формами.

1 голос
/ 16 июля 2010

Поскольку, если бы оператор

int* a, b;

также объявлял b как указатель, у вас не было бы способа объявить

int* a;
int  b;

в одной строке.

С другой стороны, вы можете сделать

int*a, *b;

, чтобы получить то, что вы хотите.

Подумайте об этом так: так, как сейчас, это все ещеСамый краткий и в то же время уникальный способ сделать это.Это то, что C в основном о:)

0 голосов
/ 16 июля 2010

Я могу только догадываться, почему нужно было бы повторять * для каждого имени переменной, указанного в строке, чтобы сделать их указателями. Может быть, это связано с решением о согласованности языков? Позвольте мне проиллюстрировать это на примере:


Предположим, у вас есть функция 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 как есть).


Теперь, возможно, это было продумано, если есть случаи, когдаобъявление типа должно быть повторено для всех переменных, возможно, это должно быть просто общим случаем.

(не забывайте, что я просто догадываюсь здесь.)

...