Интервью Hello World объяснение - PullRequest
27 голосов
/ 01 октября 2010

Эта классическая запись 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);
}

Ответы [ 5 ]

56 голосов
/ 01 октября 2010

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

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

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

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

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

i != 14

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

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

char является целочисленным типом и, таким образом:

  • '-' - '-' - это 0
  • '/' / '/' равно 1

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


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

#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 (стандартный вывод). И постдекремент является излишним, потому что эта i является локальной переменной, на которую никогда не ссылаются снова. Так что эта функция эквивалентна putchar(*i).

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

#include <stdio.h>

int i = 0;

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

   return 0;
}

, для которого значение очевидно.

17 голосов
/ 01 октября 2010

Не собираюсь разбирать это на части, но есть некоторые намеки:

  • '-' - '-' запутано для 0
  • '/' / '/' и i / i в read () запутаны для 1

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

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

Редактировать: Я получил достаточно интереса, чтобы посмотретьна это еще немного.

Конечно, i++ + "Hello, world!\n" - это то же самое, что и "Hello, world!\n"[ i++ ].

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

i в read(), конечно, локально, то есть i-- в read() не влияет на i в main().Поскольку следующее i / i всегда равно 1, оператор -- вообще ничего не делает.

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

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

10 голосов
/ 01 октября 2010

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

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

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

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

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

4 голосов
/ 01 октября 2010

Вдали от эксперта 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's 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's 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);
}
1 голос
/ 01 октября 2010

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

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

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

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

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

...