*Markus: C: Verlinken statischer Bibliothek führt zu falschem Ergebnis

Hallo,

ich wollte testen, wie ich statische Bibliotheken verwenden kann. Dazu habe ich mir ein kleines Testprogramm geschrieben, das den Durchschnitt errechnet, wobei die Berechnung in der statischen Bibliothek vonstatten geht:

durchschnitt.h
---------------------------

  
#define MAX 30  
#include <stdio.h>  
#include <stdlib.h>  
  
double durchschnitt(double arr[], int arrused);  

durchschnitt.c
--------------------------

  
double durchschnitt(double arr[], int arrused)   {  
	  
	double wert = 0.0;  
	  
	for (int i = 0; i < arrused; i++)  {  
		 wert += arr[i];  
	}  
	  
	if (arrused != 0)  
		return wert /= arrused;  
	  
	return wert;	  
}  

Main.c
-----------------------------

  
#include "durchschnitt.h"  
  
int main(void)   {  
  
	int c;  
	int j;  
	int arrayused;  
	double array[MAX];  
	char hilfsarray[10];  
	j = arrayused = 0;  
	  
	while (arrayused < MAX && ((c = getchar()) != EOF))   {  
		 if ( (c >= '0' && c <= '9') || c == '.')  {  
		 	  hilfsarray[j++] = c;  
		 }  
		 else  {  
		 	  hilfsarray[j] = '\0';  
		 	  array[arrayused++] = atof(hilfsarray);  
		 	  j = 0;  
		 }  
		 		  
	}  
  
	for (int i = 0; i < arrayused; i++)  {  
		printf("Zahl %d: %f\n", i+1, array[i]);  
	}  
	  
	printf("Durchschnitt: %f\n", durchschnitt(array, arrayused));  
  
return 0;  
}  
  

Wäre dieser Code in einer einzigen Datei stehen, würde der Durchschnitte richtig berechnet werden. Ironischerweise funktionier es aber nicht, wenn ich dies als Bibliothek auslagere. Im Ergebnis werden nämlich Hausnummern angezeigt. Gebe ich zB nur eine Zahl ein, zB 5, bekomme ich als Ergebnis:
5
Zahl 1: 5.000000
Durchschnitt: 0.057253

Gebe ich 3 ein, bekomme ich folgendes:
3
Zahl 1: 3.000000
Durchschnitt: 0.073297

Der Code ist richtig. Ich habe alle per Copy&Paste aus der Gesamtdatei übernommen. Es muss also irgend etwas beim Zerteilen in Einzeldateien oder beim Kompilieren schief laufen, aber was? Ich ging dabei so vor:

$ gcc -c durchschnitt.c -o durchschnitt.o -std=c99
$ ar rcs libdurchschnitt.a durchschnitt.o
$ gcc -static durchschnitt.h Main.c -L. -ldurchschnitt -o Main_statisch -std=c99

Aufruf durch ./Main_statisch

Kann sich jemand dieses Phänomen erklären?

