Следующий вопрос был задан в конкурсе по программированию в колледже. Нам было предложено угадать результат и/или объяснить его работу. Излишне говорить, что никто из нас не преуспел.
main(_){write(read(0,&_,1)&&main());}
Несколько коротких Googling привели меня к этому точному вопросу, заданному в codegolf.stackexchange.com
:
https://codegolf.stackexchange.com/a/1336/4085
Там объясняется, что он делает: Reverse stdin and place on stdout
, но не так.
Я также нашел некоторую помощь в этом вопросе: Три аргумента в основном и другие обфускационные трюки
но он все еще не объясняет, как работают main(_)
, &_
и &&main()
.
Мой вопрос: как эти синтаксисы работают? Являются ли они чем-то, о чем я должен знать, так как они все еще актуальны?
Я был бы признателен за любые указатели (ссылки на ресурсы и т.д.), если не прямые ответы.
Ответ 1
Что делает эта программа?
main(_){write(read(0,&_,1)&&main());}
Прежде чем мы проанализируем это, пусть префикс:
main(_) {
write ( read(0, &_, 1) && main() );
}
Во-первых, вы должны знать, что _
является допустимым именем переменной, хотя и уродливым. Позвольте изменить его:
main(argc) {
write( read(0, &argc, 1) && main() );
}
Далее, поймите, что тип возврата функции и тип параметра являются необязательными в C (но не в С++):
int main(int argc) {
write( read(0, &argc, 1) && main() );
}
Далее, понять, как работают возвращаемые значения. Для некоторых типов ЦП возвращаемое значение всегда сохраняется в одних и тех же регистрах (например, EAX на x86). Таким образом, если вы опускаете оператор return
, возвращаемое значение, скорее всего, будет тем, что возвращает самая последняя функция.
int main(int argc) {
int result = write( read(0, &argc, 1) && main() );
return result;
}
Вызов read
более или менее очевидный: он читает из стандартного в (дескриптор файла 0) в память, расположенную в &argc
, для 1
байта. Он возвращает 1
, если чтение было успешным, и 0 в противном случае.
&&
является логическим оператором "и". Он оценивает свою правую сторону тогда и только тогда, когда левая сторона "истинна" (технически, любое ненулевое значение). Результатом выражения &&
является int
, который всегда равен 1 (для "true" ) или 0 (для false).
В этом случае правая сторона вызывает main
без аргументов. Вызов main
без аргументов после объявления его 1 аргументом - это поведение undefined. Тем не менее, он часто работает, если вы не заботитесь об исходном значении параметра argc
.
Результат &&
затем передается в write()
. Итак, наш код теперь выглядит так:
int main(int argc) {
int read_result = read(0, &argc, 1) && main();
int result = write(read_result);
return result;
}
Хм. Быстрый просмотр страниц руководства показывает, что write
принимает три аргумента, а не один. Другой случай поведения undefined. Точно так же, как вызов main
с слишком небольшим количеством аргументов, мы не можем предсказать, что получит write
для своих 2-го и 3-го аргументов. На типичных компьютерах они получат что-то, но мы не можем точно знать, что. (На нетипичных компьютерах могут случиться странные вещи.) Автор полагается на write
на получение того, что ранее было сохранено в стеке памяти. И он полагается на то, что это 2-й и 3-й аргументы для чтения.
int main(int argc) {
int read_result = read(0, &argc, 1) && main();
int result = write(read_result, &argc, 1);
return result;
}
Фиксируя недействительный вызов main
, добавляя заголовки и расширяя &&
, мы имеем:
#include <unistd.h>
int main(int argc, int argv) {
int result;
result = read(0, &argc, 1);
if(result) result = main(argc, argv);
result = write(result, &argc, 1);
return result;
}
Выводы
Эта программа не будет работать так, как ожидалось, на многих компьютерах. Даже если вы используете тот же компьютер, что и оригинальный автор, он может не работать в другой операционной системе. Даже если вы используете тот же компьютер и ту же операционную систему, он не будет работать на многих компиляторах. Даже если вы используете один и тот же компилятор и операционную систему компьютера, это может не сработать, если вы измените флаги командной строки компилятора.
Как я уже сказал в комментариях, вопрос не имеет верного ответа. Если вы нашли организатора конкурса или судьи конкурса, который говорит иначе, не приглашайте их на следующий конкурс.
Ответ 2
Хорошо, _
- это просто переменная, объявленная в раннем синтаксисе K & R C со стандартным типом int. Он функционирует как временное хранилище.
Программа попытается прочитать один байт со стандартного ввода. Если есть вход, он будет вызывать основное рекурсивно продолжение чтения одного байта.
В конце ввода read(2)
вернет 0, выражение вернет 0, системный вызов write(2)
выполнит, и цепочка вызовов, вероятно, отключится.
Я говорю "возможно" здесь, потому что с этого момента результаты сильно зависят от реализации. Остальные параметры write(2)
отсутствуют, но что-то будет в регистрах и в стеке, поэтому что-то будет передано в ядро. Такое же поведение undefined применяется к возвращаемому значению от различных рекурсивных активаций main
.
На моем x86_64 Mac программа читает стандартный ввод до EOF и затем выходит, ничего не пишу.