Willkommen ~Gast!
Registrieren || Einloggen || Hilfe/FAQ || Staff
Probleme mit der Registrierung im Forum? Melde dich unter registerEin Bild.
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:
Quellcode:#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

#define SIZE    10

void print_digits(int *pDig, int n);

int main(int argc, char *argv[]) {
    if(argc != 2) {
        printf("Usage: %s <textfile>\n", argv[0]);
        return EXIT_FAILURE;
    } else {
        FILE *pFile;
        pFile = fopen(argv[1], "r");
        if(pFile == NULL) {
            printf("Couldn't load \'%s\'.\n", argv[1]);
            return EXIT_FAILURE;
        } else {
            int ch;
            int count = 0;
            int *pDigits = (int*) calloc(SIZE, sizeof(int));
            if(pDigits == NULL) {
                printf("Couldn't allocate memory.\n");
                fclose(pFile);
                return EXIT_FAILURE;
            }
            while((ch = fgetc(pFile)) != EOF) {
                if(isdigit(ch)) {
                    count++;
                    pDigits[ch-'0']++;
                }
            }
            if(count != 0) {
                printf("File \'%s\' contains %d digits:\n", argv[1], count);
                print_digits(pDigits, count);
            } else
                printf("File \'%s\' contains no digits.\n", argv[1]);
            free(pDigits);
            fclose(pFile);
        }
    }
    return 0;
}

void print_digits(int *pDig, int n) {
    int i;
    for(i = 0; i < SIZE; i++)
        printf("* %d: %6d %6.2f%%\n", i, pDig[i], (float)pDig[i]/n*100);
}
Klappt auch alles und es tut, was es soll. Nun meine Fragen, die nicht unbedingt auf technische Aspekte eingehen:

- Ist der Aufbau so in Ordnung?
- Wo könnte es Schwachstellen geben, was könnte man besser/übersichtlicher schreiben?
- Was gibt es generell zu verbessern?
- argv[0] ok, oder lieber separates char* pName benutzen?
- mehr Funktionen benutzen?

Zum Beispiel:
Was wäre besser: erste Variante mit großer else-Anweisung oder die Zweite nur mit kurzer if-Abfrage?

Quellcode:// 1ste Variante:
main() {
  FILE *pFile;
  pFile = fopen("text.txt", "r");
  if(pFile == NULL) {
    //error -> exit
  } else {
    // viele Anweisungen
    fclose(pFile);
  }
}
// 2te Variante:
main() {
  FILE *pFile;
  pFile = fopen("text.txt", "r");
  if(pFile == NULL) {
    //error -> exit
  }
  // viele Anweisungen
  fclose(pFile);
}
Um solche Dinge geht es. Hoffentlich nicht zu viel, Danke :)

--


Dieser Beitrag wurde am 01.09.2010 um 21:13 von Raziel bearbeitet.
zum Seitenanfang zum Seitenende Profil || Suche
001
02.09.2010, 01:42
Bluthund



Zitat:
- Wo könnte es Schwachstellen geben, was könnte man besser/übersichtlicher schreiben?
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.

Zitat:
Was wäre besser: erste Variante mit großer else-Anweisung oder die Zweite nur mit kurzer if-Abfrage?
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.)

Zitat:
- Was gibt es generell zu verbessern?
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.
Zitat:
Quellcode:/* ... */
            if(count != 0) {
                printf("File \'%s\' contains %d digits:\n", argv[1], count);
                print_digits(pDigits, count);
            } else
                printf("File \'%s\' contains no digits.\n", argv[1]);
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.

Zitat:
- argv[0] ok, oder lieber separates char* pName benutzen?
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)).

Zitat:
- mehr Funktionen benutzen?
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)
{
    for (int i = 0; i < size; ++i) {
        printf("* %d: %6d %6.2f%%\n", i, freqArray[i], (float)freqArray[i]/total_count*100);
    }
}
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.
"humorig is n blödwort :>" by -CarniGGeLjumpR-


Dieser Beitrag wurde am 02.09.2010 um 02:21 von Bluthund bearbeitet.
zum Seitenanfang zum Seitenende Profil || Suche
002
02.09.2010, 08:49
Adrian_Broher
Admin


