Прошло 1 апреля. Часто первоапрельские шутки, выложенные в Интернете, продолжают свое шествие, и всплывают совершенно в неожиданное время. О такой шутке про язык Си и будет эта статья. В каждой шутке есть только доля шутки. Код из нее я взял на вооружение для беглого тестирования на знание языка Си.
Надо написать программу (с пояснениями), в которой будет работать следующая строка:
Всего одна строка, но по ней можно определить глубину понимания человеком языка Си. Эта строка будет работать также и на С++. Советую попробовать свои силы. Смешно не будет. Возможно будет полезно.
На заре своей карьеры программиста, мне друг показал статью про то, что язык Си и UNIX первоапрельская шутка. В качестве доказательства абсурдности языка приводилась вышеприведенная строка кода. На мой взгляд, вполне рабочая. Через некоторое время при проведении собеседования вспомнилась эта шутка. Как и при решении многих других тестов, здесь важен не результат (он задает цель работы), а сам процесс разбора и понимания.
Где мы взяли статью я уже не помню. Каждый раз его нахожу в поисковике по фразе «си и unix первоапрельская шутка» (например тут). В этих репостах когда-то потерялся один минус в инкременте после «R» и «e», и появился второй обратный слеш в строке "\n".
Попробуйте разобраться с заданием сами. Не задумывайтесь пока над смыслом программы.
Специально не буду давать текст программы. Считаю, что после всех подсказок эту программу можно легко написать.
Если Вы думаете, что это выдуманный пример, то я могу поспорить. Реально попадается гораздо более тяжелый для разбора код. И не подумайте, что я призываю писать такой ужас.
Для тех, кто попадет на такое тестирование: тут главное не пугаться, вас вероятно возьмут на работу и без этого задания.
Для тех, кто захочет использовать на собеседование: если Вы решите дать эту задачку на собеседование, а в глазах у соискателя блеснет улыбка, то это значит, что вы оба читали этот пост. Но Вы не расстраивайтесь, пусть повторит с объяснением…
Для читателей: я знаю только одного человека, который сделал это задание сразу (и это был не я).
P.S.
Спасибо nwalker:
«Это один из победителей IOCCC 1985, shapiro.c by Carl Shapiro, код рисует лабиринт в stdout.… В процессе поиска нашелся еще и занятный пост про обфускацию C и лабиринты: The art of obfuscation.»
Надо написать программу (с пояснениями), в которой будет работать следующая строка:
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? И нужен ли он здесь? Можно ли без return? И совсем жестокий вопрос. Какие параметры у функции main?
Не поленитесь, и попробуйте ответить на эти вопросы сами.
#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?
Не поленитесь, и попробуйте ответить на эти вопросы сами.
Разбор внешнего цикла
Если человек успешно дошел до этого этапа, то он уже понимает, что есть два вложенных цикла. Разберем внешний.
Здесь встречаем совсем простую проблемку. Нет инициализатора (после открытой скобки идет сразу точка с запятой). Некоторых это смущает. Это часто бывает, если человек пишет программы на другом языке, например, на Паскале.
Настоящим камнем преткновения, даже у достаточно опытных программистов, встречает выражение «P("\n"), R--». Многие просто не знают, что есть такая операция «запятая», и что результатом его работы будет результат выражения, стоящего после запятой. Выражение до запятой тоже вычисляется, но его результат не используется. Причем эта операция имеет самый низкий приоритет . Следовательно, сначала выполняется P("\n"), а потом R--.
Результат выражения R-- здесь является условием выполнения. Это тоже некоторых смущает, хотя этот прием часто используется. Многие программисты считают излишним писать в условных операторах if, выражения типа if (a != 0) … Тут аналогичный случай (R-- != 0). Настала пора добавить объявление первой переменной. Инкремент говорит о том, что это точно не вещественное число. Подойдет любой целочисленный тип, даже беззнаковый. Эту переменную надо не только объявить, но и проинициализировать каким-либо положительным значением (лучше небольшим).
Обычно, дойдя до сюда, всем уже ясно, что есть функция P, которая принимает на вход строку. Тут проблем уже нет. Надо объявить эту функцию. Поскольку смысл нам не важен, то она может быть даже пустой. Мне больше нравится функция, выводящая текст на экран (тут и пригодился заранее написанный #include <stdio.h>). Считаю, что эту функцию должен уметь писать программист любой уровня.
for ( ; P("\n"), R--; P("|"))
Здесь встречаем совсем простую проблемку. Нет инициализатора (после открытой скобки идет сразу точка с запятой). Некоторых это смущает. Это часто бывает, если человек пишет программы на другом языке, например, на Паскале.
Настоящим камнем преткновения, даже у достаточно опытных программистов, встречает выражение «P("\n"), R--». Многие просто не знают, что есть такая операция «запятая», и что результатом его работы будет результат выражения, стоящего после запятой. Выражение до запятой тоже вычисляется, но его результат не используется. Причем эта операция имеет самый низкий приоритет . Следовательно, сначала выполняется P("\n"), а потом R--.
Результат выражения R-- здесь является условием выполнения. Это тоже некоторых смущает, хотя этот прием часто используется. Многие программисты считают излишним писать в условных операторах if, выражения типа if (a != 0) … Тут аналогичный случай (R-- != 0). Настала пора добавить объявление первой переменной. Инкремент говорит о том, что это точно не вещественное число. Подойдет любой целочисленный тип, даже беззнаковый. Эту переменную надо не только объявить, но и проинициализировать каким-либо положительным значением (лучше небольшим).
Обычно, дойдя до сюда, всем уже ясно, что есть функция P, которая принимает на вход строку. Тут проблем уже нет. Надо объявить эту функцию. Поскольку смысл нам не важен, то она может быть даже пустой. Мне больше нравится функция, выводящая текст на экран (тут и пригодился заранее написанный #include <stdio.h>). Считаю, что эту функцию должен уметь писать программист любой уровня.
Разбор внутреннего цикла
Здесь в цикле уже все знакомо. Декремент в проверке на выполнении цикла, как было выше. Добавляем переменную e, по аналогии с R. Можно сразу объявить и переменную C того же типа, хотя это может быть и константа, или даже define. Тут воля автора.
Интерес тут вызывает вызов функции P.
Если посмотреть дальше, то мы увидим в теле функции подобную конструкцию.
Тут стоит набраться терпения. Цель близка. Это венец этого «шедевра». Не спешите открывать следующее разъяснение, подумайте.
for (e = C; e--; P("_" + (*u++ / 8) % 2) )
Здесь в цикле уже все знакомо. Декремент в проверке на выполнении цикла, как было выше. Добавляем переменную e, по аналогии с R. Можно сразу объявить и переменную C того же типа, хотя это может быть и константа, или даже define. Тут воля автора.
Интерес тут вызывает вызов функции P.
P("_" + (*u++ / 8) % 2)
Если посмотреть дальше, то мы увидим в теле функции подобную конструкцию.
P("| " + (*u / 4) % 2);
Тут стоит набраться терпения. Цель близка. Это венец этого «шедевра». Не спешите открывать следующее разъяснение, подумайте.
Изюминка
Разбираем два выражения:
Далее будем рассматривать первое выражение. Оно более сложное. Понятно, что здесь сперва вычисляется выражение в скобках, потом берется от него остаток от деления на 2, в конце это число добавляется к строке.
Самое простое, это вычисление остатка от деления. Изредка встречаются программисты не использующие такую операцию. Они могут смутиться. Главное, то что эта операция производится над целочисленными типами и результат тоже целочисленный. Коварный вопрос для самостоятельной проработки, может ли быть результат выражения (*u++ / 8) % 2 отрицательным?
Поскольку результат выражения в скобках должен быть целочисленным, то и операция деление целочисленное, и делимое целочисленное. У начинающих программистов выражение *u++ может вызвать неуверенность: в наличии постинкремента в выражении и в приоритете выполнения операций постинкремента и разыменование указателя. Данный прием иногда используется в программах на Си при движении по массиву. Выражение возвращает значение по текущему указателю (до инкрементации) и смещает указатель на следующий элемент. Следовательно, переменная u не просто указатель, но и является массивом. Дополнительный вопрос, какого размера (в элементах) должен быть этот массив?
Самый «красивый» прием – это прибавление числа к строке. Надо помнить, что это язык Си. Не стоит ждать преобразования числа в строку, а тем более строки в число. Все гораздо более странно, чем может показаться с первого взгляда, но очень логично для Си. Строка – это массив символов, а значит указатель на память, где находится первый символ. Если это указатель, то прибавление к нему целого числа означает вычисление адреса, сдвинутого относительно исходного указателя на заданное число элементов. В данном примере после получения остатка от деления на 2 выходит либо 0, либо 1. Соответственно, либо строку передаем в функцию P без смещения (как есть), либо смещаем на один символ в конец строки. Простой вопрос, могут ли возникнуть проблемы при смещении на один символ в строке, состоящей из одного символа (как в нашем случае)?
Выражение (X / 8) % 2 – это просто получение четвертого бита. Для беззнакового целого числа это эквивалентно (X >> 3) & 1. И в заключении, дополнительное задание – проверить это утверждение для отрицательных чисел.
"_" + (*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.»
Поделиться с друзьями
terrier
Очень надеюсь, что это было собеседование с самим собой в зеркале 1-го января и вам не пришло в голову принимать решение о найме программиста на основе этой бредятины.
Lirein
Почему же, очень простая задача, для сишника со стажем более полутора лет. Только вот, к сожалению, с каждым годом новых специалистов по C/C++ все меньше и меньше :(
MaM
Работу найти сложней, кнопочки на JS ляпать проше и работу найти легче.
alexeykuzmin0
Так, казалось бы, и денег больше, чем в кнопочках на JS
domix32
Да не сказал бы. Посмотреть тот же hh и сравнить вакансии c++ и какой-нибудь node.js
Drag13
А причем тут кнопочки и Node.js?
domix32
Ну, я думаю что в случае ключевых слов JS и работа — node.js первое, что приходит в голову. Все эти angular/reactive/matreshka, которые через здоровенный автоматизированный пайплайн выдают пользователю кнопочку "сделать хорошо" на одностраничнике. Мало кого сейчас интересуют рисователи кнопочек на jQuery, если вообще интересуют.
Drag13
Просто Node.js это как раз серверная часть а не UI.
domix32
Аналогично PHP работает на серверной стороне, да и также как и PHP по большей части нацелена на веб. Можно конечно проворачивать такое и с c++, но едва ли такая вакансия будет настолько же оплачиваема и востребована как знаток ноды или того же пхп.
От минусанувшего хотелось бы узнать что не так с предыдущим комментарием.
alexeykuzmin0
Перепроверил — признаю, был неправ.
terrier
Сама по себе задача простая без вопросов, вполне можно развлечь себя ей. Однако — смотрите, как бы вы прокомментировали собеседование, на котором кандидату ( видимо на должность C программиста ) задавали бы вопросы про оператор «запятая», сложение числа со строкой и приоритет операций? Готов человек блестяще решивший эту задачку писать операционные системы, базы данных или какие-нибудь cache oblivious алгоритмы? Ведь сейчас перед программистами С стоят уже в основном действительно сложные и нетривиальные задачи ( все простое пишем на питоне).
Была одно время мода на такого рода собеседования ( там еще триграфы любили спрашивать ), но казалось бы уже прошла.
Lirein
Я при поиске 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;
apro
А можете пояснить, что вы подразумеваете под понятием "сегментам" на ARM,
и ARM с MMU или с MPU имеется ввиду?
Lirein
Особой разницы нет, на большинстве RISC архитектур, и вышедших из чистой архитектуры RISC используется страничная адресация памяти, MMU выделит нам конечное количество страниц. Проблема будет если мы создадим структуру без выравнивания, например для передачи по сети. Как мы помним выравнивание на разных платформах разное. При использовании структур с выравниванием у нас никогда слово не окажется между двух физических сегментов памяти. в виртуальном адресном пространстве они могут находиться линейно и побайтово прекрасно читаться, но попытка сделать вот так: uint16_t val = my_struct->someval если данные попали между двух физических сегментов — вызывает аппаратное исключение. Как workaround используется memcpy такого блока в выравненную структуру в памяти или в локальную переменную и чтение уже оттуда, или чтение побайтово (зависит от задачи и размера читаемых данных). Ошибки такого рода проявляются не сразу а при значительных объемах линейно выделенной памяти для не выровненных структур (накопление сетевого буффера перед отдачей клиенту и отдельный поток фильтрации, например).
apro
Про проблемы с выравниванием я знаю, и по крайней мере у gcc есть несколько ключей,
для генерации предупреждений по этому поводу, на
x86
с этим тоже можно кстати столкнуться,например если компилятору "сказать" что данные выравнены и заставить его сгенерировать код для работы скажем с AVX, а потом дать на вход невыровненные данные.
Меня интересовало как раз что за сегменты (на arm скажем никаких сегментов в терминах i386 нет) и что за дырки между ними, но судя по все это было просто недопонимание.
Lirein
Ну и пример кода который должен вызывать 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;
}
}
Wedmer
-mstructure-size-boundary для gcc не спасает?
Lirein
Извините, а изменение выравнивания границ структур может повлиять на работу процессора?
Мне кажется это только флаг компилятора который генерирует код.
В примере мы готовим абстрактный блок данных для отдачи по сети, согласно абстрактному же протоколу передачи, и читаем его после получения. И вот в том случае если слово или двойное слово попадает между физических страниц памяти — при попытке его прочитать будет исключение. Грубо говоря на x86 ASM мы получим нечто такое:
mov ebx,[edx]
И при выполнении этой инструкции вылетит ошибка, на интеле такой ошибки никогда не возникнет.
Wedmer
У меня софт крутится на x86, x86_64, MIPS, ARM. Структурки выравниваются по байту.
На одном компиляторе используется этот ключ, тк прагма не работает. И ничего не крашится.
Lirein
Спасибо, попробую на тестовой площадке собрать код и погонять, может как то повлияет, хотя сильно на это не надеюсь.
Algoritmist
На то он и тест на уровень знания. Он не дает ответа «ДА» или «НЕТ». Тут много исходов. И обычно тесты не является основанием приема на работу или отказа, а просто тема для разговора.
MacIn
Так это совершенно разные проверки. Если вам нужен человек знающий хорошо язык XYZ и при этом владеющий дополнительно навыками A, B, C, проверять вы будете и то, и другое, верно?
terrier
Тут три момента:
1). Категорически неочевидно как из блестящего владения оператором «запятая» следует хорошее знание языка C
2). Кроме того само по себе знание C не нужно никакому бизнесу, кроме разработчиков компиляторов. «Знаешь C? Ну, возьми с полки пирожок. Нам интересно как ты поможешь нам в разработке нашего вэб-сервера».
3). Собеседование — это формат переговоров, весьма сжатый по времени. В текущей реальности только гугл и сопоставимые компании могут мурыжить кандидатов 6-ю собеседованиями, для более приземленных компаний нужно за час-полтора принять решение о, том, будем ли мы сотрудничать с этим человеком. Тратить это время на разговоры об операторе запятая — это похоже на «Окей, леди и джентльмены, у нас час на то, чтобы заключить эту важную для всех нас сделку, так что давайте для начала станцуем танец маленьких утят»
Algoritmist
1). Теперь и Вы знаете
кун-фуоператор «запятая»2). Нужно, если работаете программистом на С. Вы не поверите, но есть устройства, где язык С единственный язык программирования.
3). Согласен.
Algoritmist
к п.2) да, еще про ассемблер забыл
terrier
2). Видимо не совсем четко сформулировал. Даже если программист пишет код на C на платформе, на которой кроме C ничего нет, платят ему не за то, что он печатает «int main()» и т.д., а за то, что он заставляет эти несчастные микроконтроллеры контролировать станки, собирать данные о температуре и так далее. Само по себе знание С ничего не стоит, оно должно быть частью гораздо более широкого набора умений. А вот это уже оплачивается
Algoritmist
Угу! Полностью поддерживаю. За знание языка не платя, если не даешь результат. Язык это средство в достижении конкретной задачи. А эта задача — средство достижения задач других уровней.
Фундаментальные мировоззренческие вещи затронули.
alexeykuzmin0
Мне нравится сравнение программирования с написанием стихов на японском. Японский знать, конечно, надо, но это не главный навык.
MacIn
Никак. Необходимое, но не достаточное условие.
Ну, наверно, если гражданин нанимает работников, проверяя знание языка Си, то именно им он как раз и нужен, не? Знание этого языка является необходимым условием для приема на работу именно в эту фирму. Необходимым, т.е. проверка на знание A, B и C из моего предыдущего комментария последует отдельно.
Даже в маленьких конторах проводят по 2-3 собеседования, это нормально.
terrier
Итак, мы проверяем знание оператора запятая, хотя из этого мы не можем извлечь никакой информации о знании языка C. Вроде бы как напрашивается вывод…
Не. Это как: «Я взял на улицу зонт, следовательно идет дождь» — не следует.
У меня другое мнение и оно подтверждается ( моей ) практикой, но это уже совсем оффтопик. В любом случае, даже если у нас 3 собеседования — тратить время просто так не надо.
MacIn
Вы непоследовательны и нелогичны.
Я сказал, что это необходимое, но не достаточное условие. Вы понимаете, что это значит? Это значит, что мы проверяем, не отсутствует ли данное знание у человека, и выбраковываем по факту отсутствия в качестве одного из этапов.
Человек объявляет, что набирает разработчиков, знающих язык Си и проверяет это знание тем или иным образом. Ваш довод, о том, что данный кандидат, зная Си, может не знать других вещей, необходимых в работе, не отменяет необходимость проверки на знание языка. Эти две проверки связаны через конъюнкцию.
amarao
Как человек, который по работе больше код читает, чем пишет, могу сказать, что ныряние в Си вызывает наибольшее отвращение. Даже не по причине низкоуровневости, а по причине того, что вместо понятных концепций (что делает инструкция), каждый вздох на си — это поток сайд-эффектов (хороших и плохих), которым позавидует даже брейнфак.
alexeykuzmin0
Новый код в этом плане уже получше. Вообще, на мой взгляд, почти в любом проекте главное, что требуется от разработчика — читаемость кода. А с таким подходом можно и на C нормально написать.
RomanArzumanyan
C понятен и прямолинеен. Смотришь на код и понимаешь, чего ждать в отладчике.
Wedmer
Вы нутро Asterisk смотрели?
terrier
Ну, из кода 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.
*/
здесь
RomanArzumanyan
Много чего смотрел, написанного на С. Это очень маленький и простой язык, в нём легко разобраться. Если вы видите оператор + или ->, можете быть уверены на 100% в том, что будет сделано.
Wedmer
Значит вы не писали модули для Asterisk.
nwalker
?
RomanArzumanyan
Первая строка: на работе никогда такого не видел, это из разряда вредительства.
Вторая строка: после первой же сборки вражеская закладка будет обнаружена.
nwalker
причем тут враги? может у авторов кодстайл такой альтернативный.
и вообще, важно не столько что будет сделано, а с чем — вот тут с помощью препроцессора точно можно отлично разгуляться.
разобраться в С просто. а вот в коде на С — посложнее будет
Algoritmist
Согласен, это кажется бредом, но загляните в сишный код какой-либо старой библиотеки. Вы измените свою точку зрения. Среди железячников такого кода масса. Руки бы им оторвать за такой код.
MacIn
А это не проблема Си. Железячники зачастую пишут отвратительный код на любом языке, просто в силу другой специфики.
Kokto
Как начинавший с машинного кода и ассемблера DEC-овских машин, в языке С вижу только положительные моменты. Всё просто и понятно. И почему С такой каким его задумали.
fishca
Вот за эти заковырки многие и любят/ненавидят (кому что больше нравится) С/С++
VioletGiraffe
Странно. Я пишу на более-менее современном С++, и считаю, что язык С знаю не очень хорошо (как и некоторые заморочки С++). Тем не менее, ровно за 3 минуты написал обвязку, с которой приведенный фрагмент компилируется и даже не падает в рантайме. Это я не для похвастаться, это я к тому, что уж слишком просто :)
(хотя макросы не люблю и без крайней необходимости обычно не использую).Но есть нюанс: я считерил. Вообще не пришло в голову, что Р можно сделать функцией. Почему-то сразу подумал про макрос, и написал
Algoritmist
Вариант
JTG
Из той же оперы в Python:
Кто-нибудь скажет, не запуская, чему это будет равно? (отсюда)
GuMondi
Вообще-то, если попытаться запустить, то падает с ошибкой
Hvorovk
False?
A1ien
2 минуты http://cpp.sh/3pmon
Mingun
Эк вас занесло. 3 секунды: http://cpp.sh/6wx7
Eivind
вы читаете мои мысли
Mingun
Подумал то же самое. Но вы и их прочитали :D
Gumanoid
Похоже на эту оптимизацию в GCC.
Mingun
Оптимизация хвостовой рекурсии с аккумулятором? Круто, давно пора, я наверное еще 2 года назад удивлялся, что ее нет. Самое сложное — представить как можно больше паттернов рекурсий таким образом. Возможно, даже все представимы
Только что-то из патча не совсем понятно, как это сделано. Просто присвоили одному хитрому макросу результат другого хитрого макроса ???
Mingun
А, так это первоапрельская шутка… Жаль, а ведь можно было реально сделать подобную оптимизацию (с рекурсией, а не отправкой тел в
/dev/null
)Eivind
Кстати, весьма полезная «оптимизация»: иногда возникает задача создать библиотеку-заглушку для линковки
Eivind
yarric
Интересно, что понимается под словом "работает".
tangro
Решение отличное! Дурацкие задачи можно и нужно решать дурацкими способами!
FoxProlog
Algoritmist
еще return 0; добавить можно
FoxProlog
Вот ещё! И так скомпилится.
Вообще, это — правильный подход на собеседовании: такие куски надо выпиливать без жалости. Повезёт — по тестам сразу восстановить функционал можно. Не повезёт — лучше раньше начать разгребать, чем в ночь перед релизом дебажить такое.
rafuck
Блин, я специально не раскрывал подсказки. Думал, тут есть содержательный смысл и все эти вызовы
в итоге напечатают красивую картинку в консоли. Понятно, что если сделатьто можно разными строками получать разные картинки. Я пытался сделать
Ничего красивого не вышло. Потом прочитал спойлеры и разочаровался. И неинтересно, и не смешно. Чувствую себя обманутым! После такого собеседования я бы и сам к вам на работу не пошел!
aragaer
Аналогично — думал, какие надо угадать значения R, C, и куда направить u, чтобы получилась вразумительная картинка. Остальное все было как-то почти очевидно (опыт кодгольфа дает о себе знать).
Это как-то карается? А еще у меня ни ретурнов, ни инклюдов — ворнинги сыплются, но все работает корректно (опять же, опыт кодгольфа).Вопрос к автору — я вместо функции P сделал так:
Sirikid
Да, если строка x придет от пользователя запросто может быть UB и перезапись памяти.
main
это особый случай, если в нем нетуreturn
то считается что он вернул0
.То, что можно использовать функции из libc без инклюдов, особенность работы современных компиляторов.
aragaer
В данном конкретном примере все вызовы P принимают строковые литералы со смещением 0 или 1, так что все вполне управляемо.
Да, это main, поэтому я опустил return.
Функции из libc не имеют отношения к инклюду — инклюд влияет только на подтягивание объявлений функций. Фокус в том, что printf (и поразительно большое число других функций) имеют прототип, эквивалентный прототипу по-умолчанию — возвращают int, принимают то, что в них передали.
Algoritmist
Спасибо!
Algoritmist
Можно и так. Смысл не меняется. Return — будет в ворнинге
Algoritmist
Я знаю только одного человека, который сделал это задание сразу. (И это был не я)
firegurafiku
И этот человек догадался, что этот код «рисует лабиринт в stdout»? Лично у меня, когда я увидел эту строчку, возникло желание не понять, что оно делает, а тупо нейтрализовать и просто удовлетворить формальным требованиям (это несложно сделать даже без макросов):
Или всё-таки предполагается, что на соискатель должен догадаться об оригинальном назначении кода и написать программу, которая бы строила лабиринт?
nwalker
Из этого кода, если хоть чуть-чуть вчитаться, очевидно, что он что-то рисует в stdout.
Algoritmist
Да, просто «тупо нейтрализовать». Лабиринт не нужен. Немного скучноватый вариант вышел (тело циклов не выполняется), но
хороший показатель (одного этого достаточно).Algoritmist
Здесь нужен собеседник, который подскажет. Да, это не шутка. u — не одно число, а массив (u++ — гуляет по нему).
rafuck
Вовсе не обязательно.
aamonster
Практически на рефлексе: слова, написанные целиком капсом — макросы. Сразу после этого заставить пример компилироваться и ничего не делать (или делать всё, что нам заблагорассудится) — тривиально (аргументы макросов можно не использовать вообще — пример резко упростится).
Но разбор прочёл не без интереса.
Kokto
Algoritmist
Да, я так первый раз и написал, но u — не одно число, а массив. (u++ — гуляет по нему)
Лучше С задавать не в функции P.
Kokto
Это понятно. Мне просто больше нравятся выражения "|/-\"[ i++ % 4 ] для таких извращений.
UnknownUser
Думаю, если на собеседовании попадётся такое задание, то правильным ответом будет «Для начала, узнать кто это написал, и если этот человек ещё работает в компании, найти его и долго бить томиком книги за авторством Кернигана-Ритчи».
Algoritmist
Найти автора я не смог, но похоже это реальный код. Что-то в виде таблицы рисовалось.
UnknownUser
Возможно, его нашли раньше вас ).
Нет, действительно, писать всё в одну строчку, плюс все эти *p++, это чистой воды издевательство на тем, кто этот код будет разбирать.
Ну и в припадке ностальгии, вспомнил знатный холивар с коллегой, который был за if( some ) вместо if( some != 0 ). Хотя, это уже не так страшно и воспринимается более менее нормально.
Kokto
Раньше компиляторы были не такими умными, и в код вставлялась константа.
Если написать без "!= 0" то машинный код становился короче. Отсюда такой подход.
vzaburdaev
Р — разочарование
Algoritmist
возможно, но скорее Print
nwalker
Вы потеряли пробел в строке "_ ", что не лучшим образом сказывается на понимании, что вообще происходит.
Algoritmist
Нет, не потерял. Указатель может указывать на конец строки. Тогда будет пустая строка.
nwalker
Меня смутило то, что во внутреннем цикле вызывается
Вроде бы конструкции однотипные, но без пробела логика получается подозрительно разная.
Так вот, смутило это меня в достаточной степени, чтобы найти
Это один из победителей IOCCC 1985, shapiro.c by Carl Shapiro, код рисует лабиринт в stdout. И да, там есть этот самый пробел. =)
Algoritmist
Мощь и сила! Спасибо! Я теперь знаю автора
Kokto
Да, там ещё есть невероятная мощь: http://www.ioccc.org/2006/toledo2/toledo2.c
Эмулятор процессора, без проблем запускающий CP/M с BASIC-ом внутри.
d-stream
По-моему этот же исходник был подписью автора в FIDO
только почему-то мой склероз в свое время мне подсказывал maze.c а надо было shapiro.c искать
akzhan
Прочел код, не понял, где там шутка. Вполне все тривиально.
Algoritmist
Да, шутки тут нет. Она здесь.
logiz
Если считать, что это задача на C++, то можно просто перегрузить все операторы. Тогда уж можно что хочешь складывать и инкрименировать.
Algoritmist
И в Си можно складывать и инкриментировать.
Myosotis
Выводит:
Algoritmist
Да, только:
1. что-то с отступами не задалось
2. это С, а не С++ (см. функцию Р)
3. код править по заданию нельзя. (char *) — зачем?
Что-то не верю я в то, что она такое выводит.
alexeykuzmin0
Слегка подправил код товарища:
Таки выводит, правда, без лишних пробелов (см изменения в P(char*)).Myosotis
А, его тоже забыла убрать до этого аргумент в функции был не const, поэтому была ошибка
invalid conversion from 'const char*' to 'char*'
Myosotis
Теперь берёте на работу?)
Algoritmist
Почему бы нет...
maybe_im_a_leo
Когда я вижу такие задачи на собеседовании — я говорю "до свидания, всего хорошего вашей компании". Дальше обычно разговаривать не имеет смысла.
Nick_Shl
Два момента:
Каким образом константу или дефайн декрементировать? Если еще к константой можно понять — не везде они константы на самом деле, можно взять указатель, сделать преобразование типов на не константный указатель и работать.1) В циклах используется не инкремент, а декремент.
2) Ну им самое веселое:
Но вот с дефайном такое не сработает.
Algoritmist
1. Спасибо, да ошибся. Поправил.
2. Что-то не понял, где «C» декрементируем?
Algoritmist
1. Спасибо. Да, ошибся. Поправил.
2. Что-то не понял, где «C» декрементируем?
Optimus_990
Забавно сделали)