Правило чтения по спирали

Автор оригинала: David Anderson
  • Перевод
Техника, известная как «Чтение по спирали/по часовой стрелке» (“Clockwise/Spiral Rule”) позволяет любому программисту разобрать любое объявление языка Си.

Следуйте этим простым шагам:

1. Начиная с неизвестного элемента, двигайтесь по спирали/по часовой стрелке; при этом заменяйте следующие элементы соответствующими фразами:
  • [X] или [] => массив размера X типа… или массив неопределённого размера типа...
  • (type1, type2) => функция, принимающая типы type1, type2 и возвращающая...
  • * => указатель на...

2. Двигайтесь по спирали/по часовой стрелке, пока не будут пройдены все элементы.
3. Всегда сначала надо разрешать выражения в скобках!

Пример 1: Простое объявление


                     +-------+<br>
                     | +-+   |<br>
                     | ^ |   |<br>
                char *str[10];<br>
                 ^   ^   |   |<br>
                 |   +---+   |<br>
                 +-----------+<br>


Спросим себя, что такое str?
str — это...

Двигаемся по спирали/по часовой стрелке, начиная с str, и первый символ, который мы видим это '[', значит это массив, и так:
str — это массив размера 10 типа...

Продолжаем двигаться по спирали/по часовой стрелке, и следующее, что мы встречаем, это символ '*', значит у нас есть указатели, и так:
str — это массив размера 10 типа указатель на...

Двигаемся по спирали и видим конец строки, потому двигаемся дальше и видим тип 'char', и так:
str — это массив размера 10 типа указатель на char.

Мы посетили все элементы, т.е. всё, мы разобрали это выражение!

Пример 2: Объявление указателя на функцию


                     +--------------------+<br>
                     | +---+              |<br>
                     | |+-+|              |<br>
                     | |^ ||              |<br>
                char *(*fp)( int, float *);<br>
                 ^   ^ ^  ||              |<br>
                 |   | +--+|              |<br>
                 |   +-----+              |<br>
                 +------------------------+<br>

Cпросим себя, что такое fp?
fp — это...

Двигаемся по спирали/по часовой стрелке, и первое что мы видим, это ')'; таким образом fp внутри скобок и мы продолжаем спираль внутри скобок и следующий символ это '*', и так:
fp — это указатель на...

Мы теперь вне скобок и, продолжая двигаться по спирали видим '(', т.е. это функция, и так:
fp — это указатель на функцию, принимающую int и указатель на float, возвращающую...

Продолжая двигаться по спирали, встречаем символ '*', и так:
fp — это указатель на функцию, принимающую int и указатель на float, возвращающую указатель на...

Продолжая двигаться по спирали, встречаем символ ';', но так как ещё не все символы обработаны, то продолжаем и встречаем 'char', и так:
fp — это указатель на функцию, принимающую int и указатель на float, возвращающую указатель на char.


Пример 3: Ultimate


                      +-----------------------------+<br>
                      |                  +---+      |<br>
                      |  +---+           |+-+|      |<br>
                      |  ^   |           |^ ||      |<br>
                void (*signal(int, void (*fp)(int)))(int);<br>
                 ^    ^      |      ^    ^  ||      |<br>
                 |    +------+      |    +--+|      |<br>
                 |                  +--------+      |<br>
                 +----------------------------------+<br>

Спросим себя, что такое signal?
Обратите внимание, signal внутри скобок, так что сначала надо разобрать выражение внутри! Двигаясь по спирали/по часовой стрелке, мы видим '(', и так:
signal — функция, принимающая int и ...

Хм, мы можем использовать то же правило на символе 'fp', и так, что такое fp? fp также внутри скобок, так что мы продолжаем и видим '*', и так:
fp — это указатель на...

Двигаясь по спирали/по часовой стрелке, мы видим '(', и так:
fp — это указатель на функцию, принимающую int, возвращающую...

Двигаясь по спирали/по часовой стрелке, мы видим 'void', и так:
fp — это указатель на функцию, принимающую int, возвращающую void.

Мы закончили с fp, так что продолжим с signal, и сейчас мы имеем:
signal — функция, принимающая int и указатель на функцию, принимающую int, возвращающую void, возвращающая...

Мы всё ещё внутри скобок, так что следующий символ это '*', и так:
signal — функция, принимающая int и указатель на функцию, принимающую int, возвращающую void, возвращающая указатель на...

Мы разрешили элементы внутри скобок, продолжая по спирали получаем символ '(', и так:
signal — функция, принимающая int и указатель на функцию, принимающую int, возвращающую void, возвращающая указатель на функцию, принимающую int и возвращающую...

Наконец, мы продолжаем двигаться, и последним символом мы видим 'void', и так, наше полное описание таково:
signal — функция, принимающая int и указатель на функцию, принимающую int, возвращающую void, возвращающая указатель на функцию, принимающую int и возвращающую void.


Это правило применимо и для понимания атрибутов const и volatile. Например:
const char *chptr;

Итак, что такое chptr?
chptr — это указатель на char неизменяемый.

А как насчёт такого:
char * const chptr;

Итак, что такое chptr?
chptr — это неизменяемый указатель на char.

И наконец:
volatile char * const chptr;

Итак, что такое chptr?
chptr — это неизменяемый указатель на char volatile.

