Konzept hinter diesen vier Zeilen kniffligen C-Codes

Lesezeit: 9 Minuten

Benutzeravatar von codeslayer1
Codelayer1

Warum gibt dieser Code die Ausgabe aus C++Sucks? Welches Konzept steckt dahinter?

#include <stdio.h>

double m[] = {7709179928849219.0, 771};

int main() {
    m[1]--?m[0]*=2,main():printf((char*)m);    
}

Probier es aus hier.

  • @BoBTFish technisch ja, aber es läuft trotzdem in C99: ideone.com/IZOkql

    – nikolaus

    1. August 2013 um 11:26 Uhr

  • @nurettin Ich hatte ähnliche Gedanken. Aber es ist nicht die Schuld von OP, es sind die Leute, die für dieses nutzlose Wissen stimmen. Zugegeben, diese Code-Verschleierung mag interessant sein, aber geben Sie “Verschleierung” in Google ein und Sie erhalten Tonnen von Ergebnissen in jeder erdenklichen formalen Sprache. Verstehen Sie mich nicht falsch, ich finde es in Ordnung, eine solche Frage hier zu stellen. Es ist nur eine überbewertete, weil nicht sehr nützliche Frage.

    – TobiMcNamobi

    2. August 2013 um 10:44 Uhr


  • @detonator123 “Sie müssen neu hier sein” – wenn Sie sich den Schließungsgrund ansehen, können Sie feststellen, dass dies nicht der Fall ist. Das erforderliche Mindestverständnis fehlt eindeutig in Ihrer Frage – “Ich verstehe das nicht, erklären Sie es” ist bei Stack Overflow nicht willkommen. Sollten Sie etwas versucht haben dich selbst Erstens wäre die Frage nicht geschlossen worden. Es ist trivial, “doppelte Darstellung C” oder ähnliches zu googeln.

    Benutzer529758

    2. August 2013 um 20:06 Uhr

  • Meine Big-Endian-PowerPC-Maschine druckt skcuS++C.

    – Adam Rosenfield

    8. August 2013 um 3:13 Uhr

  • Meine Güte, ich hasse erfundene Fragen wie diese. Es ist ein Bitmuster im Speicher, das zufällig dasselbe ist wie eine dumme Zeichenfolge. Es erfüllt für niemanden einen nützlichen Zweck, und dennoch bringt es sowohl dem Fragesteller als auch dem Antwortenden Hunderte von Wiederholungspunkten ein. In der Zwischenzeit verdienen schwierige Fragen, die für Menschen nützlich sein könnten, vielleicht eine Handvoll Punkte, wenn überhaupt. Dies ist eine Art Aushängeschild dessen, was mit SO nicht stimmt.

    – Carey Gregory

    24. April 2014 um 3:56 Uhr

Benutzeravatar von Sergey Kalinichenko
Sergej Kalinitschenko

Die Nummer 7709179928849219.0 hat die folgende binäre Darstellung als 64-Bit double:

01000011 00111011 01100011 01110101 01010011 00101011 00101011 01000011
+^^^^^^^ ^^^^---- -------- -------- -------- -------- -------- --------

+ zeigt die Position des Schildes; ^ des Exponenten und - der Mantisse (dh der Wert ohne den Exponenten).

Da die Darstellung einen binären Exponenten und eine Mantisse verwendet, erhöht das Verdoppeln der Zahl den Exponenten um eins. Ihr Programm macht das genau 771 Mal, also ist der Exponent, der bei 1075 begann (Dezimaldarstellung von 10000110011) wird am Ende 1075 + 771 = 1846; binäre Darstellung von 1846 ist 11100110110. Das resultierende Muster sieht so aus:

01110011 01101011 01100011 01110101 01010011 00101011 00101011 01000011
-------- -------- -------- -------- -------- -------- -------- --------
0x73 's' 0x6B 'k' 0x63 'c' 0x75 'u' 0x53 'S' 0x2B '+' 0x2B '+' 0x43 'C'

Dieses Muster entspricht der Zeichenfolge, die Sie gedruckt sehen, nur rückwärts. Gleichzeitig wird das zweite Element des Arrays zu Null, wodurch ein Null-Terminator bereitgestellt wird, sodass die Zeichenfolge für die Übergabe geeignet ist printf().

  • Warum ist die Saite rückwärts?

    – Derek

    1. August 2013 um 13:26 Uhr

  • @Derek x86 ist Little-Endian

    – Angew ist nicht mehr stolz auf SO

    1. August 2013 um 13:36 Uhr

  • @Derek Das liegt an den Plattformspezifischen Endianität: Die Bytes der abstrakten IEEE 754-Darstellung werden im Speicher an abnehmenden Adressen gespeichert, sodass die Zeichenfolge korrekt gedruckt wird. Auf Hardware mit Big-Endianness müsste man mit einer anderen Nummer beginnen.

    – Sergej Kalinitschenko

    1. August 2013 um 13:38 Uhr


  • @AlvinWong Sie haben Recht, der Standard erfordert kein IEEE 754 oder ein anderes spezifisches Format. Dieses Programm ist ungefähr so ​​​​nicht portabel wie es nur geht, oder sehr nahe dran 🙂

    – Sergej Kalinitschenko

    1. August 2013 um 15:14 Uhr

  • @GrijeshChauhan Ich habe a IEEE754-Rechner mit doppelter Genauigkeit: Ich habe die eingefügt 7709179928849219 Wert und bekam die binäre Darstellung zurück.

    – Sergej Kalinitschenko

    1. August 2013 um 20:59 Uhr

