Autor | Beitrag |
---|
000 01.09.2010, 20:48 Raziel |
Hallo zusammen, da sich hier doch einige aufhalten, die Ahnung von Programmierung und C haben, hier ein kleines Anliegen: Ich habe ein einfaches Programm in C geschrieben, dass man per Kommandozeile und 1 zusätzlichen Parameter ausführen kann. Übergeben wird der Name einer Textdatei und das Programm zählt die Häufigkeit vorkommender Zahlen im Text und gibt die abs./rel. Häufigkeit aus. Hier der Code: - Ist der Aufbau so in Ordnung? Zum Beispiel:
Dieser Beitrag wurde am 01.09.2010 um 21:13 von Raziel bearbeitet. |
Profil || Suche |
001 02.09.2010, 01:42 Bluthund |
Leerzeilen! Versuche den Code auch optisch zu strukturieren. Dadurch dass du keine Leerzeilen verwendest und den Code in "Java-Style" (K&R mit Brace auch hinter dem Funktionskopf) geschrieben hast, mutiert das Ganze zu einem "riesigen" Textklumpen, der schnell schwierig zu ueberfliegen wird, weil sich dabei eigentlich nur die Blockenden ueberhaupt heraus heben. Jeweils eine Leerzeile vor und nach Kontrollstrukturen zu positionieren hilft der Uebersichtlichkeit in den meisten Faellen ungemein. Da du den ersten Zweig immer mit einem return verlaesst (guard clause), gibt es eigentlich keine Rechtfertigung dafuer den Rest des Codes in den else-Zweig zu stopfen und damit unnoetig tief einzuruecken. Daher meine Empfehlung: nimm die zweite Variante. Zu tiefe Einrueckung ist meist naemlich u.a. ein guter Indikator dafuer, dass eine Funktion viel zu viel tut und in mehrere Funktionen aufgeteilt werden sollte. Das sollte in so einem simplen Programm aber nicht der Fall sein. (Du haettest eigentlich sogar noch eine Stufe tiefer einruecken muessen wenn du konsistent else nach guard clauses eingesetzt haettest. Du benutzt naemlich bei der Pruefung von argc und pFile Variante 1 aber nach dem calloc von pDigits Variante 2.) Fehler gehoeren auf die Standardfehlerausgabe (stderr via fprintf) und nicht auf stdout (via printf). Der Rueckgabewert von malloc (wie auch der seiner Brueder calloc und realloc) sollte in C nicht direkt auf den Zieltyp gecastet werden, um zu vermeiden, dass Compilerfehler bei fehlender Header-Inkludierung unterdrueckt werden (C hat implizite Funktionsdeklaration). Hilf deinem Compiler ein bisschen und ersetze die post-increments durch pre-increments wenn du den alten Wert der Variable von vor der Inkrementierung nicht benoetigst. Wie bereits oben erwaehnt: Einrueckung (Tiefe 5 muss bei der Funktion nicht sein) und Leerzeilen. Das stoesst mir persoenlich ganz uebel auf. Coding-Style ist natuerlich immer stark subjektiv und ich hab fuer mich auch mittlerweile den Stil adoptiert, dass ich prinzipiell Braces setze auch wenn sie auslassbar sind (so muss man sich weniger Gedanken bei spaeterer Bearbeitung machen). Aber bei diesem else-Zweig leidet die Konsistenz doch ganz schoen; rein visuell. \' ist hier ueberfluessig wenn auch nicht falsch. Ein einfaches ' tut es aber auch. Bringt eigentlich keinen Gewinn fuer die Leserlichkeit. Dass in argv[0] in main() der Programmname steht, sollte eigentlich jeder wissen, der sich C-Quellen anschaut. Auch die direkte Nutzung von argv[1] in so einem trivialen Programm mit nur einem Parameter ist eigentlich voellig ok. Wenn es mehr Parameter werden solltest du den Dingern natuerlich auch sinnvolle Namen geben und dann auch ab einem gewissen Grad nicht mehr von Hand parsen (siehe getopt(3)). Macht aufgrund der nicht-vorhandenen Komplexitaet von Programm und Problem kaum Sinn. Selbst print_digits() ist eigentlich schon zu viel des Guten, da sie nur einmal ueberhaupt gerufen wird und nur eine Schleife ausfuehrt, die auch noch an den globalen Zustand "Arraygroesse" gebunden ist und somit in der Form sowieso so gut wie keine Flexibilitaet bietet. Zum Interface von print_digits(): Die meisten C-Programmierer wuerden wohl (ohne in die Implementierung geschaut zu haben) vermuten, dass der Funktion als zweites Argument die Groesse des im ersten Parameter uebergebenen Arrays zu uebergeben ist. Auch wenn dann normalerweise size_t und nicht int zum Einsatz kommen sollte, aber n sagt halt so gar nichts aus. Der Name der Funktion selbst ist auch irrefuehrend. Die Funktion gibt nicht nur Ziffern aus sondern vorallem deren Haeufigkeiten. Das sollte der Name auch widerspiegeln. Vorschlag daher (wenn die Funktion denn drin bleiben soll): Quellcode:void print_frequencies(int *freqArray, size_t size, int total_count) Im Normalfall solltest du natuerlich versuchen das Programm sinnvoll zu strukturieren und imo in main() selbst so wenig wie moeglich machen.Das macht im konkreten Fall aber wie gesagt eigentlich keinen Sinn, da die Usage-Meldung nur eine Zeile hat, das Parsen der Kommandozeilenparameter nicht-existent ist und das Auslagern der Hauptfunktionalitaet hier den Lesefluss eher stoeren wuerde als dem Leser zu helfen. -- The C language combines all the power of assembly language with all the ease-of-use of assembly language. Dieser Beitrag wurde am 02.09.2010 um 02:21 von Bluthund bearbeitet. |
Profil || Suche |
002 02.09.2010, 08:49 Adrian_Broher Admin |
Auch stossen mir die verstuemmelten Variablennamen und die namenlose Schleifenvariable in print_digits auf. --There is nothing wrong with high standards. It's your problem that you don't meet them. |
Profil || Suche |
003 02.09.2010, 13:33 Kriz |
@Adrian_Broher: Das Vertauschen des R-Values nach Links raubt der Aussage aber den Sinn: Wenn pDigits gleich NULL ist... Bei dir würde es dann bedeuten: Wenn NULL gleich pDigits ist... NULL ist Nullzeiger per Definition, wieso sollte es auf Gleichheit mit dem Inhalt eines x-beliebigen Zeigers getestet werden? Das sorgt im semantischen Grunde für mehr Verwirrung als nötig, und C ist nicht immer von Haus aus wirrenfrei für Anfänger. Es ist ja nicht falsch, aber unnötig. Ansonsten würde hier auch die Implizierung greifen K:R-I)Z++ |
Profil || Suche |
004 02.09.2010, 13:51 Raziel |
Vielen Dank für eure beiden Antworten, genau das, was ich gehofft hatte zu lesen! Zu Bluthund: Werde ich berücksichtigen und hatte ich in meinen ersten Programmen in der Weise auch geschrieben. Dann habe ich etliche Vorlesungen über Java besucht und seitdem hatte ich es mir angewöhnt die Braces direkt hinter den Kopf zu schreiben. Leider ist das auch immer verschieden, hier mal so, dort wieder anders und so weiter. Aber der Optik hilft es natürlich immens klare Blöcke zu haben und bei größerem Quellcode wird es später einfach sein Übersicht zu wahren. Gut, dann wird ab jetzt die 2te Variante genommen :) Bei der Prüfung von argc und pFile hatte ich auf eine else-Anweisung verzichtet, weil die Einrückungen selbst für mich zu viel wurden und ich wollte nicht noch tiefer einrücken, hätte ich an sich natürlich konsequent weiter führen müssen. Den Begriff Guard Clause kannte ich übrigens auch noch nicht. Standardfehlerausgabe per fprintf sagt mir auch nicht viel, aber da wird sich bestimmt Material im Internet zu finden lassen, wird nachgeschlagen. Wegen calloc sollte der Code wohl so aussehen:
Sehr gut, irgendwas muss ja auch mal richtig sein ;) Sieht eindeutig besser aus und es ist leichter zu verstehen, was passieren soll. Gernerell muss ich an der Konsequent der Braces und der Namensgebung wohl noch arbeiten und beides verbessern (vor allem sinnvollere Namen) und du hast du recht, dass man an der Funktions-Implementierung leicht in die Irre geführt wird und es nicht klar ist, was die Funktion bewerkstelligt. Die Sache mit dem size_t werde ich mir noch ansehen. Ganz davon abgesehen, ob die Funktion im Rahmen der Größe überhaupt Sinn macht. Zu Adrian: Das wusste ich (auch) noch nicht und werde ich mir noch genauer ansehen. Da hast du Recht. Ich sollte dann auch konsequenter sein und die Notation beibehalten. Bei den "einfachen" Variablen dachte ich mir, dass es ausreichen würde sie so zu benennen und z.B. nur bei Pointer/Arrays ein 'p' vorzustellen. Hoffe ich habe keinen Aspekt übersehen und alles angesprochen, noch mal großes Danke! --Dieser Beitrag wurde am 02.09.2010 um 14:01 von Raziel bearbeitet. |
Profil || Suche |
005 02.09.2010, 13:59 Raziel |
Zu Kriz: Wäre eine Überlegung wert, aber einfacher-halb werde ich es anfänglich bei 'count != 0' belassen. Da habt ihr drei natürlich Recht, dass dies die wohl übersichtlichste Methode ist und optisch den Code "auseinander" zieht und es einfacher macht sich zu einem späteren Zeitpunkt wieder zurechtzufinden. Besonders wenn der Quellcode erheblich länger wird als meine paar Zeilen. Ergo: wird so übernommen :) Auch dir Danke! Und noch eine kurze Frage: Alle Variablen generell unmittelbar nach Einstieg in main() deklarieren (weil ggf. übersichtlicher) oder erst nach und nach? Einfaches Beispiel:
Dieser Beitrag wurde am 02.09.2010 um 14:44 von Raziel bearbeitet. |
Profil || Suche |
006 02.09.2010, 15:41 Raziel |
Auch auf die Gefahr hin, dass es so langsam unübersichtlich werden könnte: hier ein ähnlich kurzer Code, mit dem Unterschied, dass er die Häufigkeit von Buchstaben auswertet. Vielleicht gibt es noch Ungereimtheiten, aber die Art und Weise gefällt mir nun schon deutlich besser, vor allem die doch recht solide Übersichtlichkeit und Gliederung:
|
Profil || Suche |
007 02.09.2010, 16:35 Bluthund |
Nein, du hast nur die Initialisierung des Pointers entfernt und weist ihn jetzt stattdessen zu (und castest dabei weiterhin). Gemeint war es so: Quellcode:int *pDigits = calloc(SIZE, sizeof(int)); Hier geht es nicht um Konsistenz sondern um Fehlervermeidung. Wenn du naemlich den entsprechenden Header nicht inkludierst (im konkreten Fall stdlib.h) baut dir C automatisch bei der ersten Verwendung einer Funktion einen Prototypen fuer diese zusammen falls noch keiner existiert. Und diese Prototypen geben int zurueck. Wenn du jetzt explizit castest (via (int*)) denkt der Compiler du weisst was du da tust. Dummerweise kannst du in C alles wild umher casten sobald Pointer involviert sind. Also auch int nach int* (bei einer 64bit-Kompilierung wuerde das hoechstens eine Warnung ausspucken, dass die beiden Typen nicht gleich breit sind).Neuere Compiler wie der gcc 4.4 spucken zusaetzlich noch eine Warnung wenn "built-in functions" mit abweichender Signatur definiert werden (egal ob implizit oder explizit). Das Ganze kompiliert und linkt gegen die libc bzw. CRT aber einwandfrei und kann je nach Plattform bei der Ausfuehrung funktionieren, nicht funktionieren oder deine Nachbarschaft abbrennen. Das kommt darauf an ob du Variante 2 ueberhaupt benutzen kannst. Bis C89 (C90) kannst du Variablen nur am Anfang eines Blocks definieren (sprich: Variante 1 nutzen). Das hiesse du muesstest wenn dir nur C89 zur Verfuegung steht fuer Variante 2 jedes Mal einen Block oeffnen und laeufst damit schnell wieder Gefahr tiefer einzuruecken als fuer die Funktion noetig. Wenn du allerdings auf C99 zurueckgreifen kannst, wuerde ich persoenlich zu Variante 2 (mit Einschraenkung; siehe unten) greifen. Das hat vornehmlich etwas mit der "Live Time" (nicht Lebenszeit!) der Variable zu tun. Wenn du alles am Anfang definierst dann haben alle deine Variablen eine "Live Time" von diesem Punkt bis zum Punkt ihrer letzten Verwendung (sie existieren danach natuerlich weiter bis zum Ende ihres Geltungsbereichs (Scope)). Die "Live Time" bezeichnet quasi die Zeit in der die Variable "live" ist, also verwendet wird. Viele hohe "Live Times" bedeuten also, dass sich jemand, der den Code liest, viele Variablen und deren Bedeutung ueber "lange" Zeit behalten muss und einige davon vielleicht erst sehr spaet in die Funktion hinein ueberhaupt benutzt werden. Zusaetzlich ist es sinnvoll Variablen den kleinsten Scope zu geben, der moeglich ist (die Ausnahme bilden hier Schleifen, wo der Scope einer Variable bspw. aus Performancegruenden vergroessert wird). Wenn du aber C99 verwenden kannst dann solltest du auch aus dem Vollen schoepfen und den Schleifen-Index einer Zaehlschleife innerhalb ihres Kopfes definieren. Quellcode:for (int i … ausser natuerlich wenn mittels break aus der Schleife ausgebrochen wird und der Wert des Index nach dem Austritt noch verwendet werden muss.
The C language combines all the power of assembly language with all the ease-of-use of assembly language. |
Profil || Suche |
008 02.09.2010, 17:27 Raziel |
Okay, Erklärung macht Sinn und ist für mich nachvollziehbar. Zum Problem der Variablendefinition: werde es dann wie in der zweiten Variante halten und die "Live-Time" bzw den Scope der betreffenden Variable beachten und die Variable ggf. am Anfang oder zum passenden Zeitpunkt erstellen. Also nach C99. Und auch Schleifenvariablen künftig anders handhaben und erst im Schleifenkopf definieren, außer es tritt zum Beispiel die von dir bereits genannte Ausnahme (break oder vergleichbares) auf. Begriffe libc bzw. CRT habe ich schon mal gehört, sagen mir aber gerade nicht wirklich viel, wird auch nachgeschlagen. Die Liste wird stetig länger ;) Bist du dir sicher? Per toupper(nChar) wird ja aus einem Kleinbuchstabe der entsprechende Großbuchstabe und dann mit -'A' meiner Meinung nach richtig in das Array eingeordnet. Die Fehlerausgabe wird verbessert, so macht das ganz - richtiger weiße - wenig Sinn. Danke :) --Dieser Beitrag wurde am 02.09.2010 um 17:33 von Raziel bearbeitet. |
Profil || Suche |
009 02.09.2010, 17:31 Bluthund |
Oh stimmt, toupper() hatte ich uebersehen (weil nicht erwartet). Das funktioniert natuerlich. The C language combines all the power of assembly language with all the ease-of-use of assembly language. Dieser Beitrag wurde am 02.09.2010 um 18:05 von Bluthund bearbeitet. |
Profil || Suche |
010 02.09.2010, 17:50 Raziel |
Stimmt, da hast du Recht, toupper() ist in diesem Fall überflüssig. Mit ++pLetters[nChar-'a']; geht es natürlich einwandfrei, Denkfehler meinerseits. Danke für deine echt ausführliche Hilfe :) Edit: Wird wohl die optimale (und auch "schönste") Lösung sein. -- Dieser Beitrag wurde am 02.09.2010 um 18:07 von Raziel bearbeitet. |
Profil || Suche |
011 02.09.2010, 18:25 Bluthund |
"Optimal" und "schoen" liegen immer im Auge des Betrachters :) The C language combines all the power of assembly language with all the ease-of-use of assembly language. Dieser Beitrag wurde am 02.09.2010 um 18:32 von Bluthund bearbeitet. |
Profil || Suche |
012 02.09.2010, 22:46 Raziel |
Wow, nicht schlecht :) Dieser Beitrag wurde am 03.09.2010 um 00:44 von Raziel bearbeitet. |
Profil || Suche |
013 03.09.2010, 19:11 Prefect |
Das ist Unfug, aus zwei Gründen: 1. Jeder Compiler optimiert das sowieso, so dass post-increment und pre-increment hier äquivalent sind. Wenn dem nicht so ist, kann man das getrost als Bugreport melden. 2. Selbst wenn der Compiler das nicht optimieren würde, wäre diese Art der Optimierung hier völlig daneben (siehe meine Signatur). Davon einmal abgesehen: Es ist tatsächlich nicht verkehrt, sich pre-increments anzugewöhnen. Wenn man nämlich irgendwann einmal C++ programmieren sollte und damit überladene operator++ verwendet (z.B. bei Iteratoren) kann man tatsächlich in eine Situation kommen, in der der Compiler den Unterschied zwischen post- und pre-increment nicht mehr wegoptimieren kann. Also wie gesagt, pre-increments sind eine feine Sache, aber bitte mit der richtigen Begründung. cu, Widelands - Gemütliche Aufbaustrategie, Free Software |
Profil || Suche |
014 03.09.2010, 20:32 Kriz |
Ich hoffe, daß macht er dann nur für Werte und nicht auch für Zeiger. Das wäre dann auch ein Grund, einen Bug zu melden. --K:R-I)Z++ |
Profil || Suche |
015 03.09.2010, 22:24 caedes |
wieso? was wäre daran falsch? -- caedes Deutschland rückt nach Einschätzung der Sicherheitsbehörden im Superwahljahr verstärkt ins Visier von Terroristen. |
Profil || Suche |
016 04.09.2010, 09:59 Kriz |
Beispiel Kopieren von C Strings: K:R-I)Z++ |
Profil || Suche |
017 04.09.2010, 11:09 Bluthund |
Jeder Compiler? ;) Aber es ist natuerlich richtig, dass die meisten und vor allem aktuellen Compiler das selbststaendig herausfinden. Ok, kann man so sehen. Aber Prefix- statt Postfix-Operatoren (da, wo es sinnvoll ist) in C tun der Leserlichkeit des Ausdrucks keinen Abbruch und bringen auch keinen Mehraufwand fuer den Programmierer mit. Von daher wuerde ich mir an dieser Stelle willens die Kappe der verfruehten Optimierung aufsetzen wenn du den konkreten Fall so bezeichnen moechtest. Auch wenn ich es persoenlich mehr als Hilfe fuer den Compiler betrachte als als Optimierung. Denn wenn ich weiss, dass ich den alten Wert nicht brauche, warum soll es der Compiler herausfinden muessen? Sobald ein per Postfix-Operator veraenderter Wert gelesen wird, wird die Sache mit der genannten Optimierung (egal ob vom Compiler oder vom Programmierer) sowieso schwierig, da dann die Veraenderung ja erst unmittelbar vor dem naechsten Sequenzpunkt vollzogen werden darf. Und da ist es eigentlich unerheblich ob es um Pointer oder Werte geht. Beispiel: Quellcode:/* ACHTUNG: Furchtbar konstruiertes Beispiel voraus. */ Analog koennte dein Beispiel (mit Zuweisung - wenn auch fuer dieses wie auch dein Beispiel irrelevant, da das Ergebnis ja gleich der rhs ist - und Sequenzpunkt nach dem vollen Ausdruck) vom Compiler auch mit Wertvariablen nicht von postfix auf prefix geaendert:Quellcode:while (j = i++) { /* tue etwas bis i gleich 0 (inkl.) ist */ } Prefect hat sich ja mit "hier" wohl auch nur auf die hier genutzten Faelle (simple Ausdrucksanweisungen ohne Zuweisungsoperator oder sonstige lesende Zugriffe) bezogen.
--The C language combines all the power of assembly language with all the ease-of-use of assembly language. Dieser Beitrag wurde am 04.09.2010 um 11:36 von Bluthund bearbeitet. |
Profil || Suche |
018 04.09.2010, 19:16 caedes |
Jo, wenn man den Wert liest macht es *immer* einen Unterschied, ob man Präfix oder Postfix nutzt, auch bei primitiven Werten, z.b:
caedes Deutschland rückt nach Einschätzung der Sicherheitsbehörden im Superwahljahr verstärkt ins Visier von Terroristen. |
Profil || Suche |
019 13.09.2010, 20:56 default |
Was eine geile Diskussion. Zu den bitops, wer meint mit so Spielchen würde man irgendwas retten können, hat verloren, der Scheiss ist nur unlesbar und muss zwangsläufig kommentiert werden. Zum Thema lesbarkeit spreche ich auch gegen if( NULL == val ), neuere gcc Versionen sagen bei assignments auch an.
Indenting hat aber noch keiner was zu gesagt, daher ... Du musst Deine Bandbreite verbreitern, damit du breiter wirst von der Bandbreite her und ein breiteres Publikum ansprechen kannst. |
Profil || Suche |