Комментарии 42

  • НЛО прилетело и опубликовало эту надпись здесь
    • НЛО прилетело и опубликовало эту надпись здесь
      • НЛО прилетело и опубликовало эту надпись здесь
          0
          Да с третьим примером можно вообще за всю жизнь не столкнуться, если не влезать сильно глубоко в дебри boost, stl или loki.
      +15
      Вот за такое я и не люблю плюсы:
      void (*signal(int, void (*fp)(int)))(int);


      Код программы должен читаться как книга, а не как… это…
        0
        void(*signal)(int, decltype(myFuncName));
          0
          у вас указатель на функцию, а в оригинале — функция возвращающая указатель на функцию
            +2
            Потому как люди, надо немного больше написать, зато будет читаться легче:
            typedef void (*fp)(int);
            и наша запись возвращается в ряды 2-ого примера:
            fp (*signal)(int, fp);
          +20
          А что, читается как книга по нелинейным дифурам )))
            +3
            Это просто крайне дурной стиль программирования.

            Вообще, данная статья интересна с точки зрения забавы, но если код и вправду приходится читать по спирали — это просто очень плохой код.
            +10
            Я боюсь С++, если там ТАК нужно читать каждую строку кода…
              +41
              Что вы, для остальных строчек правила совершенно другие, сложнее и запутаннее. Вы явно недооцениваете C++.
                0
                Поэтому, программисты отдыхают, играя в настольные ролевые игры.
                0
                Если не забывать про управление сложностью, Вам любой язык будет казаться удобным инструментом.
                0
                  +2
                  Спасибо!
                  Внятно написано. Попробовал — отнимает больше времени, но в запутанных выражениях чужих исходников реально помогает понять, что же наворотил автор…
                    +1
                    Если пользоваться typedef в таких случаях, то жизнь упрощается)
                      +3
                      Статья рассматривает вопрос о де-факто свершившемся акте насилия над C++, т.е. когда ничего не остается, кроме как копаться в чужой фантазии.
                      А так естественно в собственном коде такое надо избегать.
                      +6
                      Не даром говорят, что С++ is write only
                        +3
                        Любой язык write only, если хорошо постараться. Вопрос культуры программирования всего лишь.
                          +1
                          как говорится — это вы еще perl не видели! )))
                            0
                            Я то видел :)
                        –8
                        Не знаю где вы тут С++ увидели.
                        Тут Си, язык продуманный до мелочей.

                        Статься бесполезна.
                        Я с самого рождения могу прочесть любое объявление на Си без каких-либо раздумий.
                        Для тех, кто таким даром не обладает, Керниган и Ритчи написали целую страницу.
                        Называется она — «Сложные объявления».

                        Мне вот интересно. Как метод автора справится с char (*(*x[3])())[5]

                        А по поводу С++ согласен с вами. write only язык.
                          0
                          Ну холивар C vs C++ это явно не тема этого топика.

                          Имхо статья подает ту самую страницу из K&R в более запоминающейся форме.
                          Да и с вашим примером метод вроде справляется, или я что-то не понял подвоха?
                            +6
                            Чтобы не быть голословным:
                            x это массив из трех указателей на функцию без параметров возращающую указатель на массив из трех char'ов
                            typedef char arr[5];
                            
                            arr * foo()
                            {
                            }
                            
                            int main()
                            {
                                char (*(*x[3])())[5];
                                x[0] = foo;
                                return 0;
                            }
                              +4
                              может указатель на массив из пяти char'ов?
                          –1
                          думаю если хорошенько расставить скобки, пробелы, а может даже и переносы строки, то третий пример будет намного читабельнее
                            +6
                            Есть даже тул для этого написанный. Называется cdecl (http://gd.tuwien.ac.at/linuxcommand.org/man_pages/cdecl1.html). И у него даже есть онлайн версия (http://cdecl.org/).
                              +3
                              Кстати, cdecl этот работает в обе стороны. Можно на английском описать тип, а он его в С преобразует.
                                0
                                Последний пример эта онлайн-версия не осилила. Говорит, syntax error.
                                  0
                                  Надо вот так: void (*signal(int, void (*)(int)))(int), т.е. fp убрать. Оно не понимает имен у параметров указателей на функции.
                                0
                                После нескольких дней чтения исходников всяких gnome-овских приложений окончательно убедился, что Си — это ад. Но за статью спасибо, очень доступно. Хотя думаю после недельных перерывов без С++ по-прежнему буду входить в ступор при виде 'char * const' и 'const char*' :)
                                  0
                                  В WebKit код ещё интересней ;-)
                                  –5
                                  Нытики и неосиляторы детектед. Вполне себе краткая и компактная запись, после некоторой практики читается на ура.
                                    0
                                    Используйте typedef-ы, и забейте на эти объявления
                                      +3
                                      Что хочется отметить. Статья вовсе не про С++.
                                      Все приведенные примеры — чистый Си.

                                      Да, конечно С++ позволит вам написать те же самые конструкции, но… тем не менее на С++ так обычно не пишут. Обычно так пишут как раз на Си.

                                      Пример номер 3, как уже отмечали — пример плохого кода. Этот код сложно:
                                      1. Читать
                                      2. Понимать
                                      3. Исправлять
                                      4. Дополнять.

                                      Я понимаю, что автор кода преодолеет все 4 указанные сложности, но если он этот код передает другому человеку (коллективная разработка), то код становится источником постоянных ошибок и фиксов.
                                        0
                                        Блога по чистому Си вроде не существует. А языки очень родственные, так что почему бы и нет.И в заголовке отображено что речь идет о Си.
                                          0
                                          топиком промахнулся)
                                        0
                                        Функция, принимающая указатель на функцию и возвращающая указатель на функцию — это действительно ultimate.

                                        Хотя где-нибудь запросто может использоваться, например при построении ООП на C (виртуальный метод для данного метода).
                                          0
                                          Знакомого одного речь вспоминается мне:
                                            0
                                            Прикольно
                                              0
                                              Замечательно конечно. Но именно такие вещи как правило и отталкивают.
                                              Лютвидж Доджсон(aka Льюис Кэрролл) не застал все эти компьютеры и языки программирования. Его код и методы разработки особенно интересно было бы читать…

                                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                              Самое читаемое