C-Puzzle: Ausgabe von printf sollte immer ‘5’ sein

Lesezeit: 7 Minuten

Benutzer-Avatar
Sync-Master

Ich habe dieses Rätsel in einer Kapitulationsarbeit gefunden.

void change()
{
    //write something in this function so that output of printf in main function
    //should always give 5.you can't change the main function
}

int main()
{
    int i = 5;
    change();
    i = 10;
    printf("%d", i);
    return 0;
}

Irgendwelche Lösungen.?

  • gähn… solche Sachen waren schon vor 20 Jahren Thema im Obfuscated C Contest.

    – Thorsten79

    2. Februar 2010 um 12:39 Uhr

  • Rätsel wie dieses sind sinnvoller, um zu testen, ob Leute einen Vermissten entdecken \n am Ende der Ausgabe oder eine fehlende Deklaration für printf Funktion.

    – AnT steht zu Russland

    2. Februar 2010 um 21:18 Uhr

  • +1 Trotz anfänglicher Skepsis gibt es hier einige wirklich hervorragende Antworten. Zweifler sollten sich die POSIX-kompatible Version ohne Makros ansehen.

    – Chris Lutz

    4. Februar 2010 um 6:20 Uhr

definieren?

#include <stdio.h>

void change()
{
//write something in this function so that output of printf in main function
//should always give 5.you can't change the main function
#define printf_ printf
#define printf(a, b) printf_("5");
}

int main()
{
       int i = 5;
       change();
       i = 10;
       printf("%d", i);
       return 0;
}

  • Habe dieses Problem schon einmal gesehen und das war die Lösung.

    – Gregor

    2. Februar 2010 um 5:48 Uhr

  • Was zum Teufel ist die Moral dieser Geschichte? was soll das lehren?? Ich wusste nicht, dass sie Kurse darüber machen, wie man schrecklich unüberschaubaren und gehackten Code schreibt … (obwohl ich nach der Menge, die ich gesehen habe, nicht überrascht wäre, wenn sie es tun würden …)

    – matt

    2. Februar 2010 um 7:27 Uhr

  • @RC: Ich kann nicht wirklich glauben, dass dies die wirkliche Lösung ist. Was wäre der Sinn der change() Funktion? Wenn dies die erwartete Antwort ist, würde die Frage oben nicht einfach “Schreiben Sie hier etwas” sagen main() ohne sich um die Funktion zu kümmern?

    – GrahamS

    2. Februar 2010 um 12:20 Uhr


  • Eine andere Lösung mit nur einem Makro ist #define printf(a, b) (printf)("5") wodurch rekursive Makroerweiterungsprobleme vermieden werden (mein Compiler beschwert sich gerne, wenn Leute rekursive Makros haben, was wahrscheinlich gut ist). Obwohl ich die bevorzuge puts() Version, da sie zur besseren Lesbarkeit einen Zeilenumbruch hinzufügt.

    – Chris Lutz

    4. Februar 2010 um 5:49 Uhr


  • Zitat des Präprozessors: „Missbrauch mich! Missbrauche mich!

    – Tim Post

    28. März 2010 um 18:11 Uhr

Dies ist eine POSIX-Antwort, die wirklich das tut, was das Problem verlangt 🙂

Es funktioniert nicht auf einigen Architekturen/Compilern, aber hier.

#include <stdio.h>

void
change () {

    void _change();
    _change();
}
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/mman.h>
void
_change()
{
    int main();
    uintptr_t m=(uintptr_t)main;
    uintptr_t ps=sysconf(_SC_PAGESIZE);
    m/=ps;
    m*=ps;
    mprotect((void*)m,ps,PROT_READ|PROT_WRITE|PROT_EXEC);
    char *s=(char*)(intptr_t)main;
    s=memchr(s,10,ps);
    *s=5;
    mprotect((void*)m,ps,PROT_READ|PROT_EXEC);

}

int
main() {
    int i=5;
    change();
    i=10;
    printf ("%d\n",i);
    return 0;
}

