Сама задача — получить число 0x17 самым внезапным образом.
Внимание! Данный пост оскорбляет чувство прекрасного и практики программирования на C. Читайте, воспринимайте и комментируйте на свой страх и риск.
Вы любите магические числа в коде? Вот бывает смотришь какой-то код и видишь a = b ^ 7. Почему именно 7? Что это за число, откуда оно взялось, что означает? Столько много вопросов и так мало ответов, если кто-то не утрудился комментарием. Есть несколько способов разрешить неопределенность такого рода:
- Оставить комментарий. Но комменты для слабаков — не наш путь.
- Сделать переменную или макрос с говорящим именем. a = b ^ HEARTBEAT_MASK_BYTE. Уже неплохо, но можно же и лучше, не так ли?
- Показать вычислениями путь, который привел к данному числу. Вот! Это отличный способ. Но не всегда применимый, как, например, в рассматриваемом коде. В нем 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)
VioletGiraffe
20.04.2018 12:34Хочу поставить 0х17 лайков. Но с пояснениями статья была бы намного интереснее и полезнее.
Второй пример сильно попахивает неопределённым поведением и индексацией случайных адресов памяти. Или я неправ?xi-tauw Автор
20.04.2018 12:49Нет, UB нет ни в одном из примеров.
Я обдумывал идею добавить разборы, но они сделали бы статью существенно скучнее. В итоге решил оставить без пояснений, благо комментарии позволяют обсудить конкретные места.
mayorovp
20.04.2018 12:49+1Нет.
(!true)["true"]
— это то же самое что"true"[0]
, т.е. символ 't'. Со второй частью аналогично.
SidMeier
20.04.2018 12:52+1Вроде как ptr[n] == n[ptr], то есть это выражение эквивалентно ('t' -'F') >> 1 => 0x2e >> 1 => 0x17
spinach
20.04.2018 16:02+1Здесь всё в порядке:
x[y]
раскрывается в*(x + y)
.VioletGiraffe
20.04.2018 16:21Да, дошло через 5 минут модификаций кода в ideone, чтоб понять, что же он делает. А комментарий ни добавить, ни отредактировать уже не мог. В общем, надо сначала подумать, а потом уже в комментариях спрашивать :)
Только насчёт x + y не согласен, потому что здесь это явно раскрылось в y+x. Арифметика указателей разве коммутативна?TheCalligrapher
21.04.2018 21:31Коммутативность не имеет никакого отношения к "арифметике указателей". Коммутативен встроенный бинарный оператор
+
и коммутативен абсолютно всегда, безусловно и во всех контекстах.VioletGiraffe
21.04.2018 21:47Я о том, что совершенно не очевидно, что int + char* — такая же валидная операция, как char* + int. Более того: возможно, даже, имело бы смысл сделать первый вариант синтаксической ошибкой.
TheCalligrapher
21.04.2018 22:46Я не вижу никакой опасности в такой коммутативности. А запрещать чисто ради запрещения в С не принято.
KvanTTT
20.04.2018 13:06Простите, но там в начале разве должна быть O (буква) или все же 0 (цифра)?
int Ox01 = ~-~-~-~-~-~-~-~-~-' ';
Mikihiso
20.04.2018 14:08+1Есть еще сильно подгонный вариант (по сути будет работать только в вашем примере на Ideone, и может еще на других 64 битных машинах с linux, gcc 6.3 и использующих ту же версию libc) — в глобальных переменных обьявить extern «C» void _start(); (в случае чистого C — просто extern) а непосредственно в main int Ox18 = ~((char*)_start)[142];
xi-tauw Автор
20.04.2018 14:09В некоторых промежуточных версиях у нас были подобные варианты, например, через отрицательные индексы массивов, но в итоге, от них решили отказаться, поскольку они очень ненадежные.
Mikihiso
20.04.2018 14:13Вообще говоря общая идея — это использовать оп код вызова call x86 ассемблера, я пока думаю как это можно сделать 100% предсказуемым образом.
P.S сори, промахнулся веткой :(
TheCalligrapher
20.04.2018 22:40-1Во-первых, ваши решения написаны не на С, а на GCC, да и те платформеннозависимы. Такие задачи интереснее решать именно на стандартном С. Ваши решения в большинстве своем к С относятся мало или вообще никак.
Во-вторых, ideone в профессиональных кругах не считается уважаемым или убедительным ресурсом, особенно в вопросах С, поэтому заявления вида "для сомневающихся — ссылка на ideone" никаких сомнений не развеивают, а могут вызвать лишь facepalm.
xi-tauw Автор
20.04.2018 22:45А можно аргументы?
TheCalligrapher
20.04.2018 23:55Запросто!
На "во-первых" аргументы очевидны: большинство вариантов завязаны на свойство платфоременно-зависимого character set. Вот и все. К тому же фактически способов что-то сделать у вас от силы три. И один из них — применение
*
к строковому литералу — раздут в огромное число вариантов. Зачем было делать искусственное и скучное раздувание этих способов в такое количество уныло повторяющихся вариантов — не ясно. Это же сразу бросается в глаза.
Найти остроумные решения, которые бы работали на настоящем стандартном С — вот это действительно интересная задача, потому что в ней заключается challenge. А ваши чисто косметические выверты на фактически готовеньком результате (т.е. на конкретных значениях character constants) — примитивная пустышка/профанация для студенток-первокурсниц.
На "во-вторых" аргументы тоже несложны: хорошо известно, что ideone занимается наглой пост-фильтрацией диагностических сообщений компилятора. Не видя стандартных диагностических сообщений компилятора рядовой пользователь не сможет судить о корректности кода. Ваш код грубо ошибочен, но тем не менее проглатывается ideone — вескость этого аргумента переоценить невозможно.
Например, для использования
true
иfalse
обязательно требуется включение<stdbool.h>
. Для использованияcompl
иxor
обязательно требуется включение<iso646.h>
. У вас же ideone в режиме С (!) проглотил это все даже не поперхнувшись. Именно по таким причинам в темах по языку С не принято оскорблять присутствующих ссылками на потешные глюкала типа ideone. Возмите в привычку пользоваться общепризнанными стандартами типа coliru. Какой бы вы ресурс не использовали, контроль над командной строкой компилятора — обязателен.
P.S. Ой, только что обратил внимание! Вы вообще в С++ это все компилировали! Так зачем же вы нам тогда баки забиваете какими то сказками про "практику программирования на C"?
xi-tauw Автор
21.04.2018 00:05А я все думал, когда же вы заметите про c++.
Я себе ограничений в «чистый си, где даже даже в ASCII нельзя быть уверенным» не ставил.
Произошло ровно то, что написано в статье — мой код оскорбил практики программирования на си.TheCalligrapher
21.04.2018 00:13На С++ все даже хуже, ибо в С++ символьная константа имеет тип
char
, а неint
. И это значит, что в С++ все ваши символьные константы будет подвергаться integral promotions, которые, в зависимости от платформы, могут превратить ее вint
или вunsigned int
. Вариант сunsigned int
— целый ящик Пандоры самостоятельных проблем. (Хотя аналогичные проблемы с integral promotions присутствуют в этом коде и с точки зрения С).
mayorovp
21.04.2018 10:37+1Тем не менее, используемый character set не является чем-то уникальным для GCC, так что ваше утверждение «решения написаны на GCC» все еще странное.
TheCalligrapher
21.04.2018 19:07Это не принципиально. Завязка на character set в решении таких задач — это совершенно неинтересный читинг. А тут еще 90% приведенных "решений" — унылое повторение снова, снова и снова одного и того же приема с
*"строка"
, к тому же завязанного на character set.
iamoverit
21.04.2018 00:06На картинке для кликбайта написано 0x17 а в решениях совсем другое
int Ox07 = '.'>>!false;
int Ox17 = 010-001+010+010;
какая то даже не пятничная статья
Sinatr
xi-tauw Автор
Ничем специальным число не обусловлено. Просто генератор выдал именно 0x17 и дальше пошло-поехало.