Прошло 1 апреля. Часто первоапрельские шутки, выложенные в Интернете, продолжают свое шествие, и всплывают совершенно в неожиданное время. О такой шутке про язык Си и будет эта статья. В каждой шутке есть только доля шутки. Код из нее я взял на вооружение для беглого тестирования на знание языка Си.

Надо написать программу (с пояснениями), в которой будет работать следующая строка:

for(;P("\n"),R--;P("|"))for(e=C;e--;P("_"+(*u++/8)%2))P("| "+(*u/4)%2);

Всего одна строка, но по ней можно определить глубину понимания человеком языка Си. Эта строка будет работать также и на С++. Советую попробовать свои силы. Смешно не будет. Возможно будет полезно.

На заре своей карьеры программиста, мне друг показал статью про то, что язык Си и UNIX первоапрельская шутка. В качестве доказательства абсурдности языка приводилась вышеприведенная строка кода. На мой взгляд, вполне рабочая. Через некоторое время при проведении собеседования вспомнилась эта шутка. Как и при решении многих других тестов, здесь важен не результат (он задает цель работы), а сам процесс разбора и понимания.

Где мы взяли статью я уже не помню. Каждый раз его нахожу в поисковике по фразе «си и unix первоапрельская шутка» (например тут). В этих репостах когда-то потерялся один минус в инкременте после «R» и «e», и появился второй обратный слеш в строке "\n".

Попробуйте разобраться с заданием сами. Не задумывайтесь пока над смыслом программы.

Форматирование творит чудеса
Настоятельно советую привести это однострочное безобразие в читаемый вид, расставив переносы строк, отступы и пробелы.

for ( ; P("\n"), R--; P("|"))
    for (e = C; e--; P("_" + (*u++ / 8) % 2))
        P("| " + (*u / 4) % 2);

Это совсем легко, если видели текст нормальных программ. На пробелы можно закрыть глаза, но циклы должны быть на разных строках с разными отступами.

Надо написать элементарную программу, типа «Hello world!»
Вместо вывода приветствия всему миру, надо вставить текст самого задания и объявить некие переменные (это будет далее).

#include <stdio.h>
int main()
{
...
    for ( ; P("\n"), R--; P("|"))
        for (e = C; e--; P("_" + (*u++ / 8) % 2))
            P("| " + (*u / 4) % 2);
    return 0;
} 

Это уже можно обсуждать. Зачем нужен include? И нужен ли он здесь? Можно ли без return? И совсем жестокий вопрос. Какие параметры у функции main?

Не поленитесь, и попробуйте ответить на эти вопросы сами.

Разбор внешнего цикла
Если человек успешно дошел до этого этапа, то он уже понимает, что есть два вложенных цикла. Разберем внешний.

for ( ; P("\n"), R--; P("|"))

Здесь встречаем совсем простую проблемку. Нет инициализатора (после открытой скобки идет сразу точка с запятой). Некоторых это смущает. Это часто бывает, если человек пишет программы на другом языке, например, на Паскале.

Настоящим камнем преткновения, даже у достаточно опытных программистов, встречает выражение «P("\n"), R--». Многие просто не знают, что есть такая операция «запятая», и что результатом его работы будет результат выражения, стоящего после запятой. Выражение до запятой тоже вычисляется, но его результат не используется. Причем эта операция имеет самый низкий приоритет . Следовательно, сначала выполняется P("\n"), а потом R--.

Результат выражения R-- здесь является условием выполнения. Это тоже некоторых смущает, хотя этот прием часто используется. Многие программисты считают излишним писать в условных операторах if, выражения типа if (a != 0) … Тут аналогичный случай (R-- != 0). Настала пора добавить объявление первой переменной. Инкремент говорит о том, что это точно не вещественное число. Подойдет любой целочисленный тип, даже беззнаковый. Эту переменную надо не только объявить, но и проинициализировать каким-либо положительным значением (лучше небольшим).