BEARBEITEN: Dies sollte es für Leute mit Boykott-Headern robuster machen.

  • +1 erstaunlich. Ich musste die verschieben #includes nach oben, um es zum Kompilieren zu bringen, aber es funktioniert wie ein Zauber, und diese Technik ist ziemlich brutal. Sie gewinnen mindestens 3 Internets.

    – Chris Lutz

    4. Februar 2010 um 6:02 Uhr

  • Ich persönlich würde gerne faktorisieren sysconf(_SC_PAGESIZE) in eine Variable, anstatt aufzurufen sysconf 5 mal. 😛

    – C. K. Young

    4. Februar 2010 um 6:04 Uhr

  • @jbcreix – Ich mag deine Indizierung. Ich benutze gcc -Wall -Wextra -Werror standardmäßig. Ich wette, es würde gut kompilieren, wenn ich die Warnungen herunterdrehen würde. Ich vergesse sie einfach, weil sie meistens so nett sind. Update: Nein, auch ohne Warnungen muss ich einschalten -fnested-functions, und dann spuckt es noch eine Warnung aus. Ich gebe OS X die Schuld.

    – Chris Lutz

    4. Februar 2010 um 6:17 Uhr


  • @SiegeX – Die Funktion _change schaut in den Maschinencode der main Funktion, macht es beschreibbar, sucht nach einer 10 und ändert die 10 in eine 5. Also, when _change kehrt zurück zu change und change kehrt zurück zu maindie nächste Zeile, i = 10; wird geändert worden sein i = 5; im Maschinencode, der ausgeführt wird. Es ist selbstmodifizierender Code.

    – Chris Lutz

    12. Januar 2011 um 1:00 Uhr

  • @SiegeX – C++ verbietet das Aufrufen mainaber in C aufrufen main von einer anderen Funktion ist vollkommen legal. Und ja, der Zeiger zeigt auf das Codesegment. Es ist ein Funktionszeiger, der in einen regulären Zeiger umgewandelt wird (der UB ist, aber in der Praxis auf POSIX-Systemen funktioniert, von denen wir sowieso bereits ausgehen).

    – Chris Lutz

    12. Januar 2011 um 2:03 Uhr

void change()
{
  //write something in this function so that output of printf in main function
  //should always give 5.you can't change the main function
  #define i a=5,b
}

  • -1, Das würde nicht funktionieren. printf("%d", i) würde durch den Präprozessor ersetzt werden printf("%d", a=5,) was nicht kompilieren würde.

    – GrahamS

    2. Februar 2010 um 11:59 Uhr


  • Nun, es wird durch ersetzt printf("%d", a=5,b). Kompiliert das? Ich bin mir nicht sicher, wie printf funktioniert

    – Chris Burt-Brown

    2. Februar 2010 um 12:24 Uhr


  • @Graham, @Chris: Ja, das funktioniert. Zusätzliche Argumente zu printf werden ignoriert.

    – C. K. Young

    2. Februar 2010 um 14:54 Uhr

  • @GrahamS es funktioniert. printf("%d", i) wird ersetzt durch printf("%d", a=5,b) (Ich bin mir nicht sicher, warum Sie dachten, das b würde entfallen). a=5 ist ein Ausdruck, der zu 5 ausgewertet wird b übergeben wird printf auch (printf verwendet varargs), aber printf ignoriert es, weil es nur einen Formatierungscode gibt (die %d). Einige Compiler warnen möglicherweise, dass Sie ein zusätzliches Argument an übergeben printfaber es ist immer noch gültiger Standard C, um dies zu tun.

    – Laurence Gonsalves

    3. Februar 2010 um 1:21 Uhr

  • @ Laurence Ja, ich stehe korrigiert. Ich bin mir nicht sicher, warum ich das ohne gelesen habe b (es war spät), aber printf scheint tatsächlich mit den zusätzlichen Argumenten umzugehen.

    – GrahamS

    3. Februar 2010 um 11:21 Uhr


Hier ist ein Ja wirklich billige antwort:

void
change()
{
    printf("%d", 5);
    exit(0);
}

😛

Hier noch eine Möglichkeit:

void change()
{
  char const *literal = "%d";
  char * ptr = (char*)literal;
  ptr[0] = '5';
  ptr[1] = 0;
}

