Иногда ответ не «42»

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

    Сама задача — получить число 0x17 самым внезапным образом.


    Внимание! Данный пост оскорбляет чувство прекрасного и практики программирования на C. Читайте, воспринимайте и комментируйте на свой страх и риск.


    Вы любите магические числа в коде? Вот бывает смотришь какой-то код и видишь a = b ^ 7. Почему именно 7? Что это за число, откуда оно взялось, что означает? Столько много вопросов и так мало ответов, если кто-то не утрудился комментарием. Есть несколько способов разрешить неопределенность такого рода:

    1. Оставить комментарий. Но комменты для слабаков — не наш путь.
    2. Сделать переменную или макрос с говорящим именем. a = b ^ HEARTBEAT_MASK_BYTE. Уже неплохо, но можно же и лучше, не так ли?
    3. Показать вычислениями путь, который привел к данному числу. Вот! Это отличный способ. Но не всегда применимый, как, например, в рассматриваемом коде. В нем 7 — это законченный и самостоятельный элемент, который не является результатом каких-то операций.

    Зачем рассматривать еще какие-то способы? Берем последний и в путь! Но, как я сказал, есть проблема — не всегда есть адекватный способ вычислениями показать путь к числу. Но задача стояла избавиться от магического числа, а не сделать это логично. Более того, доведем задачу до абсурда — будем получать числа максимально непонятным образом.

    Программно-аппаратный комплекс в виде вызова функции rand() постановил, что избавляться мы будем от магического числа 0x17.

    Коллектив погроммистов в составе меня, Viscount и [УДАЛЕНО] приступил к творчеству.
    0х17 способов получить 0x17:

    	int Ox01 = ~-~-~-~-~-~-~-~-~-' ';
    	int Ox02 = ((!true)["true"]-(false)["FALSE"])>>true;
    	int Ox03 = 'X'/2/2^!*"";
    	int Ox04 = ('0'>>!*"")-!*"";
    	int Ox05 = (~'!'-~'~')>>!*"">>(2==1==0);
    	int Ox06 = ('|'||'|'|'|')["||||||||"]%*"error";
    	int Ox07 = '.'>>!false;
    	int Ox08 = '\\'>>('!'>>(1<<2));
    	int Ox09 = '/'-'/'/'/'>>'/'/'/';
    	int Ox0a = (*"")["yes"]^(*"")["no"];
    	int Ox0b = *"yes"^*"no";
    	int Ox0c = '0'/2-!*"";
    	int Ox0d = ((!'!'+'+')>>true)+(true<<true);
    	int Ox0e = (-~true^!false)*(true<<(-~true|!false))-!false;
    	int Ox0f = '!'-'^'%*"*";
    	int Ox10 = -~*" L"^-~*"5Z";
    	int Ox11 = *"Totally" -* "not" +* "0x17" + true;
    	int Ox12 = -~!*""*-~!*""*-~!*""*-~!*""*-~!*""+~!*""*-~!*""*-~!*""-true;
    	int Ox13 = -compl(-compl true xor true)<<-compl true|'8'>>('1'^'2');
    	int Ox14 = '^'>>('<'^'>');
    	int Ox15 = *"'"-(' '>>!0);
    	int Ox16 = '_'>>-~1;
    	int Ox17 = 010-001+010+010;
    

    Для сомневающихся в том, что указанная задача выполнена верно — ссылка на ideone.

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

    Кстати, мы ищем Питон-разработчика, который умеет программировать понятнее чем я. Присоединяйтесь!
    Поделиться публикацией
    Похожие публикации
    Комментарии 35
    • +1
      доведем задачу до абсурда
      По-моему у вас получилось. Почему 0x17? Из-за 0x0b?
      • +2
        Ничем специальным число не обусловлено. Просто генератор выдал именно 0x17 и дальше пошло-поехало.
      • +1
        Блин, уже на втором примере сломал мозг…
        • +3
          Хочу поставить 0х17 лайков. Но с пояснениями статья была бы намного интереснее и полезнее.

          Второй пример сильно попахивает неопределённым поведением и индексацией случайных адресов памяти. Или я неправ?
          • 0
            Нет, UB нет ни в одном из примеров.
            Я обдумывал идею добавить разборы, но они сделали бы статью существенно скучнее. В итоге решил оставить без пояснений, благо комментарии позволяют обсудить конкретные места.
            • +5

              Нет. (!true)["true"] — это то же самое что "true"[0], т.е. символ 't'. Со второй частью аналогично.

              • +3
                Вроде как ptr[n] == n[ptr], то есть это выражение эквивалентно ('t' -'F') >> 1 => 0x2e >> 1 => 0x17
                • +2
                  Здесь всё в порядке: x[y] раскрывается в *(x + y).
                  • 0
                    Да, дошло через 5 минут модификаций кода в ideone, чтоб понять, что же он делает. А комментарий ни добавить, ни отредактировать уже не мог. В общем, надо сначала подумать, а потом уже в комментариях спрашивать :)

                    Только насчёт x + y не согласен, потому что здесь это явно раскрылось в y+x. Арифметика указателей разве коммутативна?
                    • 0
                      Коммутативна для байт, на этом и построен трюк 3[«test»] == «test»[3]
                      • 0

                        Что такое "коммутативна для байт"??? О_о

                      • 0

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

                        • 0
                          Я о том, что совершенно не очевидно, что int + char* — такая же валидная операция, как char* + int. Более того: возможно, даже, имело бы смысл сделать первый вариант синтаксической ошибкой.
                          • 0

                            Я не вижу никакой опасности в такой коммутативности. А запрещать чисто ради запрещения в С не принято.

                  • –1

                    Простите, но там в начале разве должна быть O (буква) или все же 0 (цифра)?


                    int Ox01 = ~-~-~-~-~-~-~-~-~-' ';
                    • +6
                      Названия переменных же нельзя начинать с цифры. Вот и выкрутились
                      • 0
                        Точно, не заметил что это название переменной.
                    • +1
                      Есть еще сильно подгонный вариант (по сути будет работать только в вашем примере на Ideone, и может еще на других 64 битных машинах с linux, gcc 6.3 и использующих ту же версию libc) — в глобальных переменных обьявить extern «C» void _start(); (в случае чистого C — просто extern) а непосредственно в main int Ox18 = ~((char*)_start)[142];
                      • 0
                        В некоторых промежуточных версиях у нас были подобные варианты, например, через отрицательные индексы массивов, но в итоге, от них решили отказаться, поскольку они очень ненадежные.
                      • 0
                        Вообще говоря общая идея — это использовать оп код вызова call x86 ассемблера, я пока думаю как это можно сделать 100% предсказуемым образом.
                        P.S сори, промахнулся веткой :(
                        • +3
                          Нумерация OxZZ не с нуля — вся статья на выброс.
                          • +1
                            Ну хоть кто-то заметил, а я уже начал терять веру в людей.
                            • 0
                              Разве это не было сделано, что бы последняя переменная было с номером 0x17?
                              • 0
                                Примерно так. Но даже если бы отсчет шел с Ox00 по Ox16 общее количество в 0x17 все равно было бы очевидно.
                          • –1

                            Во-первых, ваши решения написаны не на С, а на GCC, да и те платформеннозависимы. Такие задачи интереснее решать именно на стандартном С. Ваши решения в большинстве своем к С относятся мало или вообще никак.


                            Во-вторых, ideone в профессиональных кругах не считается уважаемым или убедительным ресурсом, особенно в вопросах С, поэтому заявления вида "для сомневающихся — ссылка на ideone" никаких сомнений не развеивают, а могут вызвать лишь facepalm.

                            • 0
                              А можно аргументы?
                              • +3

                                Запросто!


                                На "во-первых" аргументы очевидны: большинство вариантов завязаны на свойство платфоременно-зависимого character set. Вот и все. К тому же фактически способов что-то сделать у вас от силы три. И один из них — применение * к строковому литералу — раздут в огромное число вариантов. Зачем было делать искусственное и скучное раздувание этих способов в такое количество уныло повторяющихся вариантов — не ясно. Это же сразу бросается в глаза.


                                Найти остроумные решения, которые бы работали на настоящем стандартном С — вот это действительно интересная задача, потому что в ней заключается challenge. А ваши чисто косметические выверты на фактически готовеньком результате (т.е. на конкретных значениях character constants) — примитивная пустышка/профанация для студенток-первокурсниц.


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


                                Например, для использования true и false обязательно требуется включение <stdbool.h>. Для использования compl и xor обязательно требуется включение <iso646.h>. У вас же ideone в режиме С (!) проглотил это все даже не поперхнувшись. Именно по таким причинам в темах по языку С не принято оскорблять присутствующих ссылками на потешные глюкала типа ideone. Возмите в привычку пользоваться общепризнанными стандартами типа coliru. Какой бы вы ресурс не использовали, контроль над командной строкой компилятора — обязателен.


                                P.S. Ой, только что обратил внимание! Вы вообще в С++ это все компилировали! Так зачем же вы нам тогда баки забиваете какими то сказками про "практику программирования на C"?

                                • +1
                                  А я все думал, когда же вы заметите про c++.
                                  Я себе ограничений в «чистый си, где даже даже в ASCII нельзя быть уверенным» не ставил.
                                  Произошло ровно то, что написано в статье — мой код оскорбил практики программирования на си.
                                  • 0

                                    На С++ все даже хуже, ибо в С++ символьная константа имеет тип char, а не int. И это значит, что в С++ все ваши символьные константы будет подвергаться integral promotions, которые, в зависимости от платформы, могут превратить ее в int или в unsigned int. Вариант с unsigned int — целый ящик Пандоры самостоятельных проблем. (Хотя аналогичные проблемы с integral promotions присутствуют в этом коде и с точки зрения С).

                                  • +1
                                    Тем не менее, используемый character set не является чем-то уникальным для GCC, так что ваше утверждение «решения написаны на GCC» все еще странное.
                                    • 0

                                      Это не принципиально. Завязка на character set в решении таких задач — это совершенно неинтересный читинг. А тут еще 90% приведенных "решений" — унылое повторение снова, снова и снова одного и того же приема с *"строка", к тому же завязанного на character set.

                              • +7
                                избавляться мы будем от магического числа 0x17.

                                Вот это мне повезло!
                              • 0
                                del
                                • –1
                                  На картинке для кликбайта написано 0x17 а в решениях совсем другое
                                  int Ox07 = '.'>>!false;
                                  int Ox17 = 010-001+010+010;
                                  какая то даже не пятничная статья

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

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