Quellcode:if(pDigits == NULL) { Dieser Schnippsel wird bei einem Tippfehler zu einem Fehlverhalten fuehren und dennoch sauber kompilieren. Es macht meiner Meinung nach mehr Sinn R-Values auf die linke Seite Vergleichsoperators schreiben, dann nimmt sich der Kompiler der Sache an.

Quellcode:if(NULL == pDigits) { Ausserdem solltest du entweder immer die Ungarische Notation nutzen oder nie, aber nicht ab und zu. (pDigits, pFile, pDig im Gegensatz zu ch, count). Das gleiche gilt auch fuer EXIT_FAILURE, fuer welches es auch das Gegenstueck EXIT_SUCCESS gibt.

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.
If you think it's simple, then you have misunderstood the problem.
When a customer says "nothing has changed", assume they're lying.

zum Seitenanfang zum Seitenende Profil || Suche
003
02.09.2010, 13:33
Kriz



Quellcode:if(count != 0) Sowas kann man reduzieren auf die Implizierung
Quellcode:if(count) Ansonsten gebe ich Bluthund recht, wenn er die "mangelhafte" Formatierung des Quellcodes anprangert. Ich persönlich formatiere Blöcke bspw. so:
Quellcode:if(abc != xyz)
{
    ...
}
else
{
    ...
}
Imho das übersichtlichste Verfahren. Man muß nur konsequent bleiben ;)

@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
Quellcode:if(!pDigits)

--

K:R-I)Z++
"CSS ist cascading style sheets. Und nicht so'n Ranzspiel." - dp
In memory of Voice († 2005/03/30)

zum Seitenanfang zum Seitenende 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:

Zitat:
Bluthund postete
Zitat:
- Wo könnte es Schwachstellen geben, was könnte man besser/übersichtlicher schreiben?
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.
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.

Zitat:
Bluthund postete
Zitat:
Was wäre besser: erste Variante mit großer else-Anweisung oder die Zweite nur mit kurzer if-Abfrage?
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.)
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.

Zitat:
Bluthund postete
Zitat:
- Was gibt es generell zu verbessern?
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.
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:

Quellcode:int *pDigits;
pDigits  = (int*) calloc(SIZE, sizeof(int));
Eben wie bei Schleifen, dass die Laufvariable vor dem Schleifenkopf deklariert wird. Habe ich hoffentlich so richtig verstanden.

Zitat:
Bluthund postete
Zitat:
- argv[0] ok, oder lieber separates char* pName benutzen?
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.
Sehr gut, irgendwas muss ja auch mal richtig sein ;)

Zitat:
Bluthund postete
Vorschlag daher (wenn die Funktion denn drin bleiben soll):
Quellcode:void print_frequencies(int *freqArray, size_t size, int total_count)
{
    for (int i = 0; i < size; ++i) {
        printf("* %d: %6d %6.2f%%\n", i, freqArray[i], (float)freqArray[i]/total_count*100);
    }
}
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:

