2015-04-24 7 views
13

Questo codice proviene da Hacker's Delight. Si dice che questo è il più breve programma così in C e è di 64 caratteri di lunghezza, ma io non lo capisco:Come si duplica questo programma da solo?

main(a){printf(a,34,a="main(a){printf(a,34,a=%c%s%c,34);}",34);} 

ho provato a compilarlo. Compilare con 3 avvisi e nessun errore.

+8

3 avvertimenti e nessun errore indica compilazione successo perché non lo fai funzionare? – Cestarian

+2

@Cestarian La domanda non è * cosa * fa - è * come * lo fa? Quindi, il titolo. – Barry

+4

Questo non è in realtà il programma più breve. Il più breve effettivo è lungo 0 byte. È possibile ottenere il compilatore per compilare correttamente un file c di 0 byte in un file eseguibile. L'esecuzione di questo exe comporta la stampa di 0 byte, ovvero l'intero codice sorgente per il programma originale. –

risposta

7

Questo programma si basa sulle ipotesi che

  • tipo di ritorno di main è tipo di parametro int
  • della funzione è int per impostazione predefinita e
  • l'argomento a="main(a){printf(a,34,a=%c%s%c,34);}" sarà valutato prima.

Invocherà comportamento non definito. L'ordine di valutazione degli argomenti di una funzione non è garantito in C.
Anche se, questo programma funziona come segue:

L'espressione assegnazionea="main(a){printf(a,34,a=%c%s%c,34);}" assegnerà la stringa "main(a){printf(a,34,a=%c%s%c,34);}" per a e il valore dell'espressione assegnazione sarebbe troppo "main(a){printf(a,34,a=%c%s%c,34);}" secondo standard C --C11: 6.5.16

Un operatore di assegnazione memorizza un valore nell'oggetto designato dall'operando di sinistra. Un'espressione assegnazioneha il valore dell'operando sinistro dopo l'assegnazione [...]

Tenendo presente la semantica sopra dell'operatore assegnazione del programma verrà esteso come

main(a){ 
     printf("main(a){printf(a,34,a=%c%s%c,34);}",34,a="main(a){printf(a,34,a=%c%s%c,34);}",34); 
} 

ASCII 34 è ". Specificatori e suoi argomenti corrispondenti:

%c ---> 34 
%s ---> "main(a){printf(a,34,a=%c%s%c,34);}" 
%c ---> 34 

Una versione migliore sarebbe

main(a){a="main(a){a=%c%s%c;printf(a,34,a,34);}";printf(a,34,a,34);} 

È 4 carattere più ma almeno consegue K & R C.

5

Si basa su diversi capricci del linguaggio C e (cosa che penso sia) comportamento indefinito.

Innanzitutto, definisce la funzione main. È legale dichiarare una funzione senza tipo di ritorno o tipi di parametro e si presumerà che sia int. Questo è il motivo per cui la parte main(a){ funziona.

Quindi, chiama printf con 4 parametri. Poiché non ha un prototipo, si presume che restituisca int e accetti i parametri int (a meno che il compilatore lo dichiari implicitamente in modo diverso, come fa Clang).

Il primo parametro è presunto int ed è argc all'inizio del programma. Il secondo parametro è 34 (che è ASCII per il carattere a virgolette). Il terzo parametro è un'espressione di assegnazione che assegna la stringa di formato a a e lo restituisce. Si basa su una conversione da puntatore a int, che è legale in C. L'ultimo parametro è un altro carattere di citazione in forma numerica.

In fase di runtime, gli identificatori di formato %c sono sostituiti da virgolette, lo %s viene sostituito con la stringa di formato e si ottiene di nuovo la fonte originale.

Per quanto ne so, l'ordine di valutazione dell'argomento non è definito. Questo quine funziona perché l'assegnazione a="main(a){printf(a,34,a=%c%s%c,34);}" viene valutata prima che a venga passato come primo parametro a printf, ma per quanto ne so, non esiste alcuna regola per applicarlo. Inoltre, questo non può funzionare su piattaforme a 64 bit perché la conversione da puntatore a int troncherà il puntatore a un valore a 32 bit. In effetti, anche se posso vedere come funziona su alcune piattaforme, non funziona sul mio computer con il mio compilatore.

+0

Sì, c'è UB a causa dell'ordine di valutazione. C'è anche più UB perché il tipo di 'a' (' int') differisce dal tipo previsto dalla conversione '% s' (' char * '). –

+0

@zneak grazie ora capisco questo programma e sei davvero bravo in c – PDP

+0

@JerryCoffin; Cosa è "più" o "meno" UB? – haccks

2

Il programma è supposto per stampare il proprio codice. Notare la somiglianza della stringa letterale con il codice generale del programma. L'idea è che il letterale sarà usato come stringa di formato printf() perché il suo valore è assegnato alla variabile a (anche se nella lista degli argomenti) e che sarà anche passato come stringa da stampare (perché l'espressione di un assegnamento valuta il valore è stato assegnato). Lo 34 è il codice ASCII per il carattere di doppia citazione ("); utilizzandolo si evita una stringa di formato contenente caratteri di virgolette di escape letterali.

Il codice si basa su un comportamento non specificato sotto forma di ordine di valutazione degli argomenti della funzione. Se vengono valutati nell'ordine dell'elenco di argomenti, è probabile che il programma fallisca perché il valore di a verrà quindi utilizzato come puntatore alla stringa di formato prima che il valore corretto fosse effettivamente assegnato ad esso.

Inoltre, il tipo di a default int, e non v'è alcuna garanzia che int è sufficientemente ampia per contenere un puntatore oggetto senza troncare esso.

Inoltre, lo standard C specifica solo due firme consentite per main() e la firma utilizzata non è tra queste.

Inoltre, il tipo di printf() dedotto dal compilatore in assenza di un prototipo non è corretto. Non è in alcun modo garantito che il compilatore generi una sequenza chiamante che funzioni per esso.

4

Questo funziona sulla base di molti accorgimenti che C ti consente di fare e alcuni comportamenti non definiti che funzionano a tuo favore. In ordine:

main(a) { ... 

Tipi si presume siano int se non specificato, quindi questo è equivalente a:

int main(int a) { ... 

Anche se main si suppone di prendere 0 o 2 argomenti, e questo è un comportamento indefinito , questo può essere permesso semplicemente ignorando il secondo argomento mancante.

Successivamente, il corpo, che sposterò. Si noti che a è un int secondo main:

printf(a, 
     34, 
     a = "main(a){printf(a,34,a=%c%s%c,34);}", 
     34); 

L'ordine di valutazione degli argomenti non è definito, ma stiamo contando sul terzo argomento - l'assegnazione - sempre valutata per prima. Ci stiamo anche affidando al comportamento indefinito di poter assegnare uno char * a uno int. Inoltre, si noti che 34 è il valore ASCII di ". Pertanto, l'impatto previsto del programma è:

int main(int a, char**) { 
    printf("main(a){printf(a,34,a=%c%s%c,34);}", 
      '"', 
      "main(a){printf(a,34,a=%c%s%c,34);}", 
      '"'); 
    return 0; // also left off 
} 

che, una volta valutato, produce:

main(a){printf(a,34,a="main(a){printf(a,34,a=%c%s%c,34);}",34);} 

che è stato il programma originale. Tada!