Я только сегодня увидел фотографию и думаю, что буду благодарен за объяснения. Итак, вот картинка:
Я нашел это смущающим и задавался вопросом, практичны ли когда-либо такие коды. Я гуглил картинку и нашел другую картинку в этой записи Reddit, и вот эта картинка:
Так что это «чтение по спирали» является чем-то действительным? Это как C-компиляторы анализируют?
Было бы здорово, если бы есть более простые объяснения этого странного кода.
Кроме всего прочего, могут ли эти коды быть полезными? Если да, то где и когда?
Есть вопрос о «спиральном правиле», но я не просто спрашиваю о том, как оно применяется или как выражения читаются с этим правилом. Я подвергаю сомнению использование таких выражений и правильность спирального правила. Что касается этого, некоторые хорошие ответы уже опубликованы.
f
как массив указателей на функции, которые могут принимать любой аргумент ... если бы это было такvoid (*(*f[])(void))(void);
, тогда да, это были бы функции, которые не принимают аргументов ...Ответы:
Существует правило, называемое «Правило по часовой стрелке / спираль», которое помогает найти значение сложного объявления.
Из c-faq :
Вы можете проверить ссылку выше для примеров.
Также обратите внимание, что в помощь вам также существует сайт под названием:
http://www.cdecl.org
Вы можете ввести декларацию C, и она придаст смысл английскому языку. Для
это выводит:
РЕДАКТИРОВАТЬ:
Как указано в комментариях Random832 , спиральное правило не обращается к массиву массивов и приведет к неправильному результату (большей части) этих объявлений. Например, для
int **x[1][2];
спирального правила игнорируется тот факт, что[]
имеет более высокий приоритет над*
.Перед массивом массивов можно сначала добавить явные скобки перед применением правила спирали. Например:
int **x[1][2];
то же самое, что иint **(x[1][2]);
(также допустимый C) из-за приоритета, и тогда спиральное правило правильно его читает, поскольку «x - это массив 1 из массива 2 указателя на указатель на int», который является правильным объявлением на английском языке.Обратите внимание , что этот вопрос также рассматривается в этом ответе по Джеймс Kanze (указал haccks в комментариях).
источник
Вид «спирального» правила выпадает из следующих правил приоритета:
Операторы
[]
вызова индекса и функции()
имеют более высокий приоритет, чем унарные*
, поэтому*f()
анализируются как*(f())
и*a[]
анализируются как*(a[])
.Так что если вы хотите указатель на массив или указатель на функцию, то вам нужно явно сгруппировать
*
с идентификатором, как в(*a)[]
или(*f)()
.Тогда вы понимаете это
a
иf
можете быть более сложными выражениями, чем просто идентификаторы; inT (*a)[N]
,a
может быть простым идентификатором, или это может быть вызов функции вроде(*f())[N]
(a
->f()
), или это может быть массив типа(*p[M])[N]
(a
->p[M]
), или это может быть массив указателей на функции вроде(*(*p[M])())[N]
(a
->(*p[M])()
), и т.п.Было бы неплохо, если бы оператор косвенности
*
был постфиксным, а не унарным, что могло бы несколько облегчить чтение деклараций слева направо (void f[]*()*();
безусловно, лучше, чемvoid (*(*f[])())()
), но это не так.Когда вы сталкиваетесь с таким волосатым объявлением, начните с поиска крайнего левого идентификатора и примените приведенные выше правила приоритета, рекурсивно применяя их к любым параметрам функции:
signal
Функция в стандартной библиотеке, вероятно , образец типа для этого вида безумия:В этот момент большинство людей говорят «используйте typedefs», что, безусловно, является опцией:
Но...
Как бы вы использовали
f
в выражении? Вы знаете, что это массив указателей, но как вы используете его для выполнения правильной функции? Вы должны просмотреть typedefs и найти правильный синтаксис. Напротив, «голая» версия довольно приятна, но она точно говорит вам, как использоватьf
в выражении (а именно(*(*f[i])())();
, если предположить, что ни одна функция не принимает аргументы).источник
f
дерево замедления, объясняющее приоритет ... почему-то я всегда получаюvoid
в скобках функции, в противном случае она может принимать любые аргументы.В C декларация отражает использование - это то, как это определено в стандарте. Декларация:
Утверждение, что выражение
(*(*f[i])())()
выдает результат типаvoid
. Что значит:f
должен быть массивом, так как вы можете индексировать его:Элементы
f
должны быть указателями, так как вы можете разыменовать их:Эти указатели должны быть указателями на функции без аргументов, так как вы можете вызывать их:
Результаты этих функций также должны быть указателями, так как вы можете разыменовать их:
Эти указатели также должны быть указателями на функции без аргументов, так как вы можете вызывать их:
Эти указатели функций должны возвращаться
void
«Правило спирали» - это просто мнемоника, которая дает другой способ понимания одного и того же.
источник
vector< function<function<void()>()>* > f
, особенно если добавить вstd::
с. (Но хорошо, пример это ухитрялся ... дажеf :: [IORef (IO (IO ()))]
выглядит странно.)a[x]
указывает, что выражениеa[i]
является действительным, когдаi >= 0 && i < x
. Принимая во внимание, чтоa[]
оставляет размер неуказанным, и, следовательно, идентичен*a
: он указывает, что выражениеa[i]
(или эквивалентно*(a + i)
) является допустимым для некоторого диапазонаi
.(*f[])()
это тип, который вы можете индексировать, затем разыменовывать, затем вызывать, так что это массив указателей на функции.Применение спирального правила или использование cdecl не всегда допустимы. Оба в некоторых случаях терпят неудачу. Спиральное правило работает во многих случаях, но оно не универсально .
Чтобы расшифровать сложные объявления, запомните эти два простых правила:
Всегда читайте объявления изнутри : начинайте с самой внутренней, если есть, круглой скобки. Найдите идентификатор, который объявлен, и начните расшифровывать объявление оттуда.
Когда есть выбор, всегда отдавайте предпочтение
[]
и()
больше*
: если*
предшествует идентификатору и[]
следует за ним, идентификатор представляет собой массив, а не указатель. Аналогично, если*
предшествует идентификатору и()
следует за ним, идентификатор представляет функцию, а не указатель. (Круглые скобки всегда можно использовать для переопределения обычного приоритета над[]
и()
над*
.)Это правило включает зигзаги от одной стороны идентификатора к другой.
Теперь расшифровываем простую декларацию
Применяя правило:
Давайте расшифруем сложное объявление как
применяя вышеуказанные правила:
Вот GIF, демонстрирующий, как вы идете (нажмите на картинку для увеличения):
Упомянутые здесь правила взяты из книги « Программирование на современном подходе» К.Н. Кинга .
источник
char (x())[5]
должно привести к синтаксической ошибке, но cdecl проанализирует ее как: объявитьx
как функцию, возвращающую массив 5 ofchar
.Это всего лишь «спираль», потому что в этом объявлении только один оператор на каждой стороне в каждом уровне скобок. Утверждение о том, что вы действуете «по спирали», обычно предполагает, что вы должны чередовать массивы и указатели в объявлении,
int ***foo[][][]
когда в действительности все уровни массива предшествуют любому из уровней указателя.источник
Я сомневаюсь, что такие конструкции могут быть полезны в реальной жизни. Я даже ненавижу их как вопросы интервью для обычных разработчиков (вероятно, хорошо для авторов компиляторов). Вместо этого следует использовать typedefs.
источник
Как случайный фактоидный пустяк, вам может показаться забавным знать, что в английском языке есть реальное слово, описывающее, как читаются объявления C: бустрофедонически , то есть чередуется справа налево с слева направо.
Ссылка: Ван дер Линден, 1994 - Страница 76
источник
Что касается полезности этого, при работе с шеллкодом вы часто видите эту конструкцию:
Хотя этот синтаксис не так сложен, он часто встречается.
Более полный пример в этом вопросе.
Таким образом, хотя полезность в такой степени в исходной картинке сомнительна (я бы предположил, что любой производственный код должен быть радикально упрощен), есть некоторые синтаксические конструкции, которые подходят совсем немного.
источник
Декларация
это просто неясный способ сказать
с участием
На практике, вместо ResultFunction и Function потребуется больше описательных имен . Если возможно, я бы также указал списки параметров как
void
.источник
Я нашел метод, описанный Брюсом Экелом, полезным и простым для понимания:
Взято из: Мышление в C ++ Том 1, второе издание, глава 3, раздел «Адреса функций» Брюса Эккеля.
источник
За исключением случаев, когда они были изменены скобками, конечно. И обратите внимание, что синтаксис их объявления точно отражает синтаксис использования этой переменной для получения экземпляра базового класса.
Серьезно, это не трудно научиться делать с первого взгляда; Вы просто должны быть готовы потратить некоторое время на оттачивание навыка. Если вы собираетесь поддерживать или адаптировать C-код, написанный другими людьми, это определенно стоит потратить на это время. Это также забавный трюк для вечеринки, который сводит с ума других программистов, которые этого не изучали.
Для вашего собственного кода: как всегда, тот факт, что что-то может быть написано в виде одной строки, не означает, что так должно быть, если только это не очень распространенный шаблон, ставший стандартной идиомой (такой как цикл копирования строк) , Вы и те, кто следует за вами, будут намного счастливее, если вы будете создавать сложные типы из многоуровневых типов и пошаговых разыменований, а не полагаться на свою способность генерировать и анализировать их «одним махом». Производительность будет такой же хорошей, а удобочитаемость и удобство сопровождения кода будут значительно выше.
Знаешь, могло быть и хуже. Было юридическое заявление PL / I, которое начиналось с чего-то вроде:
источник
IF IF = THEN THEN THEN = ELSE ELSE ELSE = ENDIF ENDIF
и анализируется какif (IF == THEN) then (THEN = ELSE) else (ELSE = ENDIF)
.Я был первым автором спирального правила, которое я написал очень много лет назад (когда у меня было много волос :), и было честью, когда оно было добавлено в cfaq.
Я написал спиральное правило, чтобы моим студентам и коллегам было легче читать декларации С "в их голове"; т.е. без использования программных инструментов, таких как cdecl.org и т. д. Я никогда не намеревался объявлять, что спиральное правило является каноническим способом синтаксического анализа выражений Си. Тем не менее, я рад видеть, что это правило помогло буквально тысячам студентов и практикующих программистов на C за эти годы!
Для записи,
На многих сайтах, в том числе Линусом Торвальдсом (кем-то, кого я безмерно уважаю), «правильно» неоднократно идентифицировалось, что бывают ситуации, когда мое спиральное правило «нарушается». Наиболее распространенным является:
Как отмечали другие в этом потоке, правило может быть обновлено, чтобы сказать, что когда вы сталкиваетесь с массивами, просто используйте все индексы, как если бы они были написаны так:
Теперь, следуя спиральному правилу, я получу:
«ar - это двумерный массив указателей на символ 10x10»
Я надеюсь, что спиральное правило будет полезным для изучения C!
PS:
Я люблю образ "С не сложно" :)
источник
(*(*f[]) ()) ()
Разрешение
void
>>(*(*f[]) ())
() = пустоResoiving
()
>>(*f[]) ()
) = функция, возвращающая (void)Разрешение
*
>>(*f[])
() = указатель на (функция, возвращающая (void))Разрешение
()
>>f[]
) = функция, возвращающая (указатель на (функция, возвращающая (void)))Разрешение
*
>>f
[] = указатель на (функция, возвращающая (указатель на (функция, возвращающая (void))))Разрешение
[ ]
>>источник