Zitat:
Adrian_Broher postete
Quellcode:if(pDigits == NULL) { Dieser Schnippsel wird bei einem Tippfehler zu einem Fehlverhalten fuehren und dennoch sauber kompilieren. Es macht meiner Meinung nach mehr Sinn R-Values auf die linke Seite Vergleichsoperators schreiben, dann nimmt sich der Kompiler der Sache an.
Quellcode:if(NULL == pDigits) {
Das wusste ich (auch) noch nicht und werde ich mir noch genauer ansehen.

Zitat:
Adrian_Broher postete
Ausserdem solltest du entweder immer die Ungarische Notation nutzen oder nie, aber nicht ab und zu. (pDigits, pFile, pDig im Gegensatz zu ch, count). Das gleiche gilt auch fuer EXIT_FAILURE, fuer welches es auch das Gegenstueck EXIT_SUCCESS gibt.
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.
zum Seitenanfang zum Seitenende Profil || Suche
005
02.09.2010, 13:59
Raziel



Zu Kriz:

Zitat:
Kriz postete
Quellcode:if(count != 0) Sowas kann man reduzieren auf die Implizierung
Quellcode:if(count)
Wäre eine Überlegung wert, aber einfacher-halb werde ich es anfänglich bei 'count != 0' belassen.

Zitat:
Kriz postete
Ansonsten gebe ich Bluthund recht, wenn er die "mangelhafte" Formatierung des Quellcodes anprangert. Ich persönlich formatiere Blöcke bspw. so:
Quellcode:if(abc != xyz)
{
    ...
}
else
{
    ...
}
Imho das übersichtlichste Verfahren. Man muß nur konsequent bleiben ;)
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:

Quellcode:/* 1ste: */
main()
{
    int i;
    /* und alle anderen Veriablen */

    /* viele andere Anweisungen, die auch zum vorzeitigen Abbruch führen könnten */

    for(i = 0; i < n; ++i)
    {
        /* Ausgabe */
    }
}

/* 2te: */

main()
{
    /* viele andere Anweisungen, die auch zum vorzeitigen Abbruch führen könnten */

    int i;    
    for(i = 0; i < n; ++i)
    {
        /* Ausgabe */
    }
}

--


Dieser Beitrag wurde am 02.09.2010 um 14:44 von Raziel bearbeitet.
zum Seitenanfang zum Seitenende 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:

Quellcode:#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>

#define    SIZE    26

int main(int argc, char *argv[])
{
    /* Variablen anfänglich deklarieren?
    FILE *pFile;
    int nChar;
    int nTotal = 0;
    int *pLetters;
    */

    if(argc != 2)
    {
        fprintf(stderr, "Usage: %s <textfile>\n", argv[0]);    /* so richtig? */
        return EXIT_FAILURE;
    }

    FILE *pFile;
    pFile = fopen(argv[1], "r");
    
    if(!pFile)
    {
        fprintf(stderr, "Couldn't load \'%s\'.\n", argv[1]);
        return EXIT_FAILURE;
    }

    int *pLetters;
    pLetters = (int*) calloc(SIZE, sizeof(int));

    if(!pLetters)
    {
        fprintf(stderr, "Couldn't allocate memory.\n");
        fclose(pFile);
        return EXIT_FAILURE;
    }

    int nChar; /* ungluecklicher Name? */
    int nTotal = 0;

    while((nChar = fgetc(pFile)) != EOF)
    {
        if(isalpha(nChar))
        {
            ++nTotal;
            if(isupper(nChar))
            {
                ++pLetters[nChar-'A'];
            }
            else if(islower(nChar))
            {
                ++pLetters[toupper(nChar)-'A'];
            }
        }
    }

    fclose(pFile);

    printf("* Statistic of \'%s\':\n", argv[1]);
    printf("* Total letters: %d\n", nTotal);
    printf("* Overview:\n");

    int i;
    for(i = 0; i < SIZE; ++i)
    {
        if(pLetters[i] != 0)
        {
            printf("%c: %6d %6.2f%%\n", (char)i+'A', pLetters[i], (float)pLetters[i]/nTotal*100);
        }
    }

    free(pLetters);

    return EXIT_SUCCESS;
}

--

zum Seitenanfang zum Seitenende Profil || Suche
007
02.09.2010, 16:35
Bluthund



Zitat:
Wegen calloc sollte der Code wohl so aussehen:

Quellcode:int *pDigits;
pDigits  = (int*) calloc(SIZE, sizeof(int));
Eben wie bei Schleifen, dass die Laufvariable vor dem Schleifenkopf deklariert wird. Habe ich hoffentlich so richtig verstanden.

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.

Zitat:
Und noch eine kurze Frage: Alle Variablen generell unmittelbar nach Einstieg in main() deklarieren (weil ggf. übersichtlicher) oder erst nach und nach?
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.

Quellcode:else if(islower(nChar))
{
    ++pLetters[toupper(nChar)-'A'];
}
*buzz* Arraygrenze ueberschritten. Sollte wohl -'a' sein.
Nutzung von fprintf passt. Ausser fuer die Usage-Meldung. Die sollte kein Fehler sein sondern eine normale Ausgabe. Der Fehler an der Stelle waere etwas wie "Zu wenig/viele Argumente".

--

The C language combines all the power of assembly language with all the ease-of-use of assembly language.
"humorig is n blödwort :>" by -CarniGGeLjumpR-

zum Seitenanfang zum Seitenende Profil || Suche
008
02.09.2010, 17:27
Raziel



Zitat:
Bluthund postete
[...] 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.
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 ;)