Dies ist viel portabler als das Ändern der Rücksendeadresse, erfordert jedoch, dass Sie (a) einen Compiler haben, der Zeichenfolgenliterale zusammenfasst (die meisten tun dies), und (b) einen Compiler haben, der keine Konstanten in einem schreibgeschützten Abschnitt platziert. oder auf einer Architektur ohne MMU ausgeführt werden (heutzutage unwahrscheinlich).

  • Leider bekomme ich unter OS X einen Bus-Fehler, bevor irgendetwas gedruckt wird, also habe ich keine Ahnung, ob das funktioniert hat. Aber es ist eine hervorragende Lösung.

    – Chris Lutz

    4. Februar 2010 um 6:13 Uhr

  • Verdammt! Das wollte ich gerade posten!

    – MSN

    5. Februar 2010 um 1:01 Uhr

  • Sie müssten mit bauen -fwritable-strings damit das funktioniert. Ich mag das!

    – Donal Fellows

    28. März 2010 um 11:01 Uhr

  • Ist das wirklich legal C oder nur das, was Compiler akzeptieren? Ich dachte, es gäbe eine Standardregel, Konstanten nicht durch Zeiger zu ändern.

    – Ira Baxter

    20. Januar 2011 um 21:49 Uhr

  • Das C ist vollkommen legal und wird überall kompiliert. Ob es zur Laufzeit tatsächlich funktioniert, ist unterschiedlich – der Compiler bündelt möglicherweise keine Literale (in diesem Fall wirkt sich die Änderung nicht auf den aufrufenden Code aus), oder die Literale werden möglicherweise in einem Speicherbereich platziert, in den die MMU-Blöcke schreiben (in dem Fall gibt es eine Ausnahme, wenn der Code ausgeführt wird).

    – Mondschatten

    21. Januar 2011 um 10:49 Uhr


Hat jemand daran gedacht, atexit zu verwenden?


void change (void)
{
    static int i = 0;
    if (i == 0) atexit (change);

    if (i == 1) printf ("\r5 \b\n");
    ++i;
}

Beachten Sie, dass es in der Hauptfunktion keinen abschließenden Zeilenumbruch gibt. Wenn wir 2 Backspace-Zeichen an stdout senden, wird die 10 gelöscht und nur die 5 gedruckt.

  • Leider bekomme ich unter OS X einen Bus-Fehler, bevor irgendetwas gedruckt wird, also habe ich keine Ahnung, ob das funktioniert hat. Aber es ist eine hervorragende Lösung.

    – Chris Lutz

    4. Februar 2010 um 6:13 Uhr

  • Verdammt! Das wollte ich gerade posten!

    – MSN

    5. Februar 2010 um 1:01 Uhr

  • Sie müssten mit bauen -fwritable-strings damit das funktioniert. Ich mag das!

    – Donal Fellows

    28. März 2010 um 11:01 Uhr

  • Ist das wirklich legal C oder nur das, was Compiler akzeptieren? Ich dachte, es gäbe eine Standardregel, Konstanten nicht durch Zeiger zu ändern.

    – Ira Baxter

    20. Januar 2011 um 21:49 Uhr

  • Das C ist vollkommen legal und wird überall kompiliert. Ob es zur Laufzeit tatsächlich funktioniert, ist unterschiedlich – der Compiler bündelt möglicherweise keine Literale (in diesem Fall wirkt sich die Änderung nicht auf den aufrufenden Code aus), oder die Literale werden möglicherweise in einem Speicherbereich platziert, in den die MMU-Blöcke schreiben (in dem Fall gibt es eine Ausnahme, wenn der Code ausgeführt wird).

    – Mondschatten

    21. Januar 2011 um 10:49 Uhr


Rufen Sie das erforderliche #include auf und ersetzen Sie den Kommentar durch den Text ohne Klammern:

}
int printf(const char *s, ...) {
  return fprintf(stdout,"%d",5);

Erfolgreich getestet. Danke an dreamlax und Chris Lutz für Bugfixes.

  • printf kehrt zurück inthaben Sie es so definiert, dass es keinen Rückgabewert hat.

    – Traumlax

    2. Februar 2010 um 10:35 Uhr

  • printf gibt ein zurück int aber Ihre Implementierung gibt keinen Wert zurück. return fprintf ist in ordnung, denke ich. Aber +1 für extreme Klugheit.

    – Chris Lutz

    4. Februar 2010 um 5:59 Uhr

1190800cookie-checkC-Puzzle: Ausgabe von printf sollte immer ‘5’ sein

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

Privacy policy