Тест на знание языка Си, найденный в первоапрельской шутке

Прошло 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
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

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

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

    Очень надеюсь, что это было собеседование с самим собой в зеркале 1-го января и вам не пришло в голову принимать решение о найме программиста на основе этой бредятины.
      +5
      Почему же, очень простая задача, для сишника со стажем более полутора лет. Только вот, к сожалению, с каждым годом новых специалистов по C/C++ все меньше и меньше :(
        +2
        Работу найти сложней, кнопочки на JS ляпать проше и работу найти легче.
          0
          Так, казалось бы, и денег больше, чем в кнопочках на JS
            +5

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

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

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

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

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

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

            Была одно время мода на такого рода собеседования ( там еще триграфы любили спрашивать ), но казалось бы уже прошла.
              –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;
                0
                На ARM мы получим исключение если участок памяти находится между сегментами

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

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

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


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

                      0
                      Ну и пример кода который должен вызывать 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;
                      }
                      }
                        0
                        -mstructure-size-boundary для gcc не спасает?
                          0
                          Извините, а изменение выравнивания границ структур может повлиять на работу процессора?
                          Мне кажется это только флаг компилятора который генерирует код.

                          В примере мы готовим абстрактный блок данных для отдачи по сети, согласно абстрактному же протоколу передачи, и читаем его после получения. И вот в том случае если слово или двойное слово попадает между физических страниц памяти — при попытке его прочитать будет исключение. Грубо говоря на x86 ASM мы получим нечто такое:
                          mov ebx,[edx]
                          И при выполнении этой инструкции вылетит ошибка, на интеле такой ошибки никогда не возникнет.
                            0
                            У меня софт крутится на x86, x86_64, MIPS, ARM. Структурки выравниваются по байту.
                            На одном компиляторе используется этот ключ, тк прагма не работает. И ничего не крашится.
                              0
                              Спасибо, попробую на тестовой площадке собрать код и погонять, может как то повлияет, хотя сильно на это не надеюсь.
                  –2
                  На то он и тест на уровень знания. Он не дает ответа «ДА» или «НЕТ». Тут много исходов. И обычно тесты не является основанием приема на работу или отказа, а просто тема для разговора.
                    0
                    на котором кандидату ( видимо на должность C программиста ) задавали бы вопросы про оператор «запятая», сложение числа со строкой и приоритет операций? Готов человек блестяще решивший эту задачку писать операционные системы, базы данных или какие-нибудь cache oblivious алгоритмы

                    Так это совершенно разные проверки. Если вам нужен человек знающий хорошо язык XYZ и при этом владеющий дополнительно навыками A, B, C, проверять вы будете и то, и другое, верно?
                      +1
                      Тут три момента:
                      1). Категорически неочевидно как из блестящего владения оператором «запятая» следует хорошее знание языка C
                      2). Кроме того само по себе знание C не нужно никакому бизнесу, кроме разработчиков компиляторов. «Знаешь C? Ну, возьми с полки пирожок. Нам интересно как ты поможешь нам в разработке нашего вэб-сервера».
                      3). Собеседование — это формат переговоров, весьма сжатый по времени. В текущей реальности только гугл и сопоставимые компании могут мурыжить кандидатов 6-ю собеседованиями, для более приземленных компаний нужно за час-полтора принять решение о, том, будем ли мы сотрудничать с этим человеком. Тратить это время на разговоры об операторе запятая — это похоже на «Окей, леди и джентльмены, у нас час на то, чтобы заключить эту важную для всех нас сделку, так что давайте для начала станцуем танец маленьких утят»
                        0
                        1). Теперь и Вы знаете кун-фу оператор «запятая»
                        2). Нужно, если работаете программистом на С. Вы не поверите, но есть устройства, где язык С единственный язык программирования.
                        3). Согласен.
                          0
                          к п.2) да, еще про ассемблер забыл
                            0
                            2). Видимо не совсем четко сформулировал. Даже если программист пишет код на C на платформе, на которой кроме C ничего нет, платят ему не за то, что он печатает «int main()» и т.д., а за то, что он заставляет эти несчастные микроконтроллеры контролировать станки, собирать данные о температуре и так далее. Само по себе знание С ничего не стоит, оно должно быть частью гораздо более широкого набора умений. А вот это уже оплачивается
                              0
                              Угу! Полностью поддерживаю. За знание языка не платя, если не даешь результат. Язык это средство в достижении конкретной задачи. А эта задача — средство достижения задач других уровней.
                              Фундаментальные мировоззренческие вещи затронули.
                                0
                                Мне нравится сравнение программирования с написанием стихов на японском. Японский знать, конечно, надо, но это не главный навык.
                            0
                            1). Категорически неочевидно как из блестящего владения оператором «запятая» следует хорошее знание языка C

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

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

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

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

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

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

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

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

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

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

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

                                Человек объявляет, что набирает разработчиков, знающих язык Си и проверяет это знание тем или иным образом. Ваш довод, о том, что данный кандидат, зная Си, может не знать других вещей, необходимых в работе, не отменяет необходимость проверки на знание языка. Эти две проверки связаны через конъюнкцию.
                        +2
                        Как человек, который по работе больше код читает, чем пишет, могу сказать, что ныряние в Си вызывает наибольшее отвращение. Даже не по причине низкоуровневости, а по причине того, что вместо понятных концепций (что делает инструкция), каждый вздох на си — это поток сайд-эффектов (хороших и плохих), которым позавидует даже брейнфак.
                          +1
                          Новый код в этом плане уже получше. Вообще, на мой взгляд, почти в любом проекте главное, что требуется от разработчика — читаемость кода. А с таким подходом можно и на C нормально написать.
                            –1
                            C понятен и прямолинеен. Смотришь на код и понимаешь, чего ждать в отладчике.
                              +1
                              Вы нутро Asterisk смотрели?
                                +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.
                                */

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

                                      ?

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

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


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

                              0
                              Согласен, это кажется бредом, но загляните в сишный код какой-либо старой библиотеки. Вы измените свою точку зрения. Среди железячников такого кода масса. Руки бы им оторвать за такой код.
                                +1
                                А это не проблема Си. Железячники зачастую пишут отвратительный код на любом языке, просто в силу другой специфики.
                                  0
                                  Как начинавший с машинного кода и ассемблера DEC-овских машин, в языке С вижу только положительные моменты. Всё просто и понятно. И почему С такой каким его задумали.
                                +3
                                Вот за эти заковырки многие и любят/ненавидят (кому что больше нравится) С/С++
                                  0
                                  Странно. Я пишу на более-менее современном С++, и считаю, что язык С знаю не очень хорошо (как и некоторые заморочки С++). Тем не менее, ровно за 3 минуты написал обвязку, с которой приведенный фрагмент компилируется и даже не падает в рантайме. Это я не для похвастаться, это я к тому, что уж слишком просто :)

                                  Но есть нюанс: я считерил. Вообще не пришло в голову, что Р можно сделать функцией. Почему-то сразу подумал про макрос, и написал
                                  #define P(x) 0
                                  
                                  (хотя макросы не люблю и без крайней необходимости обычно не использую).
                                  0
                                  Из той же оперы в Python:

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

                                  Кто-нибудь скажет, не запуская, чему это будет равно? (отсюда)
                                    0
                                    Вообще-то, если попытаться запустить, то падает с ошибкой
                                      0
                                      False?
                                      0
                                      2 минуты http://cpp.sh/3pmon
                                        +3

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

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

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

                                            0
                                              0

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


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

                                                0

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

                                                  0
                                                  Кстати, весьма полезная «оптимизация»: иногда возникает задача создать библиотеку-заглушку для линковки
                                          +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

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

                                              +7
                                              Решение отличное! Дурацкие задачи можно и нужно решать дурацкими способами!
                                                +5
                                                int main() {/*
                                                    for(;P("\n"),R--;P("|"))for(e=C;e--;P("_"+(*u++/8)%2))P("| "+(*u/4)%2);
                                                */}
                                                
                                                  0
                                                  еще return 0; добавить можно
                                                    +2
                                                    Вот ещё! И так скомпилится.

                                                    Вообще, это — правильный подход на собеседовании: такие куски надо выпиливать без жалости. Повезёт — по тестам сразу восстановить функционал можно. Не повезёт — лучше раньше начать разгребать, чем в ночь перед релизом дебажить такое.
                                                +12
                                                Блин, я специально не раскрывал подсказки. Думал, тут есть содержательный смысл и все эти вызовы
                                                void P(const char *s){
                                                    printf("%s", s);
                                                }
                                                
                                                в итоге напечатают красивую картинку в консоли. Понятно, что если сделать
                                                char *u,
                                                

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

                                                Ничего красивого не вышло. Потом прочитал спойлеры и разочаровался. И неинтересно, и не смешно. Чувствую себя обманутым! После такого собеседования я бы и сам к вам на работу не пошел!
                                                  0
                                                  Аналогично — думал, какие надо угадать значения R, C, и куда направить u, чтобы получилась вразумительная картинка. Остальное все было как-то почти очевидно (опыт кодгольфа дает о себе знать).

                                                  Вопрос к автору — я вместо функции P сделал так:
                                                  #define P(x) printf(x)
                                                  
                                                  Это как-то карается? А еще у меня ни ретурнов, ни инклюдов — ворнинги сыплются, но все работает корректно (опять же, опыт кодгольфа).
                                                  • НЛО прилетело и опубликовало эту надпись здесь
                                                      0
                                                      В данном конкретном примере все вызовы P принимают строковые литералы со смещением 0 или 1, так что все вполне управляемо.

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

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

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

                                                      И этот человек догадался, что этот код «рисует лабиринт в 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

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

                                                          0
                                                          Да, просто «тупо нейтрализовать». Лабиринт не нужен. Немного скучноватый вариант вышел (тело циклов не выполняется), но
                                                          int (*P)();
                                                          хороший показатель (одного этого достаточно).
                                                        0
                                                        Здесь нужен собеседник, который подскажет. Да, это не шутка. u — не одно число, а массив (u++ — гуляет по нему).
                                                          0
                                                          Вовсе не обязательно.
                                                          int k = 0;
                                                          int *u = &k;
                                                          void P(const char *s){
                                                          	printf("%s", s);
                                                          
                                                          	if (*s == 0 || *s == '_'){
                                                          		*u--;
                                                          		(*u)++;
                                                          	}
                                                          }
                                                          
                                                        +2

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


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

                                                          0
                                                          // 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);
                                                          }
                                                          
                                                          }
                                                          
                                                            0
                                                            Да, я так первый раз и написал, но u — не одно число, а массив. (u++ — гуляет по нему)
                                                            Лучше С задавать не в функции P.
                                                              0
                                                              Это понятно. Мне просто больше нравятся выражения "|/-\"[ i++ % 4 ] для таких извращений.
                                                            +3
                                                            Думаю, если на собеседовании попадётся такое задание, то правильным ответом будет «Для начала, узнать кто это написал, и если этот человек ещё работает в компании, найти его и долго бить томиком книги за авторством Кернигана-Ритчи».
                                                              0
                                                              Найти автора я не смог, но похоже это реальный код. Что-то в виде таблицы рисовалось.
                                                                0
                                                                Возможно, его нашли раньше вас ).
                                                                Нет, действительно, писать всё в одну строчку, плюс все эти *p++, это чистой воды издевательство на тем, кто этот код будет разбирать.
                                                                Ну и в припадке ностальгии, вспомнил знатный холивар с коллегой, который был за if( some ) вместо if( some != 0 ). Хотя, это уже не так страшно и воспринимается более менее нормально.
                                                                  0
                                                                  Раньше компиляторы были не такими умными, и в код вставлялась константа.
                                                                  Если написать без "!= 0" то машинный код становился короче. Отсюда такой подход.
                                                              +1
                                                              Р — разочарование
                                                                0
                                                                возможно, но скорее Print
                                                                0

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

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

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


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

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


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


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

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


                                                                    В процессе поиска нашелся еще и занятный пост про обфускацию C и лабиринты.

                                                                      0
                                                                      Мощь и сила! Спасибо! Я теперь знаю автора
                                                                        +1
                                                                        Да, там ещё есть невероятная мощь: http://www.ioccc.org/2006/toledo2/toledo2.c
                                                                        Эмулятор процессора, без проблем запускающий CP/M с BASIC-ом внутри.
                                                                          0
                                                                          По-моему этот же исходник был подписью автора в FIDO

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

                                                                    0
                                                                    Прочел код, не понял, где там шутка. Вполне все тривиально.
                                                                    0
                                                                    Если считать, что это задача на C++, то можно просто перегрузить все операторы. Тогда уж можно что хочешь складывать и инкрименировать.
                                                                      0
                                                                      И в Си можно складывать и инкриментировать.
                                                                      0
                                                                      #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
                                                                        Да, только:
                                                                        1. что-то с отступами не задалось
                                                                        2. это С, а не С++ (см. функцию Р)
                                                                        3. код править по заданию нельзя. (char *) — зачем?
                                                                        Что-то не верю я в то, что она такое выводит.
                                                                          +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*)).
                                                                            0
                                                                            (см изменения в P(char*)).
                                                                            Спасибо, да, я забыла << endl убрать.

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

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


                                                                            image

                                                                        +1

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

                                                                          0
                                                                          Два момента:
                                                                          1) В циклах используется не инкремент, а декремент.
                                                                          2) Ну им самое веселое:
                                                                          Добавляем переменную e, по аналогии с R. Можно сразу объявить и переменную C того же типа, хотя это может быть и константа, или даже define.
                                                                          Каким образом константу или дефайн декрементировать? Если еще к константой можно понять — не везде они константы на самом деле, можно взять указатель, сделать преобразование типов на не константный указатель и работать.
                                                                          Но вот с дефайном такое не сработает.
                                                                            0
                                                                            1. Спасибо, да ошибся. Поправил.
                                                                            2. Что-то не понял, где «C» декрементируем?
                                                                            0
                                                                            1. Спасибо. Да, ошибся. Поправил.
                                                                            2. Что-то не понял, где «C» декрементируем?
                                                                              0
                                                                              Забавно сделали)

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

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