Подтвердить что ты не робот

Интервью Hello World

Эта классическая запись ioccc - это программа Hello World, написанная на C. Может ли кто-нибудь объяснить, как это работает?

Исходный код (подсветка синтаксиса намеренно отсутствует):

int i;main(){for(;i["]<i;++i){--i;}"];read('-'-'-',i+++"hell\
o, world!\n",'/'/'/'));}read(j,i,p){write(j/p+p,i---j,i/i);}

Слегка очиститель:

int i;
main()
{
  for ( ; i["]<i;++i){--i;}"]; read('-' - '-', i++ + "hello, world!\n", '/' / '/'));
}

read(j, i, p)
{
  write(j / p + p, i-- - j, i / i);
}
4b9b3361

Ответ 1

для условия цикла

i["]<i;++i){--i;}"]

В этом выражении используется тот факт, что индексирование массива является коммутативным в C. Оно эквивалентно.

"]<i;++i){--i;}"[i]

Итак, цикл завершится, когда символ в позиции i будет \0, т.е. в конце строки, длина которой составляет 14 символов (что бывает такой же длины, как "hello, world!\п" ). Таким образом, условие цикла for можно переписать как:

i != 14

арифметика символов

read('-' - '-', i++ + "hello, world!\n", '/' / '/')

char - целочисленный тип и, следовательно,

  • '-' - '-' равно 0
  • '/' / '/' равен 1

    читать (0, я ++ + "привет, мир! \n", 1)


После исправления всех предупреждений компилятора (например, неявного преобразования int to pointer) и упрощения упомянутых выше вещей код становится:

#include <unistd.h>

int i = 0;

void read2(int, char*, int);

int main()
{
   while (i != 14)
   {
      read2(0, i++ + "hello, world!\n", 1);
   }

   return 0;
}

void read2(int j, char* i, int p)
{
   write(j / p + p, i-- - j, 1);
}

(Я переименовал read в read2, чтобы избежать конфликта с функцией Unix read.)

Обратите внимание, что аргументы j и p для read2 не требуются, так как функция всегда вызывается с j = 0 и p = 1.

#include <unistd.h>

int i = 0;

void read2(char*);

int main()
{
   while (i != 14)
   {
      read2(i++ + "hello, world!\n");
   }

   return 0;
}

void read2(char* i)
{
   write(1, i--, 1);
}

Вызов write(1, i--, 1) записывает 1 символ из i в дескриптор 1 файла (stdout). И postdecrement является излишним, потому что это i является локальной переменной, на которую никогда не ссылались. Таким образом, эта функция эквивалентна putchar(*i).

Вставка функции read2 в основной цикл дает

#include <stdio.h>

int i = 0;

int main()
{
   while (i != 14)
   {
      putchar(*(i++ + "hello, world!\n"));
   }

   return 0;
}

для которого смысл очевиден.

Ответ 2

Не в уме полностью разобрать это, но есть некоторые намеки:

  • '-' - '-' обфускается для 0
  • '/' / '/' и i / i в read() обфускации для 1

Помните, что [] является коммутативным, т.е. i["]<i;++i){--i;}"] совпадает с "]<i;++i){--i;}"[i] (имеющим массив char и указывающий на i-й его элемент). Содержание строки никоим образом не имеет значения, для определения количества итераций цикла используется только его длина. Любая другая строка той же длины (кстати, такая же длина, что и выход...) будет работать. После этого числа итераций "строка" [i] возвращает нулевой символ, который завершает строку. Zero == false, цикл завершается.

С этим, было бы сравнительно легко выяснить остальное.

Изменить: Аспекты заставили меня заинтересоваться, чтобы посмотреть на это еще немного.

Конечно, i++ + "Hello, world!\n" совпадает с "Hello, world!\n"[ i++ ].

codelark уже указал, что 0 является fid для stdout. (Дайте ему +1 за это, а не я, просто упомянем об этом для полноты.)

i in read(), конечно, локальный, т.е. i-- in read() не влияет на i в main(). Так как i / i всегда i / i всегда 1, оператор -- ничего не делает.

О, и попросите собеседника уволить того, кто написал этот код. Он не проверяет ошибку на возвращаемом значении write(). Я могу жить с заведомо плохо написанным кодом (и есть, на долгие годы), но не проверять ошибки хуже, чем плохо, что ошибочно.: -)

Остальное - это всего лишь математика третьего класса. Я передам это стажеру.; -)

Ответ 3

Индекс строки я [ "..." ] заставляет цикл выполнять длину строки. i ++ + строка hello world означает, что каждая итерация передает строку, начиная с одной буквы, глубже, чем локальная функция чтения.

первая итерация = "привет..." вторая итерация = "ello.."

Первый параметр функции чтения упрощает до 0, дескриптор файла stdout. Последний параметр упрощает до 1, поэтому для каждого звонка записывается только один символ. Это означает, что каждая итерация будет записывать первый символ строки, переданной в stdout. Итак, следуя примеру строк сверху:

первая итерация = "h" вторая итерация = "e"

Индекс строки в цикле for имеет ту же длину, что и глобальная строка hello, и поскольку [] является коммутативным, а строки имеют завершение нулями, последняя итерация возвращает нулевой символ и выходит из цикла.

Ответ 4

Вдали от эксперта C, но я попробую:

i["]<i;++i){--i;}"]
// is actually
char* x = "]<i;++i){--i;}"
*(i + x)
// and will turn to zero when i == 14 (will point to string ending zero)
// '-' - '-' and '/' / '/' are always 0 and 1
// int i is initiated to zero
// i++ will return original value, so main turns to
main()
{
    char* hello = "hello, world!\n";
    for (i = 0 ; i != 14; i++) read(0, hello[i], 1);
}

// read becomes
// i-- does nothing here, i is in read scope, not the global one
read(j, i, p)
{
  write(1, i, 1);
}

// and at last
main()
{
    char* hello = "hello, world!\n";
    for (i = 0 ; i<14; i++) write(1, hello[i], 1);
}

Ответ 5

Еще один вопрос интервью, который, кажется, больше отражает интервьюера, чем интервьюируемый. Ключевые вопросы здесь:  * j всегда 0 (('-' - '-') == 0)  * p всегда 1 (('/'/'/') == 1)  * я (в функции чтения) есть (i ++ + "hello world" ) == i-й символ в мире привет (затем инкремент i)

Таким образом, функция чтения становится

read(0, NextChar, 1)
{
  write(1, NextChar , 1);
}

Единственный другой бит - это коммутативность оператора [] в цикле for.

Понимание и разбор этого типа кода действительно бесполезно imho.