Benutzeravatar von Adam Stelmaszczyk
Adam Stelmaszczyk

Besser lesbare Version:

double m[2] = {7709179928849219.0, 771};
// m[0] = 7709179928849219.0;
// m[1] = 771;    

int main()
{
    if (m[1]-- != 0)
    {
        m[0] *= 2;
        main();
    }
    else
    {
        printf((char*) m);
    }
}

Es ruft rekursiv auf main() 771 mal.

Am Anfang, m[0] = 7709179928849219.0die steht zum C++Suc;C. Bei jedem Anruf m[0] wird verdoppelt, um die letzten beiden Buchstaben zu “reparieren”. Beim letzten Aufruf m[0] enthält ASCII-Zeichendarstellung von C++Sucks und m[1] enthält nur Nullen, also hat es a Null-Terminator zum C++Sucks Schnur. Alles unter der Annahme, dass m[0] wird auf 8 Bytes gespeichert, sodass jedes Zeichen 1 Byte benötigt.

Ohne Rekursion und illegal main() Der Aufruf sieht so aus:

double m[] = {7709179928849219.0, 0};
for (int i = 0; i < 771; i++)
{
    m[0] *= 2;
}
printf((char*) m);

  • Es ist Postfix-Dekrement. Es wird also 771 mal aufgerufen.

    – Jack Aidley

    1. August 2013 um 11:38 Uhr

Angew ist nicht mehr stolz auf den User-Avatar von SO
Angew ist nicht mehr stolz auf SO

Haftungsausschluss: Diese Antwort wurde in der ursprünglichen Form der Frage gepostet, die nur C++ erwähnte und einen C++-Header enthielt. Die Umwandlung der Frage in reines C wurde von der Community vorgenommen, ohne Eingaben des ursprünglichen Fragestellers.


Formal gesehen ist es unmöglich, über dieses Programm nachzudenken, weil es schlecht geformt ist (dh es ist kein legales C++). Es verstößt gegen C++11[basic.start.main]p3:

Die Funktion main darf nicht innerhalb eines Programms verwendet werden.

Abgesehen davon beruht es auf der Tatsache, dass auf einem typischen Consumer-Computer a double ist 8 Bytes lang und verwendet eine bestimmte wohlbekannte interne Darstellung. Die Anfangswerte des Arrays werden so berechnet, dass, wenn der “Algorithmus” ausgeführt wird, der Endwert der erste ist double so sein, dass die interne Darstellung (8 Bytes) die ASCII-Codes der 8 Zeichen sind C++Sucks. Das zweite Element im Array ist dann 0.0dessen erstes Byte ist 0 in der internen Darstellung, wodurch dies eine gültige Zeichenfolge im C-Stil wird. Diese wird dann mit ausgegeben printf().

Wenn Sie dies auf HW ausführen, wo einige der oben genannten Punkte nicht zutreffen, würde dies stattdessen zu Mülltext (oder vielleicht sogar zu einem Zugriff außerhalb der Grenzen) führen.

  • Ich muss hinzufügen, dass dies keine Erfindung von C++11 ist – C++03 hatte es auch basic.start.main 3.6.1/3 mit gleichem Wortlaut.

    – scharfer Zahn

    1. August 2013 um 11:30 Uhr

  • Der Sinn dieses kleinen Beispiels besteht darin, zu veranschaulichen, was man mit C++ machen kann. Magisches Sample mit UB-Tricks oder riesigen Softwarepaketen mit “klassischem” Code.

    – SCHepurin

    1. August 2013 um 11:30 Uhr

  • @sharptooth Danke, dass du das hinzugefügt hast. Ich wollte nichts anderes andeuten, ich habe nur den Standard zitiert, den ich verwendet habe.

    – Angew ist nicht mehr stolz auf SO

    1. August 2013 um 11:38 Uhr

  • @Angew: Ja, das verstehe ich, wollte nur sagen, dass die Formulierung ziemlich alt ist.

    – scharfer Zahn

    1. August 2013 um 11:41 Uhr

  • @JimBalter Hinweis Ich sagte: “Formal gesehen ist es unmöglich zu argumentieren.” nicht “Es ist unmöglich, formal zu argumentieren.” Sie haben Recht, dass es möglich ist, über das Programm nachzudenken, aber Sie müssen die Details des Compilers kennen, der dazu verwendet wird. Es wäre vollständig innerhalb der Rechte eines Compilers um den Anruf einfach zu eliminieren main()oder ersetzen Sie es durch einen API-Aufruf, um die Festplatte zu formatieren, oder was auch immer.

    – Angew ist nicht mehr stolz auf SO

    2. August 2013 um 12:12 Uhr

