Top 10 NoGos in C



  • Was sind die Top 10 NoGos in C?



  • Die Dinge die hier im Forum am häufigsten angemault werden sind mMn:

    fflush(stdin);

    cast von malloc

    Etwas anderes als int main() oder int main(void) oder int main(int,char**)

    und das verwenden von Nicht-Standard-Funktionen 😃



    1. int anstelle von size_t verwenden.

    2. Variablen in for-Headern deklarieren.

    3. Falscher main -Header (hat DirkB schon erwähnt).

    4. Cast von malloc (auch von DirkB).

    5. Arraygrößen über Variablen angeben (VLAs) - also entweder macht man das direkt zur Kompilierzeit über irgendeine Art von Konstanten ( #define oder const size_t ), oder man verwendet alloca/_alloca oder malloc .

    6. Adressen auf Speicher auf dem Stack zurückgeben (siehe Jürgen Wolf).

    7. Zweidimensionale Arrays verwenden, wo es keinen Sinn ergibt.

    8. C++ in C einbinden und vice versa.

    9. Windows als Lern- und Entwicklungsplattform einsetzen (die Manpages haben mir damals mehr geholfen als alle Lehrbücher zusammen).

    10. Einen ewig langen Codeblock um eine Bedingung setzen (z.B. if(bla){/*1,5K Zeilen Code*/} und dann am Ende ein einsames else{return 0;} setzen). Das sehe ich noch 20 Jahre in dem Geschäft arbeitende Profis machen und jedes Mal muss ich das Verlangen unterdrücken, jemanden zu würgen.

    DirkB schrieb:

    und das verwenden von Nicht-Standard-Funktionen 😃

    Gerade da bin ich ein wenig liberaler. Die Standardfunktionen sind unter anderen Primässen entstanden, als in der Realität der Fall ist. Ich will nicht eine Binärdatei erst als Binärdatei einlesen müssen und mir dann Zeile für Zeile meinen Speicher füllen lassen. Ich will fünf API-Calls: Größe der Datei feststellen, Speicher reservieren, Datei öffnen, Daten einlesen und das in einem Rutsch, und Datei schließen.

    Ich will nicht, dass am Ende ein 0-Byte auftaucht, dass da nicht ist ( fgets/getline ). Ich will auch nicht in einer Schleife solange einlesen müssen, bis nichts mehr einzulesen gibt (Ausnahme Dateien, die der Kernel für einen erstellt, siehe /proc -Verzeichnis). Und ich will auch nicht angeben müssen, wie groß die Elemente sind und wie viele davon ich einlesen will ( fread ). Und manchmal, wenn ich eine große Datei einlesen will, will ich die direkt in den Speicher mappen und mir das Kopieren in meinen Userspace sparen.



  • @dachschaden
    Als jemand der so-gut-wie nie C programmiert muss ich da mal nachfragen...
    ad 2 ... wieso?
    ad 5 ... WIESO? (Was soll alloca statt VLA bringen - fetzt beides wenn man zu viel anfordert)
    ad 8 ... WIESO???
    ad 9 ... Ah, OK, du trollst. Dann erübrigen sich die restlichen Fragen.



  • @hustbaer: m(

    2:

    #include <stdio.h>
    
    int main(int argc,char**argv)
    {
        /*size_t i;*/
        for(size_t i=0;i<argc;i++)
            printf("%lu: %s\n",i,argv[i]);
    
        return 0;
    }
    
    main2.c: In Funktion »main«:
    main2.c:6:2: Fehler: Anfangsdeklarationen in »for«-Schleifen sind nur im C99-Modus erlaubt
    main2.c:6:2: Anmerkung: -std=c99 oder -std=gnu99 verwenden, um den Code zu übersetzen
    

    Und C99 wird noch lange nicht überall unterstützt.

    5:

    Abgesehen davon, dass dir so Code wie dieser:

    #include <stdio.h>
    
    int main(int argc,char**argv)
    {
        /*Deklarationsblock*/
        size_t i;
    
        /*Codeblock*/
        scanf("%lu",&i);
    
        /*Wie, watt? Wieder Deklaration? Noe, wir sind beim Code, du hattest
        **deine Chance.*/
        char bla[i];
        printf("%c\n",bla[0]);
    
        return 0;
    }
    

    abschmiert ... es handelt sich auch wieder um eine C99-Erweiterung.

    8:

    #include <stdio.h>
    #include <iostream>
    
    int main(int argc,char**argv)
    {
        size_t i;
    
        printf("Dann geben Sie mal eine Zahl ein: ");
        std::cin>>i;
        printf("Das haben Sie gut gemacht, die Zahl ist '%lu'\n",i);
    
        return 0;
    }
    

    Weil sich sowas mit einem C-Compiler nicht übersetzen lässt?
    Sorry, aber diese Frage ist an Schwachsinn kaum zu überbieten!

    Den Code kann man zwar mit dem g++ übersetzen, aber das stdio.h ist da noch immer. Kann man durch cstdio ersetzen, aber ein übler Nachgeschmack bleibt.

    9:

    Ich habe zwei Jahre mit Windows C versucht und habe schließlich den ganzen Scheiß weggeworfen. Unter Linux habe ich nicht mal ein Jahr gebraucht damals, um hinter den ganzen Scheiß zu blicken. Ist ja auch verständlich - mit "man getline" kommst du viel schneller an die Dokumentation der Funktion. Und das auch, wenn ich mal offline bin.
    Unter Windows kann ich das zwar auch haben, aber die Offline-Doku fand ich immer mehr als fragwürdig, weil sie manche Themen nicht behandelt hat.



  • Danke für das nächste NoGo

    dachschaden schrieb:

    size_t i;
        scanf("%lu",&i);
    ....
        printf("Das haben Sie gut gemacht, die Zahl ist '%lu'\n",i);
    

    Falsche Formatspecifier.

    Wir haben mittlerweile 2014. Da darf man ruhig mal einen 15 Jahre alten Standard dem 25 Jahre Altem vorziehen.



  • DirkB schrieb:

    Falsche Formatspecifier.

    Auch hier darf ich wieder mit Fingern auf andere zeigen:

    man scanf schrieb:

    z wie für h, der nächste Zeiger ist aber ein Zeiger auf ein size_t. Dieses Änderungszeichen wurde in C99 eingeführt.

    Und das habe ich jetzt schnell in einer Minute gehackt, verzeih mir bitte, dass ich keine Prüfung auf die Architektur und damit eine Definition für einen korrekt Formatspezifizierer gemacht habe. :p

    DirkB schrieb:

    Wir haben mittlerweile 2014. Da darf man ruhig mal einen 15 Jahre alten Standard dem 25 Jahre Altem vorziehen.

    Recht hast du schon, keine Frage. Aber wenn die Compilerhersteller *hust*Microsoft*hust* da blockieren, ist es egal, wenn du deine Software mit total neuen und auch zugegebenermaßen tollen Features programmierst. Kompiliert nicht => Tonne.
    Ja gut, gibt auch andere Compiler. Aber ich seh das so, dass am immer für die kleinste gemeinsame Menge programmieren sollte. Außerdem programmiert man meines Erachtens so auch bewusster - aber das ist nur meine Meinung.


  • Mod

    Ich würde da ganz andere Prioritäten setzen. Wen interessiert ein Cast beim malloc? Klar, es zeigt, der Programmierer schreibt irgendwo ab und das vermutlich aus dubioser Quelle, aber es ist nicht krass falsch, bloß unnötig und schlechter als kein Cast.

    Ohne besondere Reihenfolge, außer den ersten und letzten beiden:

    • gets . Natürlich auch scanf mit weg gelassener Längenangabe
    • printf(vom_benutzer_eingegebener_string); Gilt natürlich auch für verwandte Funktionen. Oder diese beiden Punkte zusammen gefasst: Dem Nutzer vertrauen.
      -prüfen, lesen, verarbeiten; anstatt lesen, prüfen, verarbeiten (dieser Punkt ist hart auf der Grenze zwischen No-Go und Fehler)
      -globale Variablen
    • goto
      -Code schreiben (oder gar zusammen kopieren) ohne ihn genau zu verstehen
    • fflush(stdin);
      -C mit C++ mischen
      -Das Rad neu erfinden
      -Einem Rad aus fremder Herstellung zu sehr vertrauen :p

    Die sind natürlich allesamt bieg- und brechbar, sofern man ganz genau weiß, was man wieso tut.



  • 0. C benutzen, wenn man stattdessen C++ benutzen könnte...



  • dachschaden schrieb:

    2. Variablen in for-Headern deklarieren.

    Variablen sollten generell so spät wie möglich deklariert bzw. definiert werden. VS 2013 unterstützt C99 Variablen-Deklarationen.

    http://blogs.msdn.com/b/vcblog/archive/2013/06/28/c-11-14-stl-features-fixes-and-breaking-changes-in-vs-2013.aspx

    dachschaden schrieb:

    4. Cast von malloc (auch von DirkB).

    Das ist in C absolut richtig!

    SeppJ schrieb:

    -globale Variablen

    Man sollte globale Variablen möglichst meiden, aber man kann sie kaum verhindern, weshalb sie nicht in die Kategorie No-Go fallen.

    SeppJ schrieb:

    • goto

    Das stimmt in 98-99% der Fälle, aber gotos, die eigentlich zu Spaghetti-Code führen, können in manchen Fällen zu lesbarerem Code führen. Stichwort verschachtelte Schleifen oder Fehlerbehandlung. gotos sind außerdem effizienter und du findest sie auch im Linux-Kernel.

    L. G.,
    IBV


  • Mod

    IBV schrieb:

    dachschaden schrieb:

    4. Cast von malloc (auch von DirkB).

    Das ist in C absolut richtig!

    Einschränkung: Es ist nicht direkt falsch.

    Man sollte globale Variablen möglichst meiden, aber man kann sie kaum verhindern, weshalb sie nicht in die Kategorie No-Go fallen.
    [...]
    Das stimmt in 98-99% der Fälle, aber gotos, die eigentlich zu Spaghetti-Code führen, können in manchen Fällen zu lesbarerem Code führen. Stichwort verschachtelte Schleifen oder Fehlerbehandlung. gotos sind außerdem effizienter und du findest sie auch im Linux-Kernel.

    Deswegen ja auch die bewusste Einschränkung, dass diese Regeln bieg- und brechbar sind, vorausgesetzt, dass man genau(!) weiß, was man da tut. Globale Variablen, weil es absolut nicht anders geht oder ein goto, das (nach reichlicher Abwägung!) zu besserem Code führt, sind absolut ok. Absolut nicht ok sind globale Variablen oder goto aus Gründen wie "ich habe keine Lust", "ich weiß es nicht besser", "warum nicht?", "hab ich anderswo so gesehen" und ähnlichem.



  • SeppJ schrieb:

    , dass man genau(!) weiß, was man da tut. Globale Variablen, weil es absolut nicht anders geht oder ein goto, das (nach reichlicher Abwägung!) zu besserem Code führt, sind absolut ok.

    Das wäre ein Freibrief für alle, denn niemand wird zugeben, dass er wenig Ahnung und keinen Überblick hat und diesen "Freiraum" nutzen.



  • Wutz schrieb:

    SeppJ schrieb:

    , dass man genau(!) weiß, was man da tut. Globale Variablen, weil es absolut nicht anders geht oder ein goto, das (nach reichlicher Abwägung!) zu besserem Code führt, sind absolut ok.

    Das wäre ein Freibrief für alle, denn niemand wird zugeben, dass er wenig Ahnung und keinen Überblick hat und diesen "Freiraum" nutzen.

    Diese Einschätzung entspricht überhaupt nicht meiner Erfahrung.



  • goto kann man benutzen, muss man aber nicht. Die folgenden drei Programmausschnitte erreichen das gleiche einmal mit Blöcken, einmal mit goto und einmal mit Funktionsaufrufen.

    Man erkennt, dass die horizontale Verschachtelungstiefe der Blöcke mit jeder Ressource zunimmt. Das ist ab drei oder vier Stück nicht unbedingt lesbarer als goto .

    if (allocate_A())
    {
    	if (allocate_B())
    	{
    		if (allocate_C())
    		{
    			puts("Hat alles geklappt");
    			free_C();
    		}
    		free_B();
    	}
    	free_A();
    }
    

    Der Code wächst mit goto nicht in die Breite. Außerdem stehen alle Allokationen an einem Ort und alle Freigaben an einem anderen. Der gesamte Code für den Erfolgsfall steht in der zweiten Spalte, der Code für den Fehlerfall in der ersten (die Labels) und dritten (die goto s).

    if (!allocate_A())
    	{
    		goto fail_A;
    	}
    	if (!allocate_B())
    	{
    		goto fail_B;
    	}
    	if (!allocate_C())
    	{
    		goto fail_C;
    	}
    	puts("Hat alles geklappt");
    	free_C();
    fail_C:
    	free_B();
    fail_B:
    	free_A();
    fail_A:
    

    Die einzelnen Funktionen sind kurz und die Ressourcensituation ist sehr gut überschaubar. Man erkennt sehr leicht, dass Anforderung und Freigabe paarweise und sehr lokal erfolgen. Der Kontrollfluss ist etwas schwieriger zu erkennen, weil globale Symbole und nicht nur lokale Sprungmarken angesprungen werden.

    static void execute_C_step()
    {
    	if (allocate_C())
    	{
    		puts("Hat alles geklappt");
    		free_C();
    	}
    }
    
    static void execute_B_step()
    {
    	if (allocate_B())
    	{
    		execute_C_step();
    		free_B();
    	}
    }
    
    static void execute_A_step()
    {
    	if (allocate_A())
    	{
    		execute_B_step();
    		free_A();
    	}
    }
    

  • Mod

    Da sich die ganzen unregistrierten Trolle hier als ein und dieselbe Person heraus gestellt haben (C0d3rC), habe ich den Thread mal radikal zurecht gestutzt. Im Prinzip war alles nach C0d3rCs erstem Beitrag nur auf seine Provokationen zurück zu führen und den Speicherplatz nicht wert. Da es sonst anscheinend nicht wichtiges mehr zu sagen gab, mache ich auch mal zu, falls er es noch einmal versuchen sollte.


Anmelden zum Antworten