ansich folgenlose Befehle erzeugen veränderte Programmausgabe



  • Hi List,

    ich bin gerade dabei den evaluationscode vom fraunhofer-mp3-decoder näher zu erforschen, und bin dabei auf ein merkwürdiges Phänomen gestoßen, bei dem ich einfach nicht mehr weiter weiß:

    Beim Aufruf des decoders werden per Parameterübergabe oder per interaktion auf der Kommandozeile die notwendigen Parameter wie eingabedatei und Ausgabedatei übergeben. Dazu habe ich die Parameterverarbeitung in eine externe Funktion ausgelagert. Um sicherzugehen, daß ich nicht aus Versehen das Programmverhalten ändere, habe ich dabei immer wieder per diff die decoderausgabe zu einer zu Anfang erzeugten Referenz verglichen. Dabei stellte sich heraus, daß sich allein durch das Ersetzen der vorherigen dateinamenvariablen durch eine struct zur Parameterübergabe zwischen Parameterverarbeitungsfunktion und main-Funktion die Programmausgabe verändert habe. Ich konnte das soweit eingrenzen, daß die eingegebenen Dateinamen nachträglich in der main-Methode in die alten Variablen per strcpy übertragen werden mussten, damit die original-decoderausgabe erzeugt werden konnte. Das merkwürdige dabei ist jedoch, daß diese alten Variablen nirgendwo mehr benutzt werden - gelesen wird nur noch aus der o.g. struct. Teilcode ist hier:

    #define MAX_NAME_SIZE 81                // ist groß genug
    
    typedef struct {
                		char    encoded_file_name[MAX_NAME_SIZE]; 
                		char    decoded_file_name[MAX_NAME_SIZE];
                		int  topSb;
                  	 												} Argument_struct;
    
    int main(int argc, char **argv)
    {
    
    -snip-
    
        char              encoded_file_name[MAX_NAME_SIZE];
        char              decoded_file_name[MAX_NAME_SIZE];
        char              t[50];
        Argument_struct   arguments;
    
        computeCommandArgs(argc, argv, &arguments); -externe Parameterverarbeitung
    
        strcpy(decoded_file_name,arguments.decoded_file_name);
        strcpy(encoded_file_name,arguments.encoded_file_name);  
    
        /* report results of dialog/command line */
        printf("Input file = '%s'  output file = '%s'\n",
               arguments.encoded_file_name, arguments.decoded_file_name);
    
    -snip-
    

    wie gesagt, decoded_file_name und encoded_file_name werden danach nie wieder benutzt, genutzt wird nur noch arguments.blablabla
    Wenn ich nun die beiden strcpy-Befehle weglasse, erzeugt der decoder ein geringfügig anderes Ergebnis.

    Ist's ein Überlauf? Trau ich Fraunhofer eigentlich nicht zu - wie gesagt, bis auf meine Kommandozeilenauslagerung ists der code von denen. Wie lässt sich so etwas verifizieren? Ich arbeite unter Linux/Eclipse. Hat jemand einen Tip? Wer zum Testen den gesamten Code haben will: ich stelle ihn gerne bei Nachfrage zur Verfügung.

    Thänx in advance,

    homcy



  • homunculum schrieb:

    Beim Aufruf des decoders werden per Parameterübergabe oder per interaktion auf der Kommandozeile die notwendigen Parameter wie eingabedatei und Ausgabedatei übergeben. Dazu habe ich die Parameterverarbeitung in eine externe Funktion ausgelagert.

    Ohne von MP3 etwas zu verstehen...

    Ich frage mal ganz einfach, ob Du geprüft hast, ob das Nullbyte der beiden Arrays in Argument_struct auch innerhalb des jeweiligen Arrays liegt...?

    Mal aus persönlichem Interesse...

    Argument_struct   arguments;
        struct Argument   args;
    

    Welche Begründung hast Du, um einer Struktur einen Typen mitzugeben, der dann Argument_struct heißt?
    Wo liegt in Deinen Augen der Vorteil? Einer struct in C einen eigenen Typen mitzugeben, ist je nach Lektüre schon zweifelhaft, was manche ja tun, um sich das Schlüsselwort struct zu sparen... aber darum geht's hier wohl weniger?!



  • Im Code ist soweit kein Fehler zu erkennen, es sei denn ...
    ... es ist irgendwo anders ein Fehler.

    Wo sind encoded_file_name, decoded_file_name, arguments definiert? In main() oder etwa in einer anderen Funktion?

    Wird strcpy() ebenfalls in main() aufgerufen oder woanders?

    Was passiert mit t[50]? Wird das irgendwo übermangelt?

    Wie gesagt, der Fehler liegt m.E. nicht in dem vorliegenden Codestück.

    Zum Testen würde ich mal "arguments" als static deklarieren, dann wird diese Struktur nicht mehr im Stack angelegt, sondern global (und wird dann ggf. nicht übermangelt).



  • Danke für eure Antworten!

    Xin schrieb:

    Ich frage mal ganz einfach, ob Du geprüft hast, ob das Nullbyte der beiden Arrays in Argument_struct auch innerhalb des jeweiligen Arrays liegt...?

    natürlich. alles ok.

    Xin schrieb:

    Welche Begründung hast Du, um einer Struktur einen Typen mitzugeben, der dann Argument_struct heißt?

    ich brauche keine, ich bin - wie viele Programmierer ein bequemer Mensch, der auf selbstdefinierte Typenbezeichnungen steht. Ich nenn' es auch an guten Tagen 'Wunsch nach lesbarem code'. Wenn ich aber meinen Code aus dem Zusammenhang reiße um ihn in ein Forum zu pasten, dann scheint's mir sinnvoll solch eine kleine, das Verständnis und den Überblick fördernde Änderung anzubringen. Im Original heißt der Typ Arguments. Zufrieden?

    Xin schrieb:

    aber darum geht's hier wohl weniger?!

    Right guy, you name it. Danke für die Hilfe.

    jox schrieb:

    Wo sind encoded_file_name, decoded_file_name, arguments definiert? In main() oder etwa in einer anderen Funktion?

    Sie sind in main() definiert.

    jox schrieb:

    Wird strcpy() ebenfalls in main() aufgerufen oder woanders?

    ebenfalls in main(), der codeausschnitt ist der Reihenfolge nach so angeordnet, wie er in main() erscheint. Das erste -snip- schneidet andere Variablendeklarationen heraus, das zweite den Restcode von main().

    jox schrieb:

    Was passiert mit t[50]? Wird das irgendwo übermangelt?

    nope, ist nur noch ne Altlast, sie wurde ursprünglich als Buffer für Tastatureingaben im alten Parameterverarbeitungscode innerhalb main() (den, den ich ausgegliedert hatte) benutzt. Nun hat er ebenfalls keine Verwendung mehr. Für das Problem ist irrelevant, ob's t gibt, oder nicht.

    jox schrieb:

    Wie gesagt, der Fehler liegt m.E. nicht in dem vorliegenden Codestück.

    Ich bin mir ziemlich sicher, daß er auch nicht im Rest der main-Methode zu finden ist. Aber irren kann ich mich natürlich. Nur habe ich schon Tage damit zugebracht, mir darauf einen Reim zu machen. Dazu ist der Code von Fraunhofer eigentlich recht gut strukturiert, weshalb ich nicht von einem trivialen Fehler ausgehe.

    jox schrieb:

    Zum Testen würde ich mal "arguments" als static deklarieren, dann wird diese Struktur nicht mehr im Stack angelegt, sondern global (und wird dann ggf. nicht übermangelt).

    Ah, guter Tip! Wenn ich arguments statisch deklariere, wird die Ausgabe genauso verändert, wie bei fehlenden en/decoded_file_name Variablen / strcpy-Befehlen.
    Die struct wird aber in beiden Fällen nicht übermangelt. Die Benennung der ein- und ausgabedateien funktioniert einwandfrei.

    Das Problem scheint nicht die struct für sich zu sein, sondern die Speicheraufteilung des Stack. Es ist dabei sogar egal, was die beiden Variablen en/decoded_file_name zur Programmlaufzeit enthalten (habe testweise direkt nach den strcpy's beide komplett mit nullen gefüllt), wichtig ist nur, daß einmal das strcpy ausgeführt wird, und diese Variablen existieren.

    Ich fürchte fast, daß der Originalcode irgend einen ominösen indirekten Stackfehler aufweist, der durch meine Umbauten irgendwie kompensiert wird, ohne die tatsächliche Ursache zu bekämpfen... Das ist wahrlich keine exakte Wissenschaft mehr. Kennt jemand ein Tool/eine Technik, den Stack zur Laufzeit zu beobachten? Oder gibt's das in Eclipse bereits, und ich war nur blind?

    Dankbar für jede Hilfe,

    Homcy



  • Beim Debuggen in Eclipse kannste dir doch auch den Speicher anzeigen lassen, oder irre ich mich da?



  • Xin schrieb:

    Einer struct in C einen eigenen Typen mitzugeben, ist je nach Lektüre schon zweifelhaft...

    wtf? 😮



  • hehejo schrieb:

    Beim Debuggen in Eclipse kannste dir doch auch den Speicher anzeigen lassen, oder irre ich mich da?

    Jaaa, schon, aber leider nicht sehr komfortabel... Man kann zwar anhand von Syntax wie &Variable sich eine Speicherseite anzeigen lassen, in der diese Variable liegt, aber bei zu vielen Variablen ist's schier unmöglich fehlerhafte Zugriffe zu erkennen, weil die Speicherseite keinen Hinweis darauf gibt, wo welche Variable anfängt, resp. aufhört... Ich hatte gehofft, daß es ein spezielles Tool für Stackbeobachtung geben würde. Kennt jemand ne IDE, die sowas bietet?

    Nixdestotrotz, durch Beobachtung des Speichers, in dem der Stack zu liegen kommt, ist doch wieder etwas mehr Licht ins Dunkle gekommen.

    1. Ich Vollpfosten habe mit -o2 compiliert (also optimierungsstufe 2), was bedeutet, daß nicht benutzte Variablen erst gar nicht angelegt werden. Deshalb war das strcpy notwendig, und deshalb war's auch irrelevant, ob t[50] deklariert wurde, oder nicht - es wurde erst gar nicht im Stack abgelegt.

    2. en/decoded_file_name werden definitiv nicht innerhalb des weiteren Programmablaufs überschrieben. Hinzu kommt, daß wohl auch nicht fälschlicher- bzw. relevanterweise aus ihnen gelesen wird. Dazu habe ich einmal beide Variablen zu Programmbeginn mit 0x00 gefüllt, und einmal mit 0xff. Beide Abläufe brachten die selbe Decoderausgabe. Es scheint so zu sein, daß die beiden Variablen nur indirekt mit dem Problem zu tun haben, was die Sache natürlich nicht überschaubarer macht. *haarerauf* 😕

    ⚠ Also, please help: wer kennt ne C-IDE, die bessere Kontrollmöglichkeiten über Stackzugriffe hat, als die Eclipse-CDT?



  • Hi List nochmal,

    boah, das war 'ne harte Nuss! Habe nach weiterem Suchen im Netz ein großartiges Open-Source-Tool zur Speicherüberprüfung von C/C++ Programmen auf Linux/Intelx86/AMD/PPC-Basis gefunden: Valgrind

    www.valgrind.org

    Großartiges Teil, damit war's ein Klacks das Problem zu finden.

    Es war tatsächlich ein Fehler im Fraunhofer-Code, der verursachte, daß ein Element in einem Array nie initialisiert wurde. Von Hand hätt' ich das nie gefunden!
    Jetzt, nachdem die Initialisierung repariert ist, hat sich auch der o.G. Effekt erledigt.

    Danke noch einmal, speziell an jox, für das Brainstorming mit Meinungsbildung!

    Frohes Fest noch,

    Homcy



  • jox schrieb:

    Im Code ist soweit kein Fehler zu erkennen, es sei denn ...
    ... es ist irgendwo anders ein Fehler.

    😮

    Du sagst also, wenn irgendwo anders ein Fehler ist, erst dann
    ist im Code ein Fehler zu erkennen? 😕

    Die Qualität der Posts läßt nach; ich mach dann doch lieber mal Feierabend... 😃



  • TactX schrieb:

    Xin schrieb:

    Einer struct in C einen eigenen Typen mitzugeben, ist je nach Lektüre schon zweifelhaft...

    wtf? 😮

    .h
    struct Foo { int Bar; };
    typedef struct Foo Foo;
    
    .c
    int main (void)
    {
      Foo bar;
    
      return 0;
    }
    

    Die Information, dass Foo ein struct ist geht hiermit verloren. Es könnte auch z.B. ein enum sein.

    struct Foo bar;
    

    läßt derartige Fragen nicht aufkommen. Diese Zusatzinformationen hilfen dem Leser des Codes den Inhalt schneller und einfacher zu verstehen.
    So "Fehlerfrei programmieren in C und C++", dpunkt von Oliver Böhm, wenn ich den Namen richtig im Kopf habe.
    Ich selbst war nie sooo schreibfaul, dass ich struct nicht auch noch hinbekommen hätte, ich war zu faul, um das typedef zu machen :->

    Ich habe einen besseren Grund, der sich auch bei Scott Meyers in - ich glaube - "Effektiv C++ programmieren" wiederfindet:

    In C++ schreibe ich in Headerdateien häufig 'class Foo' statt 'Foo', damit ich mir #include "foo.h" sparen kann. Das verringert die Übersetzungszeiten und die Abhängigkeiten der Include-Dateien. Ähnliches gilt auch für struct, denn den Typ Foo gibt's erst, wenn man die entsprechende Datei per "#include" mit den zuvor genannten Nachteilen auftaucht.



  • Xin schrieb:

    TactX schrieb:

    Xin schrieb:

    Einer struct in C einen eigenen Typen mitzugeben, ist je nach Lektüre schon zweifelhaft...

    wtf? 😮

    .h
    struct Foo { int Bar; };
    typedef struct Foo Foo;
    
    .c
    int main (void)
    {
      Foo bar;
    
      return 0;
    }
    

    Die Information, dass Foo ein struct ist geht hiermit verloren. Es könnte auch z.B. ein enum sein.

    Deswegen sollte man auch sinnvolle Variablennamen verwenden. Und aus dem Kontext wird schnell wieder sichtbar ob es ein struct ist, oder nicht (O.K. es könnte auch eine union sein 🙄 ). Und spätestens dann hat man eh schon die Definition bzw. die Dokumentation gesucht.

    Das sind aber Geschmacksfragen und solche zu diskutieren ist albern.


Log in to reply