Danke,
Markus

  1. Ohne den Code gelesen zu haben, aber da gibts verschiedene Aufrufkonventionen. cdecl und so weiter, vielleicht liegts dran dass du die Funktion falsch aufrufst?

    1. Hallo,

      Ohne den Code gelesen zu haben, aber da gibts verschiedene Aufrufkonventionen. cdecl und so weiter, vielleicht liegts dran dass du die Funktion falsch aufrufst?

      ich wusste gar nicht, dass man sich darum kümmern muss, da dies ja der C-Compiler (gcc) für einen erledigt. Gibt es da irgend etwas, das ich wissen sollte?

      Markus

      1. ich wusste gar nicht, dass man sich darum kümmern muss, da dies ja der C-Compiler (gcc) für einen erledigt.

        Der erledigt es so wie man es ihm sagt, oder wie er es annimmt wenn man ihm nichts sagt. Aber ich hab echt keine wirkliche Ahnung davon, das ist schon ne Weile her.
        Was mich eh wundert, du sagst statische Bibliothek. Ist das nur eine separate Quellcodedatei oder was meinst du damit?
        Ne dll wär ja dynamisch, da zieht das mit den Konventionen.

  2. Moin Moin!

    Ich bin bei C etwas eingerostet, aber ...

    double wert = 0.0;

    0.0 ist ein single, kein double.

    for (int i = 0; i < arrused; i++)  {
    wert += arr[i];
    }

    if (arrused != 0)

    Auch wenn arrused<0? Nicht gut.

      return wert /= arrused;  
    

    return wert;

    x/0=0? Mein Mathelehrer rotiert im Grabe!

    }
    [/code]

    Main.c

    [code lang=c]
    #include "durchschnitt.h"

    int main(void)   {

    int c;
    int j;
    int arrayused;
    double array[MAX];
    char hilfsarray[10];
    j = arrayused = 0;

    while (arrayused < MAX && ((c = getchar()) != EOF))   {
    if ( (c >= '0' && c <= '9') || c == '.')  {
      hilfsarray[j++] = c;
    }
    else  {
      hilfsarray[j] = '\0';
      array[arrayused++] = atof(hilfsarray);

    atof liefert keine Fehlermeldungen, nutze strtod.

       	  j = 0;  
       }  
    

    Und was passiert, wenn c weder NUL noch Ziffer noch Punkt ist?

    $ gcc -c durchschnitt.c -o durchschnitt.o -std=c99
    $ ar rcs libdurchschnitt.a durchschnitt.o
    $ gcc -static durchschnitt.h Main.c -L. -ldurchschnitt -o Main_statisch -std=c99

    Laß gcc mal mit -Wall -pedantic laufen und beseitige alle Warnungen.

    Alexander

    --
    Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".
    1. Hallo Alexander,

      0.0 ist ein single, kein double.

      Nein, das ist falsch. 0.0 ist vom Typ double, 0.0f ist vom Typ float und 0.0L ist vom Typ long double.

      Viele Grüße,
      Christian

      --
      Mein "Weblog" [RSS]
      Using XSLT to create JSON output (Saxon-B 9.0 for Java)
      »I don't believe you can call yourself a web developer until you've built an app that uses hyperlinks for deletion and have all your data deleted by a search bot.«
                  -- Kommentar bei TDWTF
    2. Hallo,

      »» if (arrused != 0)

      Auch wenn arrused<0? Nicht gut.

      Kommt nicht vor, da man ja bei 0 anfängt zu zählen und nicht bei Minuszahlen.

      »» return wert /= arrused;
      »»
      »» return wert;

      x/0=0? Mein Mathelehrer rotiert im Grabe!

      Wie kommst du auf x/0? Was ist der Durschnitt einer einzelnen Zahl?

      »» }
      »» [/code]
      »»
      »» Main.c
      »» -----------------------------
      »»
      »» [code lang=c]
      »» #include "durchschnitt.h"
      »»
      »» int main(void)   {
      »»
      »» int c;
      »» int j;
      »» int arrayused;
      »» double array[MAX];
      »» char hilfsarray[10];
      »» j = arrayused = 0;
      »»
      »» while (arrayused < MAX && ((c = getchar()) != EOF))   {
      »» if ( (c >= '0' && c <= '9') || c == '.')  {
      »»   hilfsarray[j++] = c;
      »» }
      »» else  {
      »»   hilfsarray[j] = '\0';
      »»   array[arrayused++] = atof(hilfsarray);

      atof liefert keine Fehlermeldungen, nutze strtod.

      »»   j = 0;
      »» }

      Und was passiert, wenn c weder NUL noch Ziffer noch Punkt ist?

      Darum kümmert sich glücklicherweise atof.

      Markus

      1. Moin Moin!

        Hallo,

        »» »» if (arrused != 0)
        »»
        »» Auch wenn arrused<0? Nicht gut.

        Kommt nicht vor, da man ja bei 0 anfängt zu zählen und nicht bei Minuszahlen.

        Deine Funktion definiert arrused als signed int, und der kann negativ werden - auch gelegentlich mal durch ein ++.

        Wenn Du garantiert nur zählst, nimm keine signed ints, sondern ein unsigned int, unsigned long, oder unsigned long long.

        »» »» return wert /= arrused;
        »» »»
        »» »» return wert;
        »»
        »» x/0=0? Mein Mathelehrer rotiert im Grabe!

        Wie kommst du auf x/0?

        Definition des Durchschnitts: Summe aller Einzelelemente geteilt durch die Anzahl der Elemente.

        Wenn arrused ungleich 0 ist, berechnest Du wert/arrused und gibst das zurück. (Die Zuweisung an wert halte ich da übrigens für unnötig.) Ansonsten ist wert=0.0 und arrused=0.

        Du lieferst für das Ergebnis einer Division durch 0 schlicht 0 zurück, und das ist falsch.

        Was ist der Durschnitt einer einzelnen Zahl?

        Falsche Frage.

        Trotzdem erstmal die Antwort: Die Zahl selbst. Das wird durch if (arrused != 0) return wert /= arrused; korrekt abgedeckt.

        Was ist der Durchschnitt einer leeren Liste, wenn arrused==0?

        Er ist nicht definiert, Du gibst aber 0.0 zurück.

        »» Und was passiert, wenn c weder NUL noch Ziffer noch Punkt ist?

        Darum kümmert sich glücklicherweise atof.

        "Rhababerquark" hältst Du also für eine gültige Eingabe, wenn nach einer Gleitkommazahl gefragt ist?

        strtod gibt Dir hier eine klare Aussage, dass Müll eingegeben wurde, atof verschweigt Dir das und liefert stattdessen eine willkürlich ausgesuchte Zahl.

        Alexander

        --
        Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".
        1. Hallo,

          Trotzdem erstmal die Antwort: Die Zahl selbst. Das wird durch if (arrused != 0) return wert /= arrused; korrekt abgedeckt.

          Was ist der Durchschnitt einer leeren Liste, wenn arrused==0?

          Er ist nicht definiert, Du gibst aber 0.0 zurück.

          Ok, da hast du zwar recht. Das Programm war aber sowieso zum testen für mich selbst bestimmt, also habe ich auch gar nicht alle Sicherheitsvorkehrungen bezügl. der Eingabe berücksichtigt.

          Mit geht es aber eigentlich darum, warum das Einbinden der statischen Bibliothek zu falschen Ergebnissen führt?

          Markus.

          1. Moin Moin!

            Mit geht es aber eigentlich darum, warum das Einbinden der statischen Bibliothek zu falschen Ergebnissen führt?

            Kann ich nicht nachvollziehen:

            Makefile:

              
            CFLAGS += -std=c99  
              
            default: clean test  
              
            clean:  
                    rm -f *.o *.a allinone static  
              
            all: allinone static  
              
            allinone: allinone.o  
              
            allinone.o: allinone.c  
              
            allinone.c: durchschnitt.h durchschnitt.c Main.c  
                    cat $^ > $@  
              
            static: Main.c libdurchschnitt.a  
                    gcc -static durchschnitt.h Main.c -L. -ldurchschnitt -o static -std=c99  
              
            libdurchschnitt.a: durchschnitt.o  
                    ar rcs libdurchschnitt.a durchschnitt.o  
              
            durchschnitt.o: durchschnitt.c  
              
            test: allinone static  
                    for t in 3 5 "1 2 3" quark ""; do for f in $^ ; do echo "$$t | $$f" ; echo $$t | $$f ; done ; done  
            
            

            make:

              
            rm -f *.o *.a allinone static  
            cc -std=c99   -c -o allinone.o allinone.c  
            cc   allinone.o   -o allinone  
            cc -std=c99   -c -o durchschnitt.o durchschnitt.c  
            ar rcs libdurchschnitt.a durchschnitt.o  
            gcc -static durchschnitt.h Main.c -L. -ldurchschnitt -o static -std=c99  
            for t in 3 5 "1 2 3" quark ""; do for f in allinone static ; do echo "$t | $f" ; echo $t | $f ; done ; done  
            3 | allinone  
            Zahl 1: 3.000000  
            Durchschnitt: 3.000000  
            3 | static  
            Zahl 1: 3.000000  
            Durchschnitt: 3.000000  
            5 | allinone  
            Zahl 1: 5.000000  
            Durchschnitt: 5.000000  
            5 | static  
            Zahl 1: 5.000000  
            Durchschnitt: 5.000000  
            1 2 3 | allinone  
            Zahl 1: 1.000000  
            Zahl 2: 2.000000  
            Zahl 3: 3.000000  
            Durchschnitt: 2.000000  
            1 2 3 | static  
            Zahl 1: 1.000000  
            Zahl 2: 2.000000  
            Zahl 3: 3.000000  
            Durchschnitt: 2.000000  
            quark | allinone  
            Zahl 1: 0.000000  
            Zahl 2: 0.000000  
            Zahl 3: 0.000000  
            Zahl 4: 0.000000  
            Zahl 5: 0.000000  
            Zahl 6: 0.000000  
            Durchschnitt: 0.000000  
            quark | static  
            Zahl 1: 0.000000  
            Zahl 2: 0.000000  
            Zahl 3: 0.000000  
            Zahl 4: 0.000000  
            Zahl 5: 0.000000  
            Zahl 6: 0.000000  
            Durchschnitt: 0.000000  
             | allinone  
            Zahl 1: 0.000000  
            Durchschnitt: 0.000000  
             | static  
            Zahl 1: 0.000000  
            Durchschnitt: 0.000000  
            
            

            gcc --version:

              
            gcc (GCC) 4.2.3  
            Copyright (C) 2007 Free Software Foundation, Inc.  
            This is free software; see the source for copying conditions.  There is NO  
            warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
            
            

            Alexander

            --
            Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".
            1. Moin Moin!

              Kleiner Denkanstoß:

              echo 5 | static ; echo "5a" | static ; echo "5ab" | static ; echo "5 garbage in garbage out" | static
              Zahl 1: 5.000000
              Durchschnitt: 5.000000
              Zahl 1: 5.000000
              Zahl 2: 0.000000
              Durchschnitt: 2.500000
              Zahl 1: 5.000000
              Zahl 2: 0.000000
              Zahl 3: 0.000000
              Durchschnitt: 1.666667
              Zahl 1: 5.000000
              Zahl 2: 0.000000
              Zahl 3: 0.000000
              Zahl 4: 0.000000
              Zahl 5: 0.000000
              Zahl 6: 0.000000
              Zahl 7: 0.000000
              Zahl 8: 0.000000
              Zahl 9: 0.000000
              Zahl 10: 0.000000
              Zahl 11: 0.000000
              Zahl 12: 0.000000
              Zahl 13: 0.000000
              Zahl 14: 0.000000
              Zahl 15: 0.000000
              Zahl 16: 0.000000
              Zahl 17: 0.000000
              Zahl 18: 0.000000
              Zahl 19: 0.000000
              Zahl 20: 0.000000
              Zahl 21: 0.000000
              Zahl 22: 0.000000
              Zahl 23: 0.000000
              Zahl 24: 0.000000
              Durchschnitt: 0.208333

              Möchtest Du etwas über nicht abgefangene Fehler, insbesondere rund um atof, anmerken?

              Alexander

              --
              Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".
              1. Hallo,

                Möchtest Du etwas über nicht abgefangene Fehler, insbesondere rund um atof, anmerken?

                wie ich schon erwähnte, habe ich falsche Eingaben kaum berücksichtigt, da das Programm nur für mich selbst zum Testen vorgesehen ist. Und ich weiß ja schließlich, was ich eingaben muss. :)

                Markus

            2. Hallo,

              danke für die Hilfe. Ich werde das heute Abend, wenn ich zu Hause bin, gleich nochmals testen. Auf den ersten Blick kann ich aber gar keine Unterschiede beim Kompilieren erkennen...

              Markus

  3. Hallo,

    $ gcc -c durchschnitt.c -o durchschnitt.o -std=c99
    $ ar rcs libdurchschnitt.a durchschnitt.o
    $ gcc -static durchschnitt.h Main.c -L. -ldurchschnitt -o Main_statisch -std=c99

    ich habe das hier mit gcc Version 4.3.2 und ar aus
    den binutils 2.1.18.20080103 mal genauso probiert und kann
    kein auffälliges Verhalten finden, d. h. der Durchschnitt
    wird - auch mit Verwendung der Library - korrekt ausgegeben.

    Ein paar Anmerkungen seien mir noch erlaubt:

    • durchschnitt.h:
      Die Zeilen
      
    #define MAX 30  
    #include <stdio.h>  
    #include <stdlib.h>  
    
    

    werden allesamt nur in main.c benötigt. Aus Code-Design-Aspekten
    würde ich diese Zeilen daher auch in main.c schreiben, zumal es
    ja auch sein kann, dass durchschnitt.h von anderen Codes
    eingebunden sind, die evtl. <stdio.h> etc. schon anderweitig
    inkludieren oder die andere Vorstellungen von MAX haben.

    • durchschnitt.c:
      Einleuchtender ist es i.d.R., fehlerhafte Parameter gleich
      ganz am Anfang einer Funktion abzufangen. Ich hätte daher
      am Anfang der Funktion eine Zeile wie folgt erwartet:

    if (arrused<=0) return 0.0;

    Die Rückgabe return wert /= arrused;
    schreibt den Durschnittswert vor Rückgabe auch noch in die lokale
    Variable 'wert', was eigentlich überflüssig ist.

    • main.c:
      Die Routine zum Einlesen einer Zahl ist fehlerträchtig, da
      die Länge der Zeichenkette nicht überprüft wird und die
      Länge von hilfsarray mit 10 auch nicht gerade großzügig
      bemessen ist. Einfacher und sicherer geht es mit fgets.
      fgets sorgt dafür, dass nicht mehr Zeichen eingelesen werden
      können, als durch den 'size'-Parameter vorgegeben wird
      (genaugenommen wird ein Zeichen weniger eingelesen, da ja
      noch ein Byte für die abschließende '0' gebraucht wird),
      weiterhin gibt fgets NULL zurück, wenn ein EOF erreicht
      wird oder der Benutzer Ctrl+D eingibt.
      Die main-Routine würde damit so aussehen:
      
    #include "durchschnitt.h"  
      
    int main(void)   {  
            int arrayused=0;  
            double array[MAX];  
            char hilfsarray[20];  
            while((arrayused<MAX) && fgets(hilfsarray, 20, stdin))  
                array[arrayused++] = atof(hilfsarray);  
      
            for (int i = 0; i < arrayused; i++)  {  
                    printf("Zahl %d: %f\n", i+1, array[i]);  
            }  
            printf("Durchschnitt: %f\n", durchschnitt(array, arrayused));  
            return 0;  
    }  
      
    
    

    Viele Grüße

    Andreas

    1. Hallo,

      »» $ gcc -c durchschnitt.c -o durchschnitt.o -std=c99
      »» $ ar rcs libdurchschnitt.a durchschnitt.o
      »» $ gcc -static durchschnitt.h Main.c -L. -ldurchschnitt -o Main_statisch -std=c99

      ich habe das hier mit gcc Version 4.3.2 und ar aus
      den binutils 2.1.18.20080103 mal genauso probiert und kann
      kein auffälliges Verhalten finden, d. h. der Durchschnitt
      wird - auch mit Verwendung der Library - korrekt ausgegeben.

      Hmmm, ich werde abends mal nachsehen, welche gcc-Version ich daheim benutze. Oder eventuell sind irgendwo bestimmte Compilerflags gesetzt?

      • durchschnitt.h:
        Die Zeilen

      #define MAX 30
      #include <stdio.h>
      #include <stdlib.h>

      
      >   
      > werden allesamt nur in main.c benötigt. Aus Code-Design-Aspekten  
      > würde ich diese Zeilen daher auch in main.c schreiben, zumal es  
      > ja auch sein kann, dass durchschnitt.h von anderen Codes  
      > eingebunden sind, die evtl. <stdio.h> etc. schon anderweitig  
      > inkludieren oder die andere Vorstellungen von MAX haben [...]  
        
      Dazu hätte ich sowieso eine Frage. Wie sieht das "korrekte" Design für C-Programme eigentlich aus? Für sinnvoll halte ich natürlich einen objektorientierten Ansatz, aber da müsste ich mich wiederum fragen, warum ich nicht gleich C++ verwende, wenn ich schon mit C C++, oder Java "simulieren" will.  
        
      Markus  
      
      
  4. Hallo nochmals,

    es scheint aus irgend einem Grund nun doch geklappt. Ich weiß nicht, welchen Blödsinn ich da wohl gestern getrieben habe.

    Danke jedenfalls für eure Tipps.

    Markus