2012-04-25 8 views
17

La seguente domanda è stata data in un concorso di programmazione per il college. Ci è stato chiesto di indovinare l'output e/o spiegarne il funzionamento. Inutile dire che nessuno di noi ci è riuscito.Comprendere un argomento non comune sul principale

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

Alcuni breve Googling mi ha portato a questa domanda esatta, chiese codegolf.stackexchange.com:

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

Lì, la sua spiegato quello fa: Reverse stdin and place on stdout, ma non come.

ho trovato anche un po 'di aiuto in questa domanda: Three arguments to main, and other obfuscating tricks ma ancora non spiega come funziona main(_), &_ e &&main().

La mia domanda è, come funzionano queste sintassi? Sono qualcosa che dovrei sapere, come in, sono ancora rilevanti?

Sarei grato per qualsiasi suggerimento (per collegamenti di risorse, ecc.), Se non risposte definitive.

+0

Questo programma non verrà compilato in C++. Rimozione del tag C++. –

+0

@ Robᵩ Ah grazie. Sono stato incurante. – RaunakS

+10

Anche in C, quel programma richiama comportamenti indefiniti in più modi. Il risultato è prevedibile solo per compilatori specifici che prendono di mira tipi specifici di CPU (anche su codegolf, questo programma fa qualcosa di interessante a un livello di ottimizzazione specifico). Risposte corrette a "Cosa fa questo programma?" include "Dipende", "Qualunque cosa voglia" e "Ti fa licenziare". –

risposta

26

Cosa fa questo programma?

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

Prima lo analizziamo, diamo anche più elegante:

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

In primo luogo, si deve sapere che _ è un nome di variabile valido, sia pure brutto.Cambiamo esso:

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

successivo, si rende conto che il tipo di ritorno di una funzione, e il tipo di un parametro sono opzionali in C (ma non in C++):

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

Avanti, capire come i valori di ritorno funzionano. Per determinati tipi di CPU, il valore di ritorno è sempre memorizzato negli stessi registri (EAX su x86, ad esempio). Pertanto, se si omette un'istruzione return, il valore restituito è probabile che sia che corrisponde alla funzione restituita più recente.

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

La chiamata a read è più o meno evidente: si legge da standard (descrittore di file 0), nella memoria situato a &argc, per 1 di byte. Restituisce 1 se la lettura ha avuto esito positivo e 0 in caso contrario.

&& è l'operatore logico "e". Valuta il suo lato destro se e solo se il lato sinistro è "vero" (tecnicamente, qualsiasi valore diverso da zero). Il risultato dell'espressione && è un int che è sempre 1 (per "true") o 0 (per false).

In questo caso, il lato destro invoca main senza argomenti. Chiamare main senza argomenti dopo averlo dichiarato con 1 argomento non è un comportamento definito. Tuttavia, spesso funziona, a patto che non ti interessi il valore iniziale del parametro argc.

Il risultato dello && viene quindi passato a write(). Quindi, il nostro codice ora sembra:

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

Hmm. Una rapida occhiata alle pagine man rivela che lo write accetta tre argomenti, non uno. Un altro caso di comportamento indefinito. Proprio come chiamare lo main con troppo pochi argomenti, non possiamo prevedere cosa riceverà write per il suo secondo e terzo argomento. Sui computer tipici, riceveranno qualcosa di, ma non possiamo sapere per certo cosa. (Su computer atipici, possono accadere cose strane.) L'autore si basa su write per ricevere qualsiasi cosa precedentemente memorizzata nella pila di memoria. E, si basa su che è il secondo e il terzo argomento da leggere.

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

Fissaggio della chiamata non valida per main, e l'aggiunta di intestazioni, e ampliando la && abbiamo:

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


Conclusioni

Questo programma non funzionerà come previsto su molti computer. Anche se si utilizza lo stesso computer dell'autore originale, potrebbe non funzionare su un sistema operativo diverso. Anche se si utilizza lo stesso computer e lo stesso sistema operativo, non funzionerà su molti compilatori. Anche se si utilizza lo stesso compilatore e il medesimo sistema operativo, potrebbe non funzionare se si modificano i flag della riga di comando del compilatore.

Come ho detto nei commenti, la domanda non ha una risposta valida.Se hai trovato un organizzatore del concorso o un giudice del concorso che dice diversamente, non invitarlo al tuo prossimo concorso.

+1

Oh wow, è stato molto, molto comprensivo. Un chiarimento: la sintassi 'write()' è 'int write (int fd, char * Buff, int NumBytes)'. Quindi il valore di ritorno di 'read()' sta diventando '1' per scrivere sullo standard output? – RaunakS

+1

0 è standard, 1 è standard, 2 è standard err. Quindi, un ritorno positivo da lettura (combinato con un ritorno positivo dalla chiamata ricorsiva al principale) produce una scrittura su stdout. Un ritorno non riuscito da risultati di lettura in una scrittura su stdin. Quale è un altro comportamento indefinito. –

+0

Ah sì, avrei dovuto wikith prima di chiedere. Questo codice sarebbe un ottimo contendente IOCCC. E un comportamento indefinito come questo è replicabile? Voglio dire, sullo stesso compilatore (gcc 4.4.1), questo darà sempre lo stesso risultato? – RaunakS

8

Ok, _ è solo una variabile dichiarata all'inizio della sintassi K & R C con un tipo predefinito di int. Funziona come memoria temporanea.

Il programma proverà a leggere un byte dall'input standard. Se c'è un input, chiamerà main ricorsivamente continuando a leggere un byte.

Alla fine dell'input, read(2) restituirà 0, l'espressione restituirà 0, verrà eseguita la chiamata di sistema write(2) e la catena di chiamate si srotolerà.

Dico "probabilmente" qui perché da questo momento i risultati dipendono molto dall'implementazione. Gli altri parametri a write(2) sono mancanti, ma qualcosa sarà nei registri e nello stack, quindi qualcosa nel verrà passato nel kernel. Lo stesso comportamento non definito si applica al valore restituito dalle varie attivazioni ricorsive di main.

Sul mio x86_64 Mac, il programma legge lo standard input fino a EOF e quindi esce, non scrivendo nulla.

+0

Qualsiasi citazione su cosa sia '_'? Curioso di conoscerlo –

+0

È solo un parametro formale * ("variabile") * nome. È equivalente a 'main (int _)' ... immagina di averlo chiamato * "argc" * e sarà tutto chiaro. Cioè: 'main (argc)' sarebbe all'inizio C con default ** int, ** le dichiarazioni * prototype * sono state aggiunte in seguito. Non dichiarano il solito * argv *, ma nulla di drastico accadrà di conseguenza. – DigitalRoss

+0

Sì, un semplice '_' è un nome di variabile legale. –

Problemi correlati