2012-04-25 17 views
17

La siguiente pregunta se realizó en un concurso de programación de universidades. Nos pidieron que adivináramos el resultado y/o explicamos su funcionamiento. Huelga decir que ninguno de nosotros tuvo éxito.Comprender un argumento poco común en main

main(_){write(read(0,&_,1)&&main());} 

Algunos corto buscar en Google me llevó a esta pregunta exacta, preguntó en codegolf.stackexchange.com:

https://codegolf.stackexchange.com/a/1336/4085

Allí, su explicó lo que hace: Reverse stdin and place on stdout, pero no cómo .

También encontré un poco de ayuda en esta pregunta: Three arguments to main, and other obfuscating tricks pero todavía no explica cómo main(_), &_ y &&main() obras.

Mi pregunta es cómo funcionan estas sintaxis? ¿Son algo que debería saber, como en, siguen siendo relevantes?

Le agradecería cualquier apuntador (a los enlaces de recursos, etc.), si no respuestas directas.

+0

Ese programa no se compilará en C++. Eliminando la etiqueta C++. –

+0

@ Robᵩ Ah gracias. No tuve cuidado. – RaunakS

+10

Incluso en C, ese programa invoca comportamiento indefinido de múltiples maneras. El resultado es predecible solo para compiladores específicos dirigidos a tipos específicos de CPU (incluso en codegolf, este programa solo hace algo interesante en un nivel de optimización específico). Corrija las respuestas a "¿Qué hace este programa?" incluya "depende", "lo que sea que quiera" y "lo despiden". –

Respuesta

26

¿Qué hace este programa?

main(_){write(read(0,&_,1)&&main());} 

Antes lo analizamos, vamos a Prettify que:

main(_) { 
    write (read(0, &_, 1) && main()); 
} 

En primer lugar, usted debe saber que _ es un nombre de variable válido, aunque muy feo.Vamos a cambiarlo:

main(argc) { 
    write(read(0, &argc, 1) && main()); 
} 

A continuación, se da cuenta de que el tipo de retorno de una función, y el tipo de un parámetro son opcionales en C (pero no en C++):

int main(int argc) { 
    write(read(0, &argc, 1) && main()); 
} 

A continuación, entender cómo los valores de retorno funcionan Para ciertos tipos de CPU, el valor de retorno siempre se almacena en los mismos registros (EAX en x86, por ejemplo). Por lo tanto, si omite una instrucción return, el valor de retorno es probable que sea la función más reciente devuelta.

int main(int argc) { 
    int result = write(read(0, &argc, 1) && main()); 
    return result; 
} 

La llamada a read es más o menos evidente: se lee desde estándar en (descriptor de fichero 0), en la memoria situada en &argc, por 1 byte. Devuelve 1 si la lectura fue exitosa y 0 en caso contrario.

&& es el operador lógico "y". Evalúa su lado derecho si y solo si su lado izquierdo es "verdadero" (técnicamente, cualquier valor distinto de cero). El resultado de la expresión && es int, que siempre es 1 (para "verdadero") o 0 (para falso).

En este caso, el lado derecho invoca main sin argumentos. Llamar al main sin argumentos después de declararlo con 1 argumento es un comportamiento indefinido. Sin embargo, a menudo funciona, siempre y cuando no le importe el valor inicial del parámetro argc.

El resultado del && se pasa luego al write(). Entonces, nuestro código ahora se ve así:

int main(int argc) { 
    int read_result = read(0, &argc, 1) && main(); 
    int result = write(read_result); 
    return result; 
} 

Hmm. Un vistazo rápido a las páginas del manual revela que write toma tres argumentos, no uno. Otro caso de comportamiento indefinido. Al igual que llamar al main con muy pocos argumentos, no podemos predecir qué write recibirá para su segundo y tercer argumentos. En las computadoras típicas, obtendrán algo, pero no podemos saber con certeza qué. (En computadoras atípicas, pueden suceder cosas extrañas). El autor confía en write para recibir lo que previamente había almacenado en la pila de memoria. Y, él confía en que es el segundo y tercer argumentos para leer.

int main(int argc) { 
    int read_result = read(0, &argc, 1) && main(); 
    int result = write(read_result, &argc, 1); 
    return result; 
} 

fijación de la llamada no válida a main, y añadiendo cabeceras, y la ampliación de la && tenemos:

#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; 
} 


Conclusiones

Este programa no funcionará como se espera en muchas computadoras Incluso si usa la misma computadora que el autor original, es posible que no funcione en un sistema operativo diferente. Incluso si usa la misma computadora y el mismo sistema operativo, no funcionará en muchos compiladores. Incluso si usa el mismo compilador de la computadora y el mismo sistema operativo, es posible que no funcione si cambia los indicadores de la línea de comando del compilador.

Como dije en los comentarios, la pregunta no tiene una respuesta válida.Si encontró un organizador del concurso o un juez del concurso que diga lo contrario, no los invite a su próximo concurso.

+1

Oh wow, eso fue muy, muy completo. Una aclaración: la sintaxis 'write()' es 'int write (int fd, char * Buff, int NumBytes)'. ¿Entonces el valor de retorno de 'read()' se está convirtiendo en '1' para escribir en la salida estándar? – RaunakS

+1

0 es entrada estándar, 1 es salida estándar, 2 es estándar err. Por lo tanto, un retorno exitoso de lectura (combinado con un retorno exitoso de la llamada recursiva a main) produce una escritura en stdout. Un retorno fallido de los resultados de lectura en una escritura en stdin. Lo cual es otro comportamiento indefinido. –

+0

Ah, sí, debería haberlo hecho antes de preguntar. Este código sería un muy buen candidato para IOCCC. Y es un comportamiento indefinido como este replicable? Quiero decir, en el mismo compilador (gcc 4.4.1), ¿esto siempre dará el mismo resultado? – RaunakS

8

Ok, _ es solo una variable declarada en K & sintaxis R C con un tipo predeterminado de int. Funciona como almacenamiento temporal.

El programa intentará leer un byte desde la entrada estándar. Si hay una entrada, llamará principal recursivamente para continuar leyendo un byte.

Al final de la entrada, read(2) devolverá 0, la expresión devolverá 0, se ejecutará la llamada al sistema write(2), y la cadena de llamada probablemente se desenrollará.

Digo "probablemente" aquí porque a partir de este momento los resultados dependen en gran medida de la implementación. Los otros parámetros a write(2) faltan, pero algo estará en los registros y en la pila, por lo que algo se pasará al kernel. El mismo comportamiento indefinido se aplica al valor de retorno de las diversas activaciones recursivas de main.

En mi Mac x86_64, el programa lee la entrada estándar hasta EOF y luego sale, sin escribir nada.

+0

¿Alguna cita sobre lo que es '_'? Curioso saber –

+0

Es solo un parámetro formal * ("variable") * nombre. Es equivalente a 'main (int _)' ... imagine que lo llamaron * "argc" * y todo estará claro. Es decir: 'main (argc)' sería C inicial con el valor predeterminado ** int, ** las declaraciones * prototype * se agregaron más tarde. No declaran el * argv * habitual, pero como resultado no ocurrirá nada drástico. – DigitalRoss

+0

Sí, un '_' simple es un nombre de variable legal. –