Zitat:
Bluthund postete
Quellcode:else if(islower(nChar))
{
    ++pLetters[toupper(nChar)-'A'];
}
*buzz* Arraygrenze ueberschritten. Sollte wohl -'a' sein.
Nutzung von fprintf passt. Ausser fuer die Usage-Meldung. Die sollte kein Fehler sein sondern eine normale Ausgabe. Der Fehler an der Stelle waere etwas wie "Zu wenig/viele Argumente".
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.
zum Seitenanfang zum Seitenende Profil || Suche
009
02.09.2010, 17:31
Bluthund



Oh stimmt, toupper() hatte ich uebersehen (weil nicht erwartet). Das funktioniert natuerlich.
Aber warum noch eine Funktion rufen wenn man auch einfach direkt mit dem Wert rechnen kann den man schon hat ;)
Quellcode:++pLetters[nChar-'a']; [edit]oder direkt die komplette Verzweigung weglassen und beide Faelle durch
Quellcode:if (isalpha(nChar)) {
    ++nCount;
    ++pLetters[tolower(nChar)-'a']; // toupper(nChar)-'A' funktioniert natuerlich auch.
}
abdecken.[/edit]
libc und CRT bezeichnen die C-Standard-Bibliothek (libc im unixoiden Umfeld (glibc bei GNU) und CRT [C Runtime] bei MS)

--

The C language combines all the power of assembly language with all the ease-of-use of assembly language.
"humorig is n blödwort :>" by -CarniGGeLjumpR-


Dieser Beitrag wurde am 02.09.2010 um 18:05 von Bluthund bearbeitet.
zum Seitenanfang zum Seitenende 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:

Zitat:
Bluthund postete
Quellcode:if (isalpha(nChar)) {
    ++nCount;
    ++pLetters[tolower(nChar)-'a'];
    /* tolower, da wahrscheinlich mehr Kleinbuchstaben im Text vorkommen
     * und damit je nach Implementierung von tolower() der Aufruf in den
     * meisten Faellen "schneller" zurueckkehrt
     */
}
Wird wohl die optimale (und auch "schönste") Lösung sein.

--


Dieser Beitrag wurde am 02.09.2010 um 18:07 von Raziel bearbeitet.
zum Seitenanfang zum Seitenende Profil || Suche
011
02.09.2010, 18:25
Bluthund



"Optimal" und "schoen" liegen immer im Auge des Betrachters :)
Der gemeine C-Hacker oder Leute mit extremen Anforderungen an die Ausfuehrungsgeschwindigkeit (Stichwort Embedded Systeme) wuerden wahrscheinlich lieber sowas sehen:
Quellcode:++pLetters[nChar-'A' & ~0x20]; /* 'A' extremely readable, consider using 0x41 */ ;)
edit: oderQuellcode:++pLetters[--nChar & ~0x60];

--

The C language combines all the power of assembly language with all the ease-of-use of assembly language.
"humorig is n blödwort :>" by -CarniGGeLjumpR-


Dieser Beitrag wurde am 02.09.2010 um 18:32 von Bluthund bearbeitet.
zum Seitenanfang zum Seitenende Profil || Suche
012
02.09.2010, 22:46
Raziel



Wow, nicht schlecht :)
Bit-Operatoren kenne ich zwar, aber kamen bei mir noch nie wirklich zum Einsatz, auf jeden Fall interessant (und lehrreich) zu sehen. Bis zum gemeinen C-Hacker wird es also noch dauern ;)

--


Dieser Beitrag wurde am 03.09.2010 um 00:44 von Raziel bearbeitet.
zum Seitenanfang zum Seitenende Profil || Suche
013
03.09.2010, 19:11
Prefect



Zitat:
Bluthund postete
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.
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,
Prefect

--

Widelands - Gemütliche Aufbaustrategie, Free Software
Noch ein Blog - Lerne, wie die Welt wirklich ist, aber vergiss niemals, wie sie sein sollte.

