Bestimmten Block aus inter Hex (Binär) File lesen



  • Hallo
    Ichgendie klappt es nicht.Hatte diesen Ansatz hier:

    int main( int argc, char* argv[] )
    {
    
        FILE *file1;
         char str[16];
    //    file1=fopen("1.bin","rb"); //eine bin Datei zu verarbeiten soll das Ziel sein
       file1=fopen("1.txt","r");  // TXT Datei zum üben
    
    fseek(file1, 1, SEEK_SET);
    
         fgets(str, 4, file1);
    
    printf( "Eingabe: %x - %.16s\n ",str, str );
    }
    

    Als Ausgabe kommt:
    Eingabe. 28fefb - 236

    Die Textdatei beinhaltet:
    123654789
    Was ist hier die 28fefb
    ----------------------------------

    Warum bekomme ich hier nicht die Hexwerte ausgegeben, wenn ich das so mache

    int main( int argc, char* argv[] )
    {
    
        FILE *file1;
         char str[16];
       file1=fopen("1.bin","rb");
    
    fseek(file1, 1, SEEK_SET);
    
         fgets(str, 4, file1);
    
    printf( "Eingabe: %h \n ",str );
    
    }
    


  • Nein, Wutz. Wenn wir das so machen, gibt es einen Segfault. Zumindest bei mir. Und das liegt daran, dass ich den Wert, in den ich den Wert speichere, als erstes auf den Stack lege, und mir fscanf dann die restlichen 3 Bytes nullt beim Speichern des Wertes. Ich werde nicht entscheiden, ob mir das jetzt mein Handle vernichtet (was ich vermute, weil fclose kaputtgeht) oder den gegenwärtigen Stackframe, aber es macht das Programm kaputt.

    Wutz schrieb:

    Das ist unerheblich. char ist kompatibel zu allen int-Typen, demzufolge ist es auch ein Zeiger darauf. char* hat immer das beste Alignment, auch da kann nie was schief gehen.

    m(

    Es MACHT einen Unterschied, ob die Funktion nur einen Pointer auf uint8_t oder auf unsigned int erwartet. Keine Ahnung, wo du das gelernt hast, aber das ist falsch. Siehe unten.

    @Mr.Ostlich: Und dein Ansatz ist falsch. Hatte Wutz doch schon erklärt. Zumindest im Ansatz, auch wenn dieser fehlerhaft ist.

    Also, in deiner Datei hast du zunächst mal Bytes. Erst einmal nur 8-Bit-Werte. Die kannst du als Zeichen ansehen. Werden sie manchmal auch. fgets macht genau das, es holt sich die Daten von deiner Datei und speichert sie bei dir in str . Das willst du aber nicht (oder?), sondern du willst einen Wert, der in einem hexadezimalen String in der Datei (im Little-Endien-Format/Intel-Alignment/Reihenfolge) vorliegt.

    Aaaaaaaber dieser String wird ja wieder durch ganz eigene Bytes repräsentiert. Das bedeutet, dass du den String erst dekodieren musst, bevor du die "richtigen" Werte raushast. Aber dazu hast du ja (siehe Wutz's Post) fscanf . Die Funktion kümmert sich bereits um das Dekodieren, wenn du ihr den richtigen Formatstring gibst.

    Also, zu deinem ersten Programm:

    1. Du öffnest die Datei (und machst keine Prüfung, ob fopen erfolgreich war - das führt dann bei mir erstmal zu einem Segfault. ;)).
    2. Gehst an die Position des zweiten Bytes - in C wird mit dem Zählen und Indexieren bei 0 angefangen, das weißt du? Das fseek kannst du dir sparen, wenn der Wert direkt am Anfang der Datei liegt.
    3. Holst dir dann nur drei Bytes - siehe Dokumentation zu fgets() : liest höchstens size minus ein Zeichen, also bei dir 4-1 == 3 Zeichen ein, und setzt das letzte Zeichen 0 (Stringende in C).
    4. Und gibst das jetzt printf zum Ausgeben. Gibt es auch aus, aber du sagst der Funktion, dass sie den Parameter (die Adresse des Arrays) als hexadezimale Zahl ausgeben soll. Abgesehen davon, dass der korrekte Formatspezifizierer in einem solchen Fall %p wäre, hast du ja nichts gewonnen, wenn die Funktion das Ergebnis korrekt ausgeben würde (was sie nicht tut im Übrigen), weil du dann immer noch innerhalb des Programms mit kaputten Werten arbeitest.

    5. Das %.16s ist halb korrekt - du sagst der Funktion, dass sie maximal 16 Zeichen des Strings str ausgeben soll. Zumindest sagst du der Funktion, dass es sich um einen String handelt - deswegen kommt auch das "- 236" in deiner Ausgabe vor. Aber auch so hast du noch nichts aus den Daten intepretiert.

    Was du machen kannst: du weißt ja schon, dass ein 8-Byte-Wert 16 Nibbles hat, und je ein Buchstabe ein Nibble repräsentiert. Du holst dir also den kompletten Wert:

    #define MY_LENGTH 17
    /*16 Nibbles plus 1 für den Nullterminator*/
    char str[MY_LENGTH];
    fgets(str,MY_LENGTH,file1);
    

    Und konvertierst das dann um. Aber das ist blöd und unnötig kompliziert, du müsstest auf Upper- und Lowercase prüfen. Und das ist scheiße, wenn du nicht grade der Jäger der verlorenen Cycles bist.

    while(1)
    {
            temp=pstring[pindex++];
    
            /*Convert lower case.*/
            if(my_is_lower_hexchar(temp))
                    temp-=39;
    
            /*Convert upper case.*/
            else if(my_is_upper_hexchar(temp))
                    temp-=7;
    
            /*Any other character terminates the execution.*/
            else if(!my_is_digit(temp))
                    break;
    
            pvalue=(pvalue*16)+temp-'0';
    }
    

    Zusätzlich liest der Algorithmus nur in Big-Endian-Reihenfolge (Motorola-Reihenfolge) ein, und du willst ja Little-Endian (Intel).

    So ist das sauberer und gleichzeitig LE:

    #include <stdio.h>
    #include <stdint.h>
    
    int main(void)
    {
            uint64_t my_value;
            uint8_t*c=(uint8_t*)&my_value;
            FILE*my_file;
            if(!(my_file=fopen("1.txt","rb")))
            {
                    fprintf(stderr,"Sorry, konnte Datei nicht lesen!\n");
                    return -1;
            }
    
            /*2hhx sagt: ich erwarte zwei hexadezimale Zahlen, aber die speicherst
            **du mir bitte in jeweils einem Byte ab.*/
            if(fscanf(my_file,"%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx",c,c+1,c+2,c+3,c+4,c+5,c+6,c+7)!=8)
            {
                    printf("Konnte nicht komplett 8 Bytes lesen!\n");
                    return -2;
            }
            fclose(my_file);
    
            printf("%lx\n",my_value);
            return 0;
    }
    

    Und als Ausgabe:

    1312119078563412

    Der Inhalt der Datei ist übrigens "1234567890111213".


  • Mod

    Mr.Ostich schrieb:

    Hallo
    Ichgendie klappt es nicht.Hatte diesen Ansatz hier:

    int main( int argc, char* argv[] )
    {
    
        FILE *file1;
         char str[16];
    //    file1=fopen("1.bin","rb"); //eine bin Datei zu verarbeiten soll das Ziel sein
       file1=fopen("1.txt","r");  // TXT Datei zum üben
    
    fseek(file1, 1, SEEK_SET);
    
         fgets(str, 4, file1);
    
    printf( "Eingabe: %x - %.16s\n ",str, str );
    }
    

    Als Ausgabe kommt:
    Eingabe. 28fefb - 236

    Compilerwarnungen anschalten und Warnungen wie Fehler behandeln! %x erwartet einen unsigned int, du übergibst einen char*! Im besten Fall wird dir das den Zahlenwert deines Zeigers ergeben, im Normalfall ist das Verhalten aber einfach nur Müll.

    Die Textdatei beinhaltet:
    123654789
    Was ist hier die 28fefb
    ----------------------------------

    Warum bekomme ich hier nicht die Hexwerte ausgegeben, wenn ich das so mache

    int main( int argc, char* argv[] )
    {
    
        FILE *file1;
         char str[16];
       file1=fopen("1.bin","rb");
    
    fseek(file1, 1, SEEK_SET);
    
         fgets(str, 4, file1);
    
    printf( "Eingabe: %h \n ",str );
    
    }
    

    %h ? h ist bei printf ein Längenmodifikator, der muss sich auf irgendwas beziehen. Zum Beispiel %hi , wenn man einen short int ausgeben möchte. Hier ist dein Formatspezifizierer aber nichts. Beziehungsweise das Leerzeichen. Wieder hätte dich dein Compiler warnen können.

    Ich weiß auch gar nicht, wo du auf einmal diese Ansätze her hast. Die passen weder zu deiner ursprünglichen Problembeschreibung noch erkenne ich darin irgendeine der gegebenen Antworten wieder.



  • @dachschaden :

    Danke dir dafür

    #include <stdio.h>
    #include <stdint.h>
    
    int main(void)
    {
            uint64_t my_value;
            uint8_t*c=(uint8_t*)&my_value;
            FILE*my_file;
            if(!(my_file=fopen("1.txt","rb")))
            {
                    fprintf(stderr,"Sorry, konnte Datei nicht lesen!\n");
                    return -1;
            }
    
            /*2hhx sagt: ich erwarte zwei hexadezimale Zahlen, aber die speicherst
            **du mir bitte in jeweils einem Byte ab.*/
            if(fscanf(my_file,"%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx",c,c+1,c+2,c+3,c+4,c+5,c+6,c+7)!=8)
            {
                    printf("Konnte nicht komplett 8 Bytes lesen!\n");
                    return -2;
            }
            fclose(my_file);
    
            printf("%lx\n",my_value);
            return 0;
    }
    

    Das Klingt logisch für mich, wenn ich es lese.

    Aber hab doch noch fragen:

    Warum die variablen so definiert werden versteh ich ncht ganz

    uint64_t my_value;
            uint8_t*c=(uint8_t*)&my_value;
    

    und ich bekomme bei einer textdatei mit Inhalt "1234567890111213" immer einen Programm absturz.

    Und wenn ich eine Binärdatei anstatt der Textdatei lese bekomme ich -2 als Rückmeldung

    @SeppJ:

    Ich habe das auf dem weg versucht, weil ich nach ein wenig lesen an anderen Stelen es für einfacher hielt.

    Aber dnake euch allen nichmal


  • Mod

    neop13 schrieb:

    und ich bekomme bei einer textdatei mit Inhalt "1234567890111213" immer einen Programm absturz.

    Ist das wirklich das gezeigte Programm, ohne jede Änderung? Denn das sollte funktionieren und tut es bei mir auch.

    Und wenn ich eine Binärdatei anstatt der Textdatei lese bekomme ich -2 als Rückmeldung

    Was verstehst du unter "Binärdatei"?

    Warum die variablen so definiert werden versteh ich ncht ganz

    uint64_t my_value;
            uint8_t*c=(uint8_t*)&my_value;
    

    Wir nehmen einen 64 Bit Integer, my_value . Diesen können wir auch als ein Feld von 8 Integers mit 8 Bit Länge auffassen. Auf dieses Feld können wir zeigen, diesen Zeiger nennen wir c. Dann ist c[0] der erste 8-Bit Integer, der my_value ausmacht, c[1] der zweite, und so weiter.

    Also zwei alternative Interpretationen der gleichen Bereiche im Speicher:

    --------------------------------------------------------------------------------
    ...      |                           my_value                           |    ...
    --------------------------------------------------------------------------------
    oder anders interpetiert:
    --------------------------------------------------------------------------------
    ...      | c[0] | c[1] | c[2] | c[3] | c[4] | c[5] | c[6] | c[7] | c[8] |    ...
    --------------------------------------------------------------------------------
             ^
             |  zeigt auf
             ---------------
                           |
    Ganz woanders:         |
           ----------------------------------
           ...   |        c            |  ...
           ----------------------------------
    

    Das heißt, wenn wir etwas in die c[i] schreiben, dann ändern wir my_value!

    (Kleiner Einschub: Pointerarithmetik. Wenn man einen Zeiger hat und dazu etwas addiert, dann bekommt man als Ergebnis einen Zeiger, der um genau so viele Elemente des Typs auf den der Zeiger zeigt verschoben ist, wie man dazu addiert hat.

    int foo[4];
    int *bar1 = &foo[0];  // Zeigt auf das erste Element des Feldes
    int *bar2 = bar + 2;  // Zeigt auf das dritte Element des Feldes. Der Compiler weiß, wie groß ein int ist und erzeugt Code für die passende Rechnung.
    

    Entsprechend ist hier c + 1 ein Zeiger auf den zweiten 8 Bit Integer in my_value, und so weiter.

    Ende Einschub)

    So setzt das fscanf dann alle 8-Bit Integer im großen Integer my_value und somit auch dessen kompletten Wert, der dann später ausgegeben wird. Die 8-Bit Integer sind dann sozusagen die 8 Ziffern, die den großen 64-Bit Integer ausmachen, bloß können diese Ziffern eben nicht nur Werte 0-9 haben, sondern 0-255 (da man mit 8 Bit 256 Werte darstellen kann).

    Wichtiger Fallstrick:
    Es ist nicht festgelegt, wie ein 64-Bit Integer intern arbeitet! Konkret: Wie die "Ziffern" der Zahl angeordnet sind. Wir Menschen schreiben eine Zahl zum Beispiel so "123" und haben festgelegt, dass ganz links die am meisten zählende Ziffer steht und ganz rechts dir am wenigsten zählende Ziffer und dazwischen die Abstufungen. Aber das ist nur Konvention. Man hätte es genau so gut andersrum machen können und was davon besser oder schlechter ist, das gleicht der Diskussion der Liliputaner, auf welcher Seite das Ei aufzuschlagen ist. Keine Seite hat wirklich Recht, aber jeder hat so starke Gefühle für "seine" Methode, dass man darum Kriege führen könnte. Ähnliche Diskussionen gab es auch bezüglich des Zahlenformates im Computer und darum hat man diese Glaubensfrage "Endianness" (Englisch für "Endigkeit", nach dem Ende der Eier) genannt. Es gibt auch keine Methode, die sich zweifelsfrei durchgesetzt hat. Wenn du einen x86_64-Prozessor besitzt, dann gibt dein Programm zu deiner Textdatei beispielsweise Folgendes aus:
    "1312119078563412"
    Hoppla, wo ist denn da der Zusammenhang mit "1234567890111213"? Wenn wir die Eingabe etwas anders gestalten, wird es deutlich. Schreiben wir in die Textdatei "1122334455667788" so bekommen wir:
    "8877665544332211"
    Aha, alles umgedreht. Das liegt da dran, dass ein x86_64-Prozessor ein little-Endian Format benutzt. Das heißt, die Ziffer, die wir ganz nach vorne geschrieben haben ("11") zählt am wenigsten und wird daher in der Ausgabe als menschenlesbare Zahl ganz hinten stehen (das unter Menschen übliche System ist big-endian). Ein anderer Prozessortyp (z.B. ein IBM-Prozessor, statt einem Intelprozessor) hätte vielleicht etwas anderes ergeben. Jetzt erkennen wir auch, wie die "1312119078563412" zustande kam: Eine "Ziffer" sind hier ja immer 8 Bit, das ist auch die kleinste Einheit auf der ein typischer Prozessor (z.B. der x86_64) arbeitet. Aber 8 Bit sind 2 hexadezimale Ziffern! Bei einer Eingabe "1234567890111213" haben wir also eine Ziffer "12", eine Ziffer "34", eine Ziffer "56", und so weiter. Und diese Ziffern als ganzes enden dann in umgekehrter Reihenfolge in der Ausgabe. Daher steht ganz am Ende "12", davor "34", davor "56" und so weiter.



  • uint8_t *c = (uint8_t*)&my_value;
    

    Du definierst einen Zeiger c auf uint8_t . (Das uint8_t *c)
    Diesem weist du die Adresse von my_value zu. (Das &my_value)
    Da my_value aber ein uint64_t ist, meckert der Compiler. Darum sagst du ihm mit dem Cast (uint8_t*) "Alles in Ordnung, ich weiß was ich tue"

    neop13 schrieb:

    Und wenn ich eine Binärdatei anstatt der Textdatei lese bekomme ich -2 als Rückmeldung

    Das fscanf soll Hexziffern lesen. Also nur die Zeichen 0 1 2 3 4 5 6 7 8 9 A B C D E F a b c d e f

    Wenn es die nicht vorfindet, wird das Einlesen abgebrochen.
    fscanf gibt die Anzahl erfolgreich gelesener Elemente zurück. Die ist dann nicht 8.



  • Danke DirkB. Jetzt ist mir das auch klarer

    grundsätzlich stellt sich mir die Frage, warum ich hier mit Zeigern arbeiten muss.

    @SeppJ: naja ein Bin File.
    siehe z.B.: http://openbook.galileocomputing.de/kit/bilder/11_2.gif

    Wobei das ja grundsätlich egal ist. Einziges Poblem, das kleine Hexwerte nicht Ascii interpretiert werden können.
    Aus diesem Grund stürtzt es wahrscheinlich auch ab.
    Wo bei wir ja beim einlesen"rb" sagen

    Ansonsten haben ichgenau deinen code genommen

    verwende Code::Blocks



  • Die Darstellung auf dem Bild ist schon mit einem HexViewer gemacht.
    Dieser Viewer hat aus den Binärwerten die Hexanzeige gemacht. Die steht so nicht in der Datei.

    scanf dient zum einlesen von formatierten Text/Zahlen. Diese Zahlen bestehen aus einzelnen Ziffern. Diese Ziffern sind aber auch nur ein Binärcode.
    Z.B nach ASCII.
    Daher kommt es (je nach Formatspecifier) nicht mit bestimmten Zeichen klar und meldet einen Fehler.

    Das "rb" bei fopen dient dazu, dass mögliche Ersetzungen beim Zeilenende ('\n') nicht gemacht werden. Da haben die verschiedenen Betriebssystemfamilien unterschiedliche Varianten. Nur '\n' oder nur '\r' oder beides zusammen.

    Zum massenhaften einlesen von Binärwerten nimmt man fread : http://www.cplusplus.com/reference/cstdio/fread/
    für einzelne Zeichen eher fgetc : http://www.cplusplus.com/reference/cstdio/fgetc/



  • Du willst einen TIFF-Header-Parser schreiben? Dann arbeitest du nicht mit dekodierten Hexstrings, sondern wieder wirklich mit den Rohwerten.

    Dazu habe ich mal vor ewigen Zeiten einen Parser geschrieben:

    #include <stdint.h>
    #include <stdio.h>
    
    uint64_t read_bytes
    (
            const char*content,
            const uint8_t number_of_bytes,
            const uint8_t is_little_endian
    )
    {
            register uint8_t current_pos=number_of_bytes;
            register uint64_t return_value=0;
    
            /*Kaputter Zeiger oder kaputte Anzahl an Bytes (0 ist immer 0, und
            **mehr als 8 Bytes kriegen wir mit x64 nicht eingeladen).*/
            if(!content
            || !number_of_bytes
            || number_of_bytes>8)
            {
                    return 0;
            }
    
            if(is_little_endian)
            {
                    while(current_pos)
                            return_value|=(uint64_t)content[--current_pos]<<current_pos*8;
            }
            else
            {
                    while(current_pos)
                            return_value|=(uint64_t)content[--current_pos]<<sizeof(uint64_t)*8-(current_pos+1)*8;
            }
            return return_value;
    }
    
    int main(void)
    {
            /*Hexwerte am Stueck.*/
            const char*my_value="\x01" "\x02" "\x03" "\x04" "\x05" "\x06" "\x07" "\x08";
    
            /*Einmal mit Intel-Reihenfolge ...*/
            printf("%lx\n",read_bytes(my_value,8,1));
    
            /*... und einmal mit Motorola-Reihenfolge einladen.*/
            printf("%lx\n",read_bytes(my_value,8,0));
    
            /*Ferddisch.*/
            return 0;
    }
    

    Ausgabe:

    807060504030201
    102030405060708

    Dazu musst du noch, wie DirkB gesagt hat, die Daten vorher in den RAM einladen. Anders geht das mit der Funktion nicht, die erwartet die Adresse, von der aus sie die Werte einlesen soll. Dazu gibt es fread - mach vorher _stat (Windows) bzw. stat (POSIX, Linux und BSD) auf das Bild, reserviere Speicher in Höhe der Dateigröße, und lade dann die Daten rein.



  • Kleiner Nachtrag:

    ich habe vergessen, dass es sich bei content um einen Zeiger auf vorzeichenbehaftetes char handelt. Der Code müsste eigentlich so aussehen:

    return_value|=(uint64_t)((uint8_t)content[--current_pos])<<current_pos*8;
    return_value|=(uint64_t)((uint8_t)content[--current_pos])<<sizeof(uint64_t)*8-(current_pos+1)*8;
    

Anmelden zum Antworten