Обычно, дойдя до сюда, всем уже ясно, что есть функция P, которая принимает на вход строку. Тут проблем уже нет. Надо объявить эту функцию. Поскольку смысл нам не важен, то она может быть даже пустой. Мне больше нравится функция, выводящая текст на экран (тут и пригодился заранее написанный #include <stdio.h>). Считаю, что эту функцию должен уметь писать программист любой уровня.

Разбор внутреннего цикла
 for (e = C; e--; P("_" + (*u++ / 8) % 2) )

Здесь в цикле уже все знакомо. Декремент в проверке на выполнении цикла, как было выше. Добавляем переменную e, по аналогии с R. Можно сразу объявить и переменную C того же типа, хотя это может быть и константа, или даже define. Тут воля автора.

Интерес тут вызывает вызов функции P.

 P("_" + (*u++ / 8) % 2) 

Если посмотреть дальше, то мы увидим в теле функции подобную конструкцию.

 P("| " + (*u / 4) % 2);

Тут стоит набраться терпения. Цель близка. Это венец этого «шедевра». Не спешите открывать следующее разъяснение, подумайте.

Изюминка
Разбираем два выражения:

"_" + (*u++ / 8) % 2

"| " + (*u / 4) % 2

Далее будем рассматривать первое выражение. Оно более сложное. Понятно, что здесь сперва вычисляется выражение в скобках, потом берется от него остаток от деления на 2, в конце это число добавляется к строке.

Самое простое, это вычисление остатка от деления. Изредка встречаются программисты не использующие такую операцию. Они могут смутиться. Главное, то что эта операция производится над целочисленными типами и результат тоже целочисленный. Коварный вопрос для самостоятельной проработки, может ли быть результат выражения (*u++ / 8) % 2 отрицательным?

Поскольку результат выражения в скобках должен быть целочисленным, то и операция деление целочисленное, и делимое целочисленное. У начинающих программистов выражение *u++ может вызвать неуверенность: в наличии постинкремента в выражении и в приоритете выполнения операций постинкремента и разыменование указателя. Данный прием иногда используется в программах на Си при движении по массиву. Выражение возвращает значение по текущему указателю (до инкрементации) и смещает указатель на следующий элемент. Следовательно, переменная u не просто указатель, но и является массивом. Дополнительный вопрос, какого размера (в элементах) должен быть этот массив?

Самый «красивый» прием – это прибавление числа к строке. Надо помнить, что это язык Си. Не стоит ждать преобразования числа в строку, а тем более строки в число. Все гораздо более странно, чем может показаться с первого взгляда, но очень логично для Си. Строка – это массив символов, а значит указатель на память, где находится первый символ. Если это указатель, то прибавление к нему целого числа означает вычисление адреса, сдвинутого относительно исходного указателя на заданное число элементов. В данном примере после получения остатка от деления на 2 выходит либо 0, либо 1. Соответственно, либо строку передаем в функцию P без смещения (как есть), либо смещаем на один символ в конец строки. Простой вопрос, могут ли возникнуть проблемы при смещении на один символ в строке, состоящей из одного символа (как в нашем случае)?

Выражение (X / 8) % 2 – это просто получение четвертого бита. Для беззнакового целого числа это эквивалентно (X >> 3) & 1. И в заключении, дополнительное задание – проверить это утверждение для отрицательных чисел.

Специально не буду давать текст программы. Считаю, что после всех подсказок эту программу можно легко написать.

Если Вы думаете, что это выдуманный пример, то я могу поспорить. Реально попадается гораздо более тяжелый для разбора код. И не подумайте, что я призываю писать такой ужас.

Для тех, кто попадет на такое тестирование: тут главное не пугаться, вас вероятно возьмут на работу и без этого задания.

Для тех, кто захочет использовать на собеседование: если Вы решите дать эту задачку на собеседование, а в глазах у соискателя блеснет улыбка, то это значит, что вы оба читали этот пост. Но Вы не расстраивайтесь, пусть повторит с объяснением…

Для читателей: я знаю только одного человека, который сделал это задание сразу (и это был не я).

P.S.
Спасибо nwalker:
«Это один из победителей IOCCC 1985, shapiro.c by Carl Shapiro, код рисует лабиринт в stdout.… В процессе поиска нашелся еще и занятный пост про обфускацию C и лабиринты: The art of obfuscation
Поделиться с друзьями
-->

Комментарии (107)


  1. terrier
    06.04.2017 19:57
    +18

    Через некоторое время при проведении собеседования вспомнилась эта шутка.

    Очень надеюсь, что это было собеседование с самим собой в зеркале 1-го января и вам не пришло в голову принимать решение о найме программиста на основе этой бредятины.


    1. Lirein
      06.04.2017 20:30
      +5

      Почему же, очень простая задача, для сишника со стажем более полутора лет. Только вот, к сожалению, с каждым годом новых специалистов по C/C++ все меньше и меньше :(


      1. MaM
        06.04.2017 20:42
        +2

        Работу найти сложней, кнопочки на JS ляпать проше и работу найти легче.


        1. alexeykuzmin0
          07.04.2017 11:04

          Так, казалось бы, и денег больше, чем в кнопочках на JS


          1. domix32
            07.04.2017 11:46
            +5

            Да не сказал бы. Посмотреть тот же hh и сравнить вакансии c++ и какой-нибудь node.js


            1. Drag13
              07.04.2017 17:30

              А причем тут кнопочки и Node.js?


              1. domix32
                07.04.2017 22:50
                -1

                Ну, я думаю что в случае ключевых слов JS и работа — node.js первое, что приходит в голову. Все эти angular/reactive/matreshka, которые через здоровенный автоматизированный пайплайн выдают пользователю кнопочку "сделать хорошо" на одностраничнике. Мало кого сейчас интересуют рисователи кнопочек на jQuery, если вообще интересуют.


                1. Drag13
                  08.04.2017 21:31

                  Просто Node.js это как раз серверная часть а не UI.


                  1. domix32
                    08.04.2017 23:58

                    Аналогично PHP работает на серверной стороне, да и также как и PHP по большей части нацелена на веб. Можно конечно проворачивать такое и с c++, но едва ли такая вакансия будет настолько же оплачиваема и востребована как знаток ноды или того же пхп.
                    От минусанувшего хотелось бы узнать что не так с предыдущим комментарием.


            1. alexeykuzmin0
              07.04.2017 23:11

              Перепроверил — признаю, был неправ.


      1. terrier
        06.04.2017 20:52
        +8

        Сама по себе задача простая без вопросов, вполне можно развлечь себя ей. Однако — смотрите, как бы вы прокомментировали собеседование, на котором кандидату ( видимо на должность C программиста ) задавали бы вопросы про оператор «запятая», сложение числа со строкой и приоритет операций? Готов человек блестяще решивший эту задачку писать операционные системы, базы данных или какие-нибудь cache oblivious алгоритмы? Ведь сейчас перед программистами С стоят уже в основном действительно сложные и нетривиальные задачи ( все простое пишем на питоне).

        Была одно время мода на такого рода собеседования ( там еще триграфы любили спрашивать ), но казалось бы уже прошла.


        1. Lirein
          07.04.2017 05:06
          -3

          Я при поиске C программиста показывал кусок кода из продакшена и просил его прокомментировать. Т.к. это был код in-memory b-tree базы данных, то там были и макроопределения с переменным количеством параметров, и блокировки и rwlock'и, и ссылочные типы данных. В общем полный набор. Большинство заваливается уже на разнице в #Include <stdio.h> и #include «common.h». Более-менее адекватного программиста нашли только через полгода, в основном люди пишут в порядке возрастания популярности на Delphi/Python/PHP/Java. О существовании C/C++ знают, но архитектуры отличные от ARM и x86 приводят в ступор, а ведь там не все тривиально, интел нам многое прощает, то же выравнивание блоков и межсегментную адресацию памяти. На ARM или MIPS мы получим исключение если участок памяти находится между сегментами и мы делаем что то вроде object->timespec.tv_nsec;


          1. apro
            07.04.2017 07:46

            На ARM мы получим исключение если участок памяти находится между сегментами

            А можете пояснить, что вы подразумеваете под понятием "сегментам" на ARM,
            и ARM с MMU или с MPU имеется ввиду?


            1. Lirein
              07.04.2017 17:41

              Особой разницы нет, на большинстве RISC архитектур, и вышедших из чистой архитектуры RISC используется страничная адресация памяти, MMU выделит нам конечное количество страниц. Проблема будет если мы создадим структуру без выравнивания, например для передачи по сети. Как мы помним выравнивание на разных платформах разное. При использовании структур с выравниванием у нас никогда слово не окажется между двух физических сегментов памяти. в виртуальном адресном пространстве они могут находиться линейно и побайтово прекрасно читаться, но попытка сделать вот так: uint16_t val = my_struct->someval если данные попали между двух физических сегментов — вызывает аппаратное исключение. Как workaround используется memcpy такого блока в выравненную структуру в памяти или в локальную переменную и чтение уже оттуда, или чтение побайтово (зависит от задачи и размера читаемых данных). Ошибки такого рода проявляются не сразу а при значительных объемах линейно выделенной памяти для не выровненных структур (накопление сетевого буффера перед отдачей клиенту и отдельный поток фильтрации, например).


              1. apro
                07.04.2017 19:55

                Про проблемы с выравниванием я знаю, и по крайней мере у gcc есть несколько ключей,
                для генерации предупреждений по этому поводу, на x86 с этим тоже можно кстати столкнуться,
                например если компилятору "сказать" что данные выравнены и заставить его сгенерировать код для работы скажем с AVX, а потом дать на вход невыровненные данные.


                Меня интересовало как раз что за сегменты (на arm скажем никаких сегментов в терминах i386 нет) и что за дырки между ними, но судя по все это было просто недопонимание.


              1. Lirein
                07.04.2017 19:59

                Ну и пример кода который должен вызывать SigSegv (Segmantation fault) на ARM, MIPS, Sparc и Power:

                #include <stdint.h>
                #include <stdlib.h>

                // struct in 7 bytes
                #pragma pack(push, 1) //disable structure field align
                struct my_struct_t {
                uint16_t data1; //two bytes
                uint8_t fill1; //one byte
                uint32_t data2; //four bytes
                };
                #pragma pack(pop)

                #define ARRAY_MAX 1048576

                int main() {
                struct my_struct_t *buffer = (struct my_struct_t*)malloc(ARRAY_MAX*sizeof(struct my_struct_t));
                uint32_t i;
                uint16_t data1=12345;
                uint32_t data2=1234567890;
                //write
                for(i=0; i<ARRAY_MAX; i++) {
                buffer[i].data1=data1;
                buffer[i].fill1='A';
                buffer[i].data2=data2;
                }
                //read
                for(i=0; i<ARRAY_MAX; i++) {
                data1=buffer[i].data1;
                data2=buffer[i].data2;
                }
                }


                1. Wedmer
                  07.04.2017 20:23

                  -mstructure-size-boundary для gcc не спасает?


                  1. Lirein
                    08.04.2017 05:04

                    Извините, а изменение выравнивания границ структур может повлиять на работу процессора?
                    Мне кажется это только флаг компилятора который генерирует код.

                    В примере мы готовим абстрактный блок данных для отдачи по сети, согласно абстрактному же протоколу передачи, и читаем его после получения. И вот в том случае если слово или двойное слово попадает между физических страниц памяти — при попытке его прочитать будет исключение. Грубо говоря на x86 ASM мы получим нечто такое:
                    mov ebx,[edx]
                    И при выполнении этой инструкции вылетит ошибка, на интеле такой ошибки никогда не возникнет.


                    1. Wedmer
                      08.04.2017 11:13

                      У меня софт крутится на x86, x86_64, MIPS, ARM. Структурки выравниваются по байту.
                      На одном компиляторе используется этот ключ, тк прагма не работает. И ничего не крашится.


                      1. Lirein
                        08.04.2017 14:31

                        Спасибо, попробую на тестовой площадке собрать код и погонять, может как то повлияет, хотя сильно на это не надеюсь.


        1. Algoritmist
          07.04.2017 12:37
          -2

          На то он и тест на уровень знания. Он не дает ответа «ДА» или «НЕТ». Тут много исходов. И обычно тесты не является основанием приема на работу или отказа, а просто тема для разговора.


        1. MacIn
          07.04.2017 18:24

          на котором кандидату ( видимо на должность C программиста ) задавали бы вопросы про оператор «запятая», сложение числа со строкой и приоритет операций? Готов человек блестяще решивший эту задачку писать операционные системы, базы данных или какие-нибудь cache oblivious алгоритмы

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


          1. terrier
            07.04.2017 18:45
            +1

            Тут три момента:
            1). Категорически неочевидно как из блестящего владения оператором «запятая» следует хорошее знание языка C
            2). Кроме того само по себе знание C не нужно никакому бизнесу, кроме разработчиков компиляторов. «Знаешь C? Ну, возьми с полки пирожок. Нам интересно как ты поможешь нам в разработке нашего вэб-сервера».
            3). Собеседование — это формат переговоров, весьма сжатый по времени. В текущей реальности только гугл и сопоставимые компании могут мурыжить кандидатов 6-ю собеседованиями, для более приземленных компаний нужно за час-полтора принять решение о, том, будем ли мы сотрудничать с этим человеком. Тратить это время на разговоры об операторе запятая — это похоже на «Окей, леди и джентльмены, у нас час на то, чтобы заключить эту важную для всех нас сделку, так что давайте для начала станцуем танец маленьких утят»


            1. Algoritmist
              07.04.2017 18:57

              1). Теперь и Вы знаете кун-фу оператор «запятая»
              2). Нужно, если работаете программистом на С. Вы не поверите, но есть устройства, где язык С единственный язык программирования.
              3). Согласен.


              1. Algoritmist
                07.04.2017 19:04

                к п.2) да, еще про ассемблер забыл


              1. terrier
                07.04.2017 19:12

                2). Видимо не совсем четко сформулировал. Даже если программист пишет код на C на платформе, на которой кроме C ничего нет, платят ему не за то, что он печатает «int main()» и т.д., а за то, что он заставляет эти несчастные микроконтроллеры контролировать станки, собирать данные о температуре и так далее. Само по себе знание С ничего не стоит, оно должно быть частью гораздо более широкого набора умений. А вот это уже оплачивается


                1. Algoritmist
                  07.04.2017 21:40

                  Угу! Полностью поддерживаю. За знание языка не платя, если не даешь результат. Язык это средство в достижении конкретной задачи. А эта задача — средство достижения задач других уровней.
                  Фундаментальные мировоззренческие вещи затронули.


                  1. alexeykuzmin0
                    07.04.2017 23:13

                    Мне нравится сравнение программирования с написанием стихов на японском. Японский знать, конечно, надо, но это не главный навык.


            1. MacIn
              07.04.2017 19:25

              1). Категорически неочевидно как из блестящего владения оператором «запятая» следует хорошее знание языка C

              Никак. Необходимое, но не достаточное условие.

              2). Кроме того само по себе знание C не нужно никакому бизнесу, кроме разработчиков компиляторов. «Знаешь C? Ну, возьми с полки пирожок. Нам интересно как ты поможешь нам в разработке нашего вэб-сервера».

              Ну, наверно, если гражданин нанимает работников, проверяя знание языка Си, то именно им он как раз и нужен, не? Знание этого языка является необходимым условием для приема на работу именно в эту фирму. Необходимым, т.е. проверка на знание A, B и C из моего предыдущего комментария последует отдельно.
              Собеседование — это формат переговоров, весьма сжатый по времени. В текущей реальности только гугл и сопоставимые компании могут мурыжить кандидатов 6-ю собеседованиями, для более приземленных компаний нужно за час-полтора принять решение о, том, будем ли мы сотрудничать с этим человеком.

              Даже в маленьких конторах проводят по 2-3 собеседования, это нормально.


              1. terrier
                07.04.2017 19:39
                +1

                Никак.

                Итак, мы проверяем знание оператора запятая, хотя из этого мы не можем извлечь никакой информации о знании языка C. Вроде бы как напрашивается вывод…

                Ну, наверно, если гражданин нанимает работников, проверяя знание языка Си, то именно им он как раз и нужен, не?

                Не. Это как: «Я взял на улицу зонт, следовательно идет дождь» — не следует.

                проводят по 2-3 собеседования, это нормально.

                У меня другое мнение и оно подтверждается ( моей ) практикой, но это уже совсем оффтопик. В любом случае, даже если у нас 3 собеседования — тратить время просто так не надо.


                1. MacIn
                  13.04.2017 19:54

                  Итак, мы проверяем знание оператора запятая, хотя из этого мы не можем извлечь никакой информации о знании языка C. Вроде бы как напрашивается вывод…

                  Вы непоследовательны и нелогичны.
                  Я сказал, что это необходимое, но не достаточное условие. Вы понимаете, что это значит? Это значит, что мы проверяем, не отсутствует ли данное знание у человека, и выбраковываем по факту отсутствия в качестве одного из этапов.

                  Не. Это как: «Я взял на улицу зонт, следовательно идет дождь» — не следует.

                  Человек объявляет, что набирает разработчиков, знающих язык Си и проверяет это знание тем или иным образом. Ваш довод, о том, что данный кандидат, зная Си, может не знать других вещей, необходимых в работе, не отменяет необходимость проверки на знание языка. Эти две проверки связаны через конъюнкцию.


      1. amarao
        07.04.2017 14:26
        +2

        Как человек, который по работе больше код читает, чем пишет, могу сказать, что ныряние в Си вызывает наибольшее отвращение. Даже не по причине низкоуровневости, а по причине того, что вместо понятных концепций (что делает инструкция), каждый вздох на си — это поток сайд-эффектов (хороших и плохих), которым позавидует даже брейнфак.


        1. alexeykuzmin0
          07.04.2017 14:39
          +1

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


        1. RomanArzumanyan
          07.04.2017 15:07
          -1

          C понятен и прямолинеен. Смотришь на код и понимаешь, чего ждать в отладчике.


          1. Wedmer
            07.04.2017 17:15
            +1

            Вы нутро Asterisk смотрели?


            1. terrier
              07.04.2017 17:20
              +3

              Ну, из кода Asterisk можно узнать по крайней мере одну важную вещь

              Скрытый текст
              /* Mark: If there's one thing you learn from this code, it is this...
              Never, ever fly Air France. Their customer service is absolutely
              the worst. I've never heard the words "That's not my problem" as
              many times as I have from their staff -- It should, without doubt
              be their corporate motto if it isn't already. Don't bother giving
              them business because you're just a pain in their side and they
              will be sure to let you know the first time you speak to them.
              If you ever want to make me happy just tell me that you, too, will
              never fly Air France again either (in spite of their excellent
              cuisine).
              Update by oej: The merger with KLM has transferred this
              behaviour to KLM as well.
              Don't bother giving them business either...
              Only if you want to travel randomly without luggage, you
              might pick either of them.
              */

              здесь


            1. RomanArzumanyan
              07.04.2017 17:28
              -2

              Много чего смотрел, написанного на С. Это очень маленький и простой язык, в нём легко разобраться. Если вы видите оператор + или ->, можете быть уверены на 100% в том, что будет сделано.


              1. Wedmer
                07.04.2017 17:32
                +1

                Значит вы не писали модули для Asterisk.


              1. nwalker
                07.04.2017 19:23
                +1

                #define + -
                #define -> .

                ?


                1. RomanArzumanyan
                  07.04.2017 19:31
                  -1

                  Первая строка: на работе никогда такого не видел, это из разряда вредительства.
                  Вторая строка: после первой же сборки вражеская закладка будет обнаружена.


                  1. nwalker
                    07.04.2017 19:41

                    после первой же сборки вражеская закладка

                    причем тут враги? может у авторов кодстайл такой альтернативный.
                    и вообще, важно не столько что будет сделано, а с чем — вот тут с помощью препроцессора точно можно отлично разгуляться.


                    разобраться в С просто. а вот в коде на С — посложнее будет


    1. Algoritmist
      07.04.2017 12:27

      Согласен, это кажется бредом, но загляните в сишный код какой-либо старой библиотеки. Вы измените свою точку зрения. Среди железячников такого кода масса. Руки бы им оторвать за такой код.


      1. MacIn
        07.04.2017 18:26
        +1

        А это не проблема Си. Железячники зачастую пишут отвратительный код на любом языке, просто в силу другой специфики.


      1. Kokto
        07.04.2017 22:05

        Как начинавший с машинного кода и ассемблера DEC-овских машин, в языке С вижу только положительные моменты. Всё просто и понятно. И почему С такой каким его задумали.


  1. fishca
    06.04.2017 20:06
    +3

    Вот за эти заковырки многие и любят/ненавидят (кому что больше нравится) С/С++


  1. VioletGiraffe
    06.04.2017 20:19

    Странно. Я пишу на более-менее современном С++, и считаю, что язык С знаю не очень хорошо (как и некоторые заморочки С++). Тем не менее, ровно за 3 минуты написал обвязку, с которой приведенный фрагмент компилируется и даже не падает в рантайме. Это я не для похвастаться, это я к тому, что уж слишком просто :)

    Но есть нюанс: я считерил. Вообще не пришло в голову, что Р можно сделать функцией. Почему-то сразу подумал про макрос, и написал

    #define P(x) 0
    
    (хотя макросы не люблю и без крайней необходимости обычно не использую).


    1. Algoritmist
      07.04.2017 12:42

      Вариант


  1. JTG
    06.04.2017 20:45

    Из той же оперы в Python:

    [r'% r',][~0] % {(): '''^''' uR'|' in 2**2*__name__[::-8//(lambda _:~_)(3 or 2)]*2}<2>3j,(`3`) is ([])
    

    Кто-нибудь скажет, не запуская, чему это будет равно? (отсюда)


    1. GuMondi
      07.04.2017 04:19

      Вообще-то, если попытаться запустить, то падает с ошибкой


    1. Hvorovk
      07.04.2017 04:21

      False?


  1. A1ien
    06.04.2017 20:49

    2 минуты http://cpp.sh/3pmon


    1. Mingun
      06.04.2017 21:41
      +3

      Эк вас занесло. 3 секунды: http://cpp.sh/6wx7


      1. Eivind
        06.04.2017 21:43

        вы читаете мои мысли


        1. Mingun
          06.04.2017 21:44

          Подумал то же самое. Но вы и их прочитали :D


      1. Gumanoid
        06.04.2017 21:44

        1. Mingun
          06.04.2017 21:50

          Оптимизация хвостовой рекурсии с аккумулятором? Круто, давно пора, я наверное еще 2 года назад удивлялся, что ее нет. Самое сложное — представить как можно больше паттернов рекурсий таким образом. Возможно, даже все представимы


          Только что-то из патча не совсем понятно, как это сделано. Просто присвоили одному хитрому макросу результат другого хитрого макроса ???


          1. Mingun
            06.04.2017 21:58

            А, так это первоапрельская шутка… Жаль, а ведь можно было реально сделать подобную оптимизацию (с рекурсией, а не отправкой тел в /dev/null)


            1. Eivind
              06.04.2017 22:11

              Кстати, весьма полезная «оптимизация»: иногда возникает задача создать библиотеку-заглушку для линковки


  1. Eivind
    06.04.2017 21:42
    +13

    #define for(...)
    #define P(...)
    int main() {
        for(;P("\n"),R--;P("|"))for(e=C;e--;P("_"+(*u++/8)%2))P("| "+(*u/4)%2);
    }
    


    1. yarric
      06.04.2017 23:23
      +1

      Интересно, что понимается под словом "работает".


    1. tangro
      07.04.2017 12:21
      +7

      Решение отличное! Дурацкие задачи можно и нужно решать дурацкими способами!


    1. FoxProlog
      07.04.2017 13:01
      +5

      int main() {/*
          for(;P("\n"),R--;P("|"))for(e=C;e--;P("_"+(*u++/8)%2))P("| "+(*u/4)%2);
      */}
      


      1. Algoritmist
        07.04.2017 13:02

        еще return 0; добавить можно


        1. FoxProlog
          07.04.2017 14:14
          +2

          Вот ещё! И так скомпилится.

          Вообще, это — правильный подход на собеседовании: такие куски надо выпиливать без жалости. Повезёт — по тестам сразу восстановить функционал можно. Не повезёт — лучше раньше начать разгребать, чем в ночь перед релизом дебажить такое.


  1. rafuck
    07.04.2017 00:20
    +12

    Блин, я специально не раскрывал подсказки. Думал, тут есть содержательный смысл и все эти вызовы

    void P(const char *s){
        printf("%s", s);
    }
    
    в итоге напечатают красивую картинку в консоли. Понятно, что если сделать
    char *u,
    

    то можно разными строками получать разные картинки. Я пытался сделать
    int k = 8; /* 0, 1, 2 .. 16 */
    int *u = &k;
    

    Ничего красивого не вышло. Потом прочитал спойлеры и разочаровался. И неинтересно, и не смешно. Чувствую себя обманутым! После такого собеседования я бы и сам к вам на работу не пошел!


    1. aragaer
      07.04.2017 11:39

      Аналогично — думал, какие надо угадать значения R, C, и куда направить u, чтобы получилась вразумительная картинка. Остальное все было как-то почти очевидно (опыт кодгольфа дает о себе знать).

      Вопрос к автору — я вместо функции P сделал так:

      #define P(x) printf(x)
      
      Это как-то карается? А еще у меня ни ретурнов, ни инклюдов — ворнинги сыплются, но все работает корректно (опять же, опыт кодгольфа).


      1. Sirikid
        07.04.2017 12:52

        Да, если строка x придет от пользователя запросто может быть UB и перезапись памяти.
        main это особый случай, если в нем нету return то считается что он вернул 0.
        То, что можно использовать функции из libc без инклюдов, особенность работы современных компиляторов.


        1. aragaer
          07.04.2017 13:09

          В данном конкретном примере все вызовы P принимают строковые литералы со смещением 0 или 1, так что все вполне управляемо.

          Да, это main, поэтому я опустил return.

          Функции из libc не имеют отношения к инклюду — инклюд влияет только на подтягивание объявлений функций. Фокус в том, что printf (и поразительно большое число других функций) имеют прототип, эквивалентный прототипу по-умолчанию — возвращают int, принимают то, что в них передали.


          1. Algoritmist
            07.04.2017 13:16

            Спасибо!


      1. Algoritmist
        07.04.2017 13:13

        #define P(x) printf(x)

        Можно и так. Смысл не меняется. Return — будет в ворнинге


    1. Algoritmist
      07.04.2017 12:57

      Я знаю только одного человека, который сделал это задание сразу. (И это был не я)


      1. firegurafiku
        07.04.2017 16:59

        И этот человек догадался, что этот код «рисует лабиринт в stdout»? Лично у меня, когда я увидел эту строчку, возникло желание не понять, что оно делает, а тупо нейтрализовать и просто удовлетворить формальным требованиям (это несложно сделать даже без макросов):


        int main() {
            int R = 0;
            int e = 0;
            int C = 0;
            char *u = 0;
            int (*P)();
        
            for( ; P("\n"), R-- ; P("|"))
                for(e=C; e--; P("_" + ( *u++/8)%2))
                    P("| " + (*u/4)%2);
        
            return 0;
        }

        Или всё-таки предполагается, что на соискатель должен догадаться об оригинальном назначении кода и написать программу, которая бы строила лабиринт?


        1. nwalker
          07.04.2017 17:05
          +1

          Из этого кода, если хоть чуть-чуть вчитаться, очевидно, что он что-то рисует в stdout.


        1. Algoritmist
          07.04.2017 17:09

          Да, просто «тупо нейтрализовать». Лабиринт не нужен. Немного скучноватый вариант вышел (тело циклов не выполняется), но

          int (*P)();
          хороший показатель (одного этого достаточно).


    1. Algoritmist
      07.04.2017 13:06

      Здесь нужен собеседник, который подскажет. Да, это не шутка. u — не одно число, а массив (u++ — гуляет по нему).


      1. rafuck
        07.04.2017 13:32

        Вовсе не обязательно.

        int k = 0;
        int *u = &k;
        void P(const char *s){
        	printf("%s", s);
        
        	if (*s == 0 || *s == '_'){
        		*u--;
        		(*u)++;
        	}
        }
        


  1. aamonster
    07.04.2017 01:16
    +2

    Практически на рефлексе: слова, написанные целиком капсом — макросы. Сразу после этого заставить пример компилироваться и ничего не делать (или делать всё, что нам заблагорассудится) — тривиально (аргументы макросов можно не использовать вообще — пример резко упростится).


    Но разбор прочёл не без интереса.


  1. Kokto
    07.04.2017 11:38

    // Example program
    #include <stdio.h>
    
    extern "C"
    {
    
    int R = 5, *u, e, C, x = 5;
    
    void P( const char * ptr )
    {
        C = R;
        printf( "R:%d, u:%p, e:%d, C:%d, x:%d ptr:%p '%x,%x,%x''%c%c%c'\n"
               , R, u, e, C, x, ptr
               , *(ptr+0), *(ptr+1), *(ptr+2)
               , *(ptr+0), *(ptr+1), *(ptr+2) );
        return;
    }
    
    int main()
    {
     
     u=&x;
     
     for ( ; P("\n"), R--; P("|"))
        for (e = C; e--; P("_" + (*u++ / 8) % 2))
            P("| " + (*u / 4) % 2);
    }
    
    }
    


    1. Algoritmist
      07.04.2017 12:51

      Да, я так первый раз и написал, но u — не одно число, а массив. (u++ — гуляет по нему)
      Лучше С задавать не в функции P.


      1. Kokto
        07.04.2017 13:44

        Это понятно. Мне просто больше нравятся выражения "|/-\"[ i++ % 4 ] для таких извращений.


  1. UnknownUser
    07.04.2017 11:39
    +3

    Думаю, если на собеседовании попадётся такое задание, то правильным ответом будет «Для начала, узнать кто это написал, и если этот человек ещё работает в компании, найти его и долго бить томиком книги за авторством Кернигана-Ритчи».


    1. Algoritmist
      07.04.2017 12:53

      Найти автора я не смог, но похоже это реальный код. Что-то в виде таблицы рисовалось.


      1. UnknownUser
        07.04.2017 13:37

        Возможно, его нашли раньше вас ).
        Нет, действительно, писать всё в одну строчку, плюс все эти *p++, это чистой воды издевательство на тем, кто этот код будет разбирать.
        Ну и в припадке ностальгии, вспомнил знатный холивар с коллегой, который был за if( some ) вместо if( some != 0 ). Хотя, это уже не так страшно и воспринимается более менее нормально.


        1. Kokto
          07.04.2017 13:50

          Раньше компиляторы были не такими умными, и в код вставлялась константа.
          Если написать без "!= 0" то машинный код становился короче. Отсюда такой подход.


  1. vzaburdaev
    07.04.2017 11:39
    +1

    Р — разочарование


    1. Algoritmist
      07.04.2017 12:55

      возможно, но скорее Print


  1. nwalker
    07.04.2017 12:01

    Вы потеряли пробел в строке "_ ", что не лучшим образом сказывается на понимании, что вообще происходит.


    1. Algoritmist
      07.04.2017 12:55

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


      1. nwalker
        07.04.2017 13:25
        +3

        Меня смутило то, что во внутреннем цикле вызывается


        P("| " + BIT_SET(*u, 2));
        P("_" + BIT_SET(*u, 3));
        u++;

        Вроде бы конструкции однотипные, но без пробела логика получается подозрительно разная.


        Так вот, смутило это меня в достаточной степени, чтобы найти


        оригинальный код

        Это один из победителей IOCCC 1985, shapiro.c by Carl Shapiro, код рисует лабиринт в stdout. И да, там есть этот самый пробел. =)


        1. Algoritmist
          07.04.2017 13:42

          Мощь и сила! Спасибо! Я теперь знаю автора


          1. Kokto
            07.04.2017 14:13
            +1

            Да, там ещё есть невероятная мощь: http://www.ioccc.org/2006/toledo2/toledo2.c
            Эмулятор процессора, без проблем запускающий CP/M с BASIC-ом внутри.


          1. d-stream
            07.04.2017 22:47

            По-моему этот же исходник был подписью автора в FIDO

            только почему-то мой склероз в свое время мне подсказывал maze.c а надо было shapiro.c искать


  1. akzhan
    07.04.2017 12:02

    Прочел код, не понял, где там шутка. Вполне все тривиально.


    1. Algoritmist
      07.04.2017 13:19

      Да, шутки тут нет. Она здесь.


  1. logiz
    07.04.2017 13:16

    Если считать, что это задача на C++, то можно просто перегрузить все операторы. Тогда уж можно что хочешь складывать и инкрименировать.


    1. Algoritmist
      07.04.2017 13:17

      И в Си можно складывать и инкриментировать.


  1. Myosotis
    07.04.2017 17:11

    #include <iostream>
    using namespace std;
    
    void P(const char *str) {
         cout << str << endl;
    }
    
    int main() {
        int e; 
        char *u = "11111111111111";
        int R = 3; 
        int C = 5;
        for(; P((char *)"\n"),R--; P((char *)"|")) {
        for(e=C; e--; P("_"+ (*u++/8)%2)) {
            P(" | "+ (*u/4)%2 );
        }
        }
       return 0;
    }

    Выводит:


    | _ | _ | _ | _ | _|
    | _ | _ | _ | _ | _|
    | _ | _ | _ | _ | _|


    1. Algoritmist
      07.04.2017 17:28
      -1

      Да, только:
      1. что-то с отступами не задалось
      2. это С, а не С++ (см. функцию Р)
      3. код править по заданию нельзя. (char *) — зачем?
      Что-то не верю я в то, что она такое выводит.


      1. alexeykuzmin0
        07.04.2017 18:14
        +1

        Слегка подправил код товарища:

        #include <stdio.h>
        
        void P(const char *str) {
            printf("%c", *str);
        }
        
        int main() {
            int e;
            char *u = "11111111111111";
            int R = 3;
            int C = 5;
            for (; P("\n"), R--; P("|"))
                for (e = C; e--; P("_" + (*u++ / 8) % 2))
                    P("| " + (*u / 4) % 2);
            return 0;
        }
        
        Таки выводит, правда, без лишних пробелов (см изменения в P(char*)).


        1. Myosotis
          07.04.2017 18:51

          (см изменения в P(char*)).
          Спасибо, да, я забыла << endl убрать.

          3. код править по заданию нельзя. (char *) — зачем?
          А, его тоже забыла убрать до этого аргумент в функции был не const, поэтому была ошибка
          invalid conversion from 'const char*' to 'char*'


      1. Myosotis
        07.04.2017 19:13

        Теперь берёте на работу?)


        image



  1. maybe_im_a_leo
    09.04.2017 22:21
    +1

    Когда я вижу такие задачи на собеседовании — я говорю "до свидания, всего хорошего вашей компании". Дальше обычно разговаривать не имеет смысла.


  1. Nick_Shl
    10.04.2017 10:07

    Два момента:
    1) В циклах используется не инкремент, а декремент.
    2) Ну им самое веселое:

    Добавляем переменную e, по аналогии с R. Можно сразу объявить и переменную C того же типа, хотя это может быть и константа, или даже define.
    Каким образом константу или дефайн декрементировать? Если еще к константой можно понять — не везде они константы на самом деле, можно взять указатель, сделать преобразование типов на не константный указатель и работать.
    Но вот с дефайном такое не сработает.


    1. Algoritmist
      10.04.2017 10:14

      1. Спасибо, да ошибся. Поправил.
      2. Что-то не понял, где «C» декрементируем?


  1. Algoritmist
    10.04.2017 10:11

    1. Спасибо. Да, ошибся. Поправил.
    2. Что-то не понял, где «C» декрементируем?


  1. Optimus_990
    13.04.2017 09:20

    Забавно сделали)