zum Seitenanfang zum Seitenende 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++
"CSS ist cascading style sheets. Und nicht so'n Ranzspiel." - dp
In memory of Voice († 2005/03/30)

zum Seitenanfang zum Seitenende Profil || Suche
015
03.09.2010, 22:24
caedes



Zitat:
Kriz postete
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.
wieso? was wäre daran falsch?

--

caedes

Deutschland rückt nach Einschätzung der Sicherheitsbehörden im Superwahljahr verstärkt ins Visier von Terroristen.

zum Seitenanfang zum Seitenende Profil || Suche
016
04.09.2010, 09:59
Kriz



Beispiel Kopieren von C Strings:
Quellcode:while(*dest++ = *src++); Der Algo braucht zwingend postfixes Zeigerinkrement, sonst funzt das nicht. Würde der Compiler daraus (optimierenderweise) irgendwas in Richtung prefixes Zeigerinkrement wurschteln oder irgendwie von der vom Programmierer gedachten Norm abweichen, wäre das Ergebnis nicht das, was der Programmierer erwartet hätte. Daher ist hier Optimieren nicht sinnvoll, sondern nur für Werteinkremente/-dekremente.

--

K:R-I)Z++
"CSS ist cascading style sheets. Und nicht so'n Ranzspiel." - dp
In memory of Voice († 2005/03/30)

zum Seitenanfang zum Seitenende Profil || Suche
017
04.09.2010, 11:09
Bluthund



Zitat:
Prefect postete
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.

Jeder Compiler? ;) Aber es ist natuerlich richtig, dass die meisten und vor allem aktuellen Compiler das selbststaendig herausfinden.

Zitat:
2. Selbst wenn der Compiler das nicht optimieren würde, wäre diese Art der Optimierung hier völlig daneben (siehe meine Signatur).
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?

Zitat:
Kriz postete
Beispiel Kopieren von C Strings: (...)
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. */
if (i++ && i) {} /* false fuer i = -1 und i = 0 */
/* ist nicht das selbe wie */
if (++i && i) {} /* false fuer i = -1; aequivalent zu if (++i) */
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.
"humorig is n blödwort :>" by -CarniGGeLjumpR-


Dieser Beitrag wurde am 04.09.2010 um 11:36 von Bluthund bearbeitet.
zum Seitenanfang zum Seitenende 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:

Quellcode:int i=0;

int x = i++; // x==0
// bzw
int x = ++i; // x==1

--

caedes

Deutschland rückt nach Einschätzung der Sicherheitsbehörden im Superwahljahr verstärkt ins Visier von Terroristen.

zum Seitenanfang zum Seitenende Profil || Suche
019
13.09.2010, 20:56
default



Was eine geile Diskussion.
Sich mit Prefect über c/++ zu streiten bringts btw nicht, zuhören ist aber schon sinnvoll.
Über den Sinn von++ im Vergleich ++zu braucht man daher nicht sprechen.

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.
Und, wenn man da mit Performance argumentieren wollte, möchte ich das gerne mal gebenched haben.

Zum Thema lesbarkeit spreche ich auch gegen if( NULL == val ), neuere gcc Versionen sagen bei assignments auch an.

Quellcode:#include <string.h>

int main()
{
    char *a = NULL;
    if( a = NULL )
        return 0;
    return 1;
}
Quellcode:gcc -Wall -o test test.c
test.c: In function ‘main’:
test.c:6: warning: suggest parentheses around assignment used as truth value
Ich erkenne das Argument an, aber da es nicht notwendig ist, und ich NULL == val unlogisch finde, find ich die lösung nicht vertretbar, wo -Wall das doch hervorragend löst.

Indenting hat aber noch keiner was zu gesagt, daher ...
Quellcode:if( foo == bar )
{

}
Die Syntax ist geschenkt, die Logik sollte jedoch hervorgehoben werden.

--

Du musst Deine Bandbreite verbreitern, damit du breiter wirst von der Bandbreite her und ein breiteres Publikum ansprechen kannst.

zum Seitenanfang zum Seitenende Profil || Suche