Come funziona il codice C che stampa da 1 a 1000 senza loop o istruzioni condizionali?


148

Ho trovato un Ccodice che stampa da 1 a 1000 senza loop o condizionali : ma non capisco come funzioni. Qualcuno può passare attraverso il codice e spiegare ogni riga?

#include <stdio.h>
#include <stdlib.h>

void main(int j) {
  printf("%d\n", j);
  (&main + (&exit - &main)*(j/1000))(j+1);
}

1
Stai compilando come C o come C ++? Quali errori vedi? Non è possibile chiamare mainin C ++.
ninjalj,

@ninjalj Ho creato un progetto C ++ e copia / oltre il codice gli errori sono: illegale, l'operando di sinistra ha il tipo 'void (__cdecl *) (int)' e l'espressione deve essere un puntatore a un tipo di oggetto completo
ob_dev

1
@ninjalj Questo codice funziona su ideone.org ma non nello studio visivo ideone.com/MtJ1M
ob_dev

@oussama Simile, ma leggermente più difficile da capire: ideone.com/2ItXm Prego . :)
Segna il

2
ho rimosso tutti i caratteri '&' da queste righe (& main + (& exit - & main) * (j / 1000)) (j + 1); e questo codice funziona ancora.
ob_dev,

Risposte:


264

Non scrivere mai codice del genere.


Per j<1000, j/1000è zero (divisione intera). Così:

(&main + (&exit - &main)*(j/1000))(j+1);

è equivalente a:

(&main + (&exit - &main)*0)(j+1);

Che è:

(&main)(j+1);

Che chiama maincon j+1.

Se j == 1000, allora vengono fuori le stesse linee di:

(&main + (&exit - &main)*1)(j+1);

Il che si riduce a

(&exit)(j+1);

Che è exit(j+1)e lascia il programma.


(&exit)(j+1)e exit(j+1)sono essenzialmente la stessa cosa - citando C99 §6.3.2.1 / 4:

Un designatore di funzioni è un'espressione con tipo di funzione. Tranne quando si tratta dell'operando dell'operatore sizeof o dell'operatore unario e , un designatore di funzione con tipo " tipo di ritorno di funzione " viene convertito in un'espressione che ha il tipo " puntatore a tipo di ritorno di funzione ".

exitè un designatore di funzioni. Anche senza l'operatore unario di &indirizzo, viene trattato come un puntatore alla funzione. (Il &giusto lo rende esplicito.)

E le chiamate di funzione sono descritte in §6.5.2.2 / 1 e seguenti:

L'espressione che indica la funzione chiamata deve avere un puntatore di tipo per la funzione che restituisce il vuoto o restituisce un tipo di oggetto diverso da un tipo di matrice.

Quindi exit(j+1)funziona a causa della conversione automatica del tipo di funzione in un tipo puntatore a funzione e (&exit)(j+1)funziona anche con una conversione esplicita in un tipo puntatore a funzione.

Detto questo, il codice sopra riportato non è conforme ( mainaccetta due argomenti o nessuno), ed &exit - &mainè, a mio avviso, indefinito in base al § 6.5.5 / 9:

Quando vengono sottratti due puntatori, entrambi devono puntare a elementi dello stesso oggetto array o uno oltre l'ultimo elemento dell'oggetto array; ...

L'aggiunta (&main + ...)sarebbe valida di per sé e potrebbe essere utilizzata se la quantità aggiunta fosse zero, poiché §6.5.6 / 7 dice:

Ai fini di questi operatori, un puntatore a un oggetto che non è un elemento di un array si comporta come un puntatore al primo elemento di un array di lunghezza uno con il tipo di oggetto come tipo di elemento.

Quindi aggiungere zero a &mainsarebbe ok (ma non molto uso).


4
foo(arg)e (&foo)(arg)sono equivalenti, chiamano foo con argomento arg. newty.de/fpt/fpt.html è una pagina interessante sui puntatori a funzioni.
Mat

1
@Krishnabhadra: nel primo caso, fooè un puntatore, &fooè l'indirizzo di quel puntatore. Nel secondo caso, fooè un array ed &fooè equivalente a pippo.
Mat

8
Inutilmente complesso, almeno per C99:((void(*[])()){main, exit})[j / 1000](j + 1);
Per Johansson,

1
&foonon è lo stesso di fooquando si tratta di un array. &fooè un puntatore alla matrice, fooè un puntatore al primo elemento. Hanno lo stesso valore però. Per le funzioni, fune &funsono entrambi puntatori alla funzione.
Per Johansson,

1
Cordiali saluti, se guardate la risposta pertinente all'altra domanda citata sopra , vedrete che c'è una variazione che è in realtà conforme C99. Spaventoso, ma vero.
Daniel Pryden,

41

Utilizza la ricorsione, l'aritmetica del puntatore e sfrutta il comportamento di arrotondamento della divisione di numeri interi.

Il j/1000termine viene arrotondato per difetto a 0 per tutti j < 1000; una volta che jraggiunge 1000, viene valutato 1.

Ora se hai a + (b - a) * n, dove nè 0 o 1, finisci con aif n == 0, e bif n == 1. Usando &main(l'indirizzo di main()) e &exitper ae b, il termine (&main + (&exit - &main) * (j/1000))ritorna &mainquando jè inferiore a 1000, &exitaltrimenti. Il puntatore alla funzione risultante viene quindi alimentato l'argomento j+1.

L'intero costrutto si traduce in un comportamento ricorsivo: mentre jè inferiore a 1000, si mainchiama ricorsivamente; quando jraggiunge 1000, chiama exitinvece, facendo uscire il programma con il codice di uscita 1001 (che è un po 'sporco, ma funziona).


1
Buona risposta, ma un dubbio ... Come uscire principale con il codice di uscita 1001? Main non restituisce nulla .. Qualunque valore di ritorno predefinito?
Krishnabhadra,

2
Quando j raggiunge 1000, main non si ripete più in se stesso; invece, chiama la funzione libc exit, che prende il codice di uscita come argomento e, bene, esce dal processo corrente. A quel punto, j è 1000, quindi j + 1 è uguale a 1001, che diventa il codice di uscita.
tdammers,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.