Benutzeravatar von Jerry Coffin
Jerry Sarg

Der vielleicht einfachste Weg, den Code zu verstehen, besteht darin, die Dinge in umgekehrter Reihenfolge durchzugehen. Wir beginnen mit einer Zeichenfolge zum Ausdrucken – aus Gründen des Gleichgewichts verwenden wir „C++Rocks“. Knackpunkt: Genau wie das Original ist es genau acht Zeichen lang. Da wir (ungefähr) wie das Original vorgehen und es in umgekehrter Reihenfolge ausdrucken, beginnen wir damit, es in umgekehrter Reihenfolge einzulegen. In unserem ersten Schritt betrachten wir dieses Bitmuster einfach als a doubleund drucke das Ergebnis aus:

#include <stdio.h>

char string[] = "skcoR++C";

int main(){
    printf("%f\n", *(double*)string);
}

Dies produziert 3823728713643449.5. Also wollen wir das auf eine Weise manipulieren, die nicht offensichtlich ist, aber leicht rückgängig gemacht werden kann. Ich wähle halb willkürlich die Multiplikation mit 256, was uns ergibt 978874550692723072. Jetzt müssen wir nur etwas verschleierten Code schreiben, um durch 256 zu dividieren, und dann die einzelnen Bytes davon in umgekehrter Reihenfolge ausdrucken:

#include <stdio.h>

double x [] = { 978874550692723072, 8 };
char *y = (char *)x;

int main(int argc, char **argv){
    if (x[1]) {
        x[0] /= 2;  
        main(--x[1], (char **)++y);
    }
    putchar(*--y);
}

Jetzt haben wir viel Casting, Übergabe von Argumenten an (rekursiv) main die völlig ignoriert werden (aber die Auswertung, um das Inkrement und Dekrement zu erhalten, ist absolut entscheidend), und natürlich diese völlig willkürlich aussehende Zahl, um die Tatsache zu vertuschen, dass das, was wir tun, wirklich ziemlich einfach ist.

Da es um Verschleierung geht, können wir natürlich auch weitere Schritte unternehmen, wenn wir Lust dazu haben. Nur zum Beispiel können wir die Kurzschlussauswertung nutzen, um unsere zu drehen if -Anweisung in einen einzigen Ausdruck, also sieht der Hauptteil so aus:

x[1] && (x[0] /= 2,  main(--x[1], (char **)++y));
putchar(*--y);

Für jeden, der nicht an verschleierten Code (und/oder Code-Golf) gewöhnt ist, sieht dies in der Tat ziemlich seltsam aus – Rechnen und Verwerfen des Logischen and einer bedeutungslosen Fließkommazahl und dem Rückgabewert aus main, die nicht einmal einen Wert zurückgibt. Schlimmer noch, ohne zu wissen (und darüber nachzudenken), wie die Kurzschlussbewertung funktioniert, ist es möglicherweise nicht einmal sofort offensichtlich, wie sie unendliche Rekursion vermeidet.

Unser nächster Schritt wäre wahrscheinlich, das Drucken jedes Zeichens von der Suche nach diesem Zeichen zu trennen. Wir können das ziemlich einfach tun, indem wir das richtige Zeichen als Rückgabewert von generieren mainund was ausdrucken main kehrt zurück:

x[1] && (x[0] /= 2,  putchar(main(--x[1], (char **)++y)));
return *--y;

Zumindest erscheint mir das verschleiert genug, also belasse ich es dabei.

Benutzeravatar von DR
DR

Es baut nur ein doppeltes Array (16 Bytes) auf, das – wenn es als char-Array interpretiert wird – die ASCII-Codes für die Zeichenfolge “C++Sucks” aufbaut.

Der Code funktioniert jedoch nicht auf jedem System, er beruht auf einigen der folgenden undefinierten Fakten:

Benutzeravatar von Cody Gray
Cody Gray

Der folgende Code wird gedruckt C++Suc;Calso gilt die ganze Multiplikation nur für die letzten beiden Buchstaben

double m[] = {7709179928849219.0, 0};
printf("%s\n", (char *)m);

Benutzeravatar von Yu Hao
Yu Hao

Die anderen haben die Frage ziemlich ausführlich erklärt, ich möchte eine Anmerkung hinzufügen, dass dies der Fall ist undefiniertes Verhalten nach Norm.

C++11 3.6.1/3 Hauptfunktion

Die Funktion main darf nicht innerhalb eines Programms verwendet werden. Die Verknüpfung (3.5) von main ist implementierungsdefiniert. Ein Programm, das main als gelöscht definiert oder main als inline, static oder constexpr deklariert, ist falsch formatiert. Der Name main ist nicht anderweitig reserviert. [ Example: member functions, classes, and enumerations can be called main, as can entities in other namespaces. —end example ]

  • Ich würde sagen, es ist sogar schlecht geformt (wie ich es in meiner Antwort getan habe) – es verstößt gegen ein “soll”.

    – Angew ist nicht mehr stolz auf SO

    1. August 2013 um 16:13 Uhr

1427590cookie-checkKonzept hinter diesen vier Zeilen kniffligen C-Codes

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

Privacy policy