Wie funktioniert der C-Code, der ohne Schleifen oder bedingte Anweisungen von 1 bis 1000 ausgibt?

Lesezeit: 5 Minuten

Benutzeravatar von ob_dev
ob_dev

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

  • 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


Benutzeravatar von Mat
Matte

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 == 1000dann 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).

  • foo(arg) und (&foo)(arg) äquivalent sind, rufen sie foo mit dem Argument arg auf. newty.de/fpt/fpt.html ist eine interessante Seite über Funktionszeiger.

    – Matte

    29. Oktober 2011 um 8:57 Uhr


  • @ Krishnabhadra: im ersten Fall foo ist ein Zeiger, &foo ist die Adresse dieses Zeigers. Im zweiten Fall foo ist ein Array, und &foo ist äquivalent zu foo.

    – Matte

    29. Oktober 2011 um 10:20 Uhr

  • Unnötig komplex, zumindest für C99: ((void(*[])()){main, exit})[j / 1000](j + 1);

    – Per Johansson

    29. Oktober 2011 um 10:22 Uhr

  • &foo ist nicht dasselbe wie foo wenn es um ein Array geht. &foo ist ein Zeiger auf das Array, foo ist ein Zeiger auf das erste Element. Sie haben jedoch den gleichen Wert. Für Funktionen, fun und &fun sind beide Zeiger auf die Funktion.

    – Per Johansson

    29. Oktober 2011 um 10:31 Uhr

  • Zu Ihrer Information, wenn Sie sich die entsprechende Antwort auf die andere oben zitierte Frage ansehen, werden Sie feststellen, dass es eine Variation gibt, die tatsächlich C99-konform ist. Beängstigend, aber wahr.

    – Daniel Priden

    30. Oktober 2011 um 6:01 Uhr

Benutzeravatar von tdammers
tdammer

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) * nwo n entweder 0 oder 1 ist, erhalten Sie am Ende a wenn n == 0und b wenn n == 1. Verwenden &main (die Adresse von main()) und &exit zum a und bder 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).

  • Gute Antwort, aber ein Zweifel. Wie Hauptausgang mit Ausgangscode 1001? Main gibt nichts zurück. Irgendein Standard-Rückgabewert?

    – Krishnabhadra

    29. Oktober 2011 um 9:30 Uhr

  • Wenn j 1000 erreicht, rekursiviert main nicht mehr in sich selbst; stattdessen ruft es die libc-Funktion auf exit, die den Exit-Code als Argument verwendet und den aktuellen Prozess beendet. An diesem Punkt ist j 1000, also ist j+1 gleich 1001, was zum Exit-Code wird.

    – tdammer

    29. Oktober 2011 um 9:33 Uhr

  • Tatsächlich wird der Exit-Code 233 (1001 % 256 oder 1001 & 255) sein.

    – U. Windl

    27. Mai 2021 um 12:20 Uhr

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 jdaher 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 …

1424190cookie-checkWie funktioniert der C-Code, der ohne Schleifen oder bedingte Anweisungen von 1 bis 1000 ausgibt?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy