Ich habe gefunden C
Code, der ohne Schleifen oder Bedingungen von 1 bis 1000 druckt: Aber ich verstehe nicht, wie es funktioniert. Kann jemand den Code durchgehen und jede Zeile erklären?
#include <stdio.h>
#include <stdlib.h>
void main(int j) {
printf("%d\n", j);
(&main + (&exit - &main)*(j/1000))(j+1);
}
Schreiben Sie niemals solchen Code.
Zum j<1000
, j/1000
Null ist (ganzzahlige Division). So:
(&main + (&exit - &main)*(j/1000))(j+1);
ist äquivalent zu:
(&main + (&exit - &main)*0)(j+1);
Welches ist:
(&main)(j+1);
Was anruft main
mit j+1
.
Wenn j == 1000
dann kommen die gleichen Zeilen heraus wie:
(&main + (&exit - &main)*1)(j+1);
Worauf es hinausläuft
(&exit)(j+1);
Welches ist exit(j+1)
und verlässt das Programm.
(&exit)(j+1)
und exit(j+1)
sind im Wesentlichen dasselbe – Zitat von C99 §6.3.2.1/4:
Ein Funktionsbezeichner ist ein Ausdruck, der einen Funktionstyp hat. Außer wenn es der Operand des Operators sizeof ist oder der unäre &-Operatorein Funktionsbezeichner vom Typ “Funktion, die den Typ zurückgibt” wird in einen Ausdruck vom Typ ” konvertiertZeiger auf die Funktion, die den Typ zurückgibt“.
exit
ist ein Funktionsbezeichner. Auch ohne das Unäre &
address-of-Operator, wird er als Zeiger auf eine Funktion behandelt. (Das &
macht es nur deutlich.)
Und Funktionsaufrufe werden in §6.5.2.2/1 und folgende beschrieben:
Der Ausdruck, der die aufgerufene Funktion bezeichnet, muss einen Typ haben Zeiger auf Funktion Zurückgeben von void oder Zurückgeben eines anderen Objekttyps als eines Arraytyps.
So exit(j+1)
funktioniert aufgrund der automatischen Umwandlung des Funktionstyps in einen Zeiger-auf-Funktion-Typ, und (&exit)(j+1)
funktioniert auch mit einer expliziten Konvertierung in einen Zeiger-auf-Funktion-Typ.
Davon abgesehen ist der obige Code nicht konform (main
nimmt entweder zwei Argumente oder gar keine), und &exit - &main
ist, glaube ich, gemäß §6.5.6/9 undefiniert:
Wenn zwei Zeiger subtrahiert werden, beide müssen auf Elemente desselben Array-Objekts zeigen, oder eins nach dem letzten Element des Array-Objekts; …
Die Zugabe (&main + ...)
wäre an sich gültig und könnte verwendet werden, wenn die hinzugefügte Menge war Null, da §6.5.6/7 sagt:
Für die Zwecke dieser Operatoren verhält sich ein Zeiger auf ein Objekt, das kein Element eines Arrays ist, genauso wie ein Zeiger auf das erste Element eines Arrays der Länge eins mit dem Objekttyp als Elementtyp.
Also null addieren &main
wäre ok (aber nicht viel nutzen).
Es verwendet Rekursion, Zeigerarithmetik und nutzt das Rundungsverhalten der ganzzahligen Division aus.
Das j/1000
term wird für alle auf 0 abgerundet j < 1000
; einmal j
1000 erreicht, wird es mit 1 ausgewertet.
Nun, wenn Sie haben a + (b - a) * n
wo n
entweder 0 oder 1 ist, erhalten Sie am Ende a
wenn n == 0
und b
wenn n == 1
. Verwenden &main
(die Adresse von main()
) und &exit
zum a
und b
der Begriff (&main + (&exit - &main) * (j/1000))
kehrt zurück &main
Wenn j
ist unter 1000, &exit
Andernfalls. Der resultierende Funktionszeiger wird dann mit dem Argument gespeist j+1
.
Dieses ganze Konstrukt führt zu rekursivem Verhalten: while j
ist unter 1000, main
ruft sich selbst rekursiv auf; Wenn j
1000 erreicht, ruft es exit
Beenden Sie das Programm stattdessen mit dem Exit-Code 1001 (was etwas unsauber ist, aber funktioniert).
https://stackoverflow.com/a/7937813/6607497 erklärt alles, aber für die Ungeduldigen ist hier der äquivalente (lesbare) Code:
#include <stdio.h>
#include <stdlib.h>
void main(int j) {
printf("%d\n", j);
if (i/1000 == 0)
main(j+1);
else
exit(j+1);
}
Ich denke also, es ist offensichtlich, wie es funktioniert. Der einzige wirkliche Trick, der verwendet wird, ist das “computed goto” (&main + (&exit - &main)*(j/1000)
), entweder zu bewerten main
während j/1000
Null ist, oder exit
ansonsten (eigentlich wenn es 1
).
Beachten Sie vielleicht auch, dass das Programm missbräuchlich ist argc
wie j
daher wird es anders zählen, wenn Sie Argumente an das Programm übergeben, und es wird höchstwahrscheinlich abstürzen, wenn Sie mehr als 2000 Parameter hinzufügen …
Kompilierst du als C oder als C++? Welche Fehler sehen Sie? Sie können nicht anrufen
main
in C++.– ninjalj
29. Oktober 2011 um 10:05 Uhr
@ninjalj Ich habe ein C ++ – Projekt erstellt und den Code kopiert / eingefügt. Der Fehler lautet: illegal, linker Operand hat den Typ ‘void (__cdecl *) (int)’ und der Ausdruck muss ein Zeiger auf einen vollständigen Objekttyp sein
– ob_dev
29. Oktober 2011 um 10:15 Uhr
@ninjalj Dieser Code funktioniert auf ideone.org, aber nicht in Visual Studio ideone.com/MtJ1M
– ob_dev
29. Oktober 2011 um 10:27 Uhr
@oussama Ähnlich, aber geringfügig mehr schwierig zu verstehen: ideone.com/2ItXm Gern geschehen. 🙂
– Markieren
29. Oktober 2011 um 12:30 Uhr
ich habe alle ‘&’-Zeichen aus dieser Zeile entfernt (&main + (&exit – &main)*(j/1000))(j+1); und dieser Code funktioniert immer noch.
– ob_dev
30. Oktober 2011 um 16:57 Uhr