Code Review für ein Hexdump-Programm



  • Ok, danke so werd ichs mit den Parametern machen, sieht gut aus.



  • Und noch ne Frage:
    Hat eine Textdatei in Linux keine Metainformationen? Denn ich hab gelesen das bei fopen() unter Linux das b in "rb" ignoriert wird. Das heißt bei der Textdatei werden direkt 1Byte-Werte gespeichert welche als ASCII interpretiert werden?



  • Ist unter Windows nicht anders. Textdateien haben nie Metainformationen. Notepad erkennt das Format auch nur durch statistische Analysen.

    Aber das kann dir für ein Hexdump doch auch egal sein 😕



  • Naja, wenn ich die Hexdatei speichere ist sie ja eine Textdatei. Und die will ich wieder in die Binäre Form bringen. (Als Übung eigentlich gedacht)



  • Nirvana134 schrieb:

    Naja, wenn ich die Hexdatei speichere ist sie ja eine Textdatei. Und die will ich wieder in die Binäre Form bringen. (Als Übung eigentlich gedacht)

    Du liest aber weiterhin Byte für Byte, da kann dir die Ordnung egal sein.



  • Das binäre Format bezieht sich auf die Zeilenendezeichen '\r' und/oder '\n'.

    Die werden bei den verschiedenen OS unterschiedlich benutzt.
    Bei Linux ist das '\n'
    bei MacOS ist das '\r', auch bei OS/9
    bei Windows sind das '\r\n' (beide).

    Beim lesen mit "r" wird dann das/die Zeilenendezeichen in '\n' umgewandelt bzw.
    beim schreiben mit "w" wird das '\n' in das/die passende Zeilenendezeichen umgewandelt.

    Im binären Modus passiert das nicht. Du bekommst die Zeichen so wie sie in der Datei stehen.



  • Ich hab hier die nächste Version, die Parameterabfrage mit for-Schleife hat mich fürs erste überfordert habs verworfen. Es schlanker zu machen lohnt sich eh nicht mehr von daher, war glaub ich ne gute Übung. Wenn mich jemand noch auf ein paar Fehler aufmerksam macht könnt ich immerhin noch was lernen. Hier der Code:

    /*************************************
    This program shows the hexcode
    of any given program or file.
    May be called a hexadecimaldump or hexdump.
    
    *************************************/
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    /*More readable with TRUE and FALSE*/
    #define TRUE 1
    #define FALSE 0
    
    /*This is the actual converting to hex and writing it to a file or stdout*/
    void writehextostream(FILE *stream, FILE *hexstream, int counter,int saveflag) {
        unsigned int hex;
        while((hex=fgetc(stream))!=EOF) {
            fprintf(hexstream,"%02x ",hex);
            counter++;
            if(!(counter%5)) {
                fprintf(hexstream,"\n");
            }
        }
        fprintf(hexstream,"\n");
        fclose(stream);
    
        if(saveflag==TRUE)
            fclose(hexstream);
    }
    
    /*Selfexplaining function which helps on how to use hexdump*/
    void helptext(char *programname) {
        printf("Usage: %s [OPTION] [FILE]\n", programname);
        printf("\t%s -s [NEWFILE]\tSaves the hexdump into [NEWFILE]\n",programname);
        printf("\t%s -h print this help message\n",programname);
    }
    
    int main(int argc, char **argv) {
        /*Hex gets printed to stdout and and line counts the lines*/
        unsigned int hex, line=1;
        /*counter counts when a
        a new line should appear*/
        int saveflag, counter=0;
    
        /*Test if enough arguments are given, otherwise terminate the program*/
        if(argc < 2) {
            printf("Arguments are missing\n");
            helptext(argv[0]);
            exit(1);
        }
    
        else if(argc>4) {
            printf("Too many arguments\n");
            helptext(argv[0]);
            exit(1);
        }
    
        FILE *stream, *hexstream;
        /*Parameters for hexdump*/
        if(!strcmp(argv[1], "-s")) {
            if((hexstream=fopen(argv[2],"r"))!=NULL) {
                printf("File %s does already exist\n", argv[2]);
               fclose(hexstream);
                exit(3);
            }
            /*File gets created if it does not already exist*/
            else {
                hexstream=fopen(argv[2],"w");
                stream=fopen(argv[3],"r");
                writehextostream(stream,hexstream,counter,saveflag=TRUE);
            }
        }
        /*Print help message on how to use hexdump*/
        else if(!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
            helptext(argv[0]);
        else if(argc==2) {
            /*File which gets read*/
            stream = fopen(argv[1],"r");
            /*Test if the file exists*/
            if(stream==NULL) {
                perror(argv[1]);
                exit(2);
            }
            /*Without parameters (argc==2), hex gets written to stdout and lines won't be printed*/
            else {
                writehextostream(stream,stdout,counter,saveflag=FALSE);
            }
        }
        return 0;
    }
    


  • Für die Parameter würde ich auf eine entsprechende Bibliothek zurückgreifen - es macht sich da bezahlt, standardisiertes Verhalten zu benutzen, um den Benutzer nicht unangenehm zu überraschen. POSIX definiert getopt, das ist ein guter Ansatzpunkt. Im WinAPI ist es leider nicht enthalten, aber es gibt frei verfügbare drop-in replacements beispielsweise hier und hier. Dokumentation hier.

    Damit kann man die ganze Sache dann etwa so aufziehen:

    #include <ctype.h>
    #include <stddef.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <getopt.h>
    
    #define ERROR_OPTION_GENERIC     (1)
    #define ERROR_INVALID_LINE_WIDTH (2)
    #define ERROR_CANT_OPEN_INFILE   (3)
    #define ERROR_CANT_OPEN_OUTFILE  (4)
    #define ERROR_OUT_OF_MEMORY      (5)
    
    static char const *const error_messages[] = {
      "",
      "",
      "Ungültige Zeilenbreite\n",
      "Fehler beim Öffnen der Eingabedatei\n",
      "Fehler beim Öffnen der Ausgabedatei\n",
      "Speicherfehler\n"
    };
    
    void usage(char const *progname) {
      printf("Verwendung: %s [ -h ] [ -i DATEI ] [ -o DATEI ] [ -l ZAHL ]\n"
             "\n"
             " -h       : Diese Meldung ausgeben\n"
             " -i DATEI : Statt von stdin aus DATEI lesen\n"
             " -o DATEI : Statt nach stdout in DATEI schreiben\n"
             " -l ZAHL  : ZAHL Byte in einer Zeile darstellen (Default: 16)\n", progname);
    }
    
    int write_hexdump(FILE *in, FILE *out, int line_width) {
      unsigned char *line_buffer = malloc(line_width + 1);
      size_t i, j, r;
    
      if(!line_buffer) { 
        return ERROR_OUT_OF_MEMORY;
      }
    
      for(i = 0; (r = fread(line_buffer, 1, line_width, in)) > 0; i += line_width) {
        fprintf(out, "%08lx", (unsigned long) i);
    
        for(j = 0; j < r; ++j) {
          fprintf(out, " %02x", line_buffer[j]);
          line_buffer[j] = isprint(line_buffer[j]) ? line_buffer[j] : '.';
        }
        line_buffer[j] = 0;
        fprintf(out, "%*c%s\n", (line_width - (int) r) * 3 + 1, ' ', line_buffer);
      }
    
      free(line_buffer);
      return 0;
    }
    
    int main(int argc, char *argv[]) {
      int c;
      int status = 0;
      int line_width = 16;
    
      /* Wenn keine Dateioption angegeben wird, Standardein- bzw. -ausgabe benutzen */
      FILE *in  = stdin;
      FILE *out = stdout;
      char * in_name = NULL;
      char *out_name = NULL;
    
      while(status == 0 && (c = getopt(argc, argv, "hi:o:l:")) != -1) {
        switch(c) {
        case 'h':
          usage(argv[0]);
          return 0;
        case 'i':
          in_name = optarg;
          break;
        case 'o':
          out_name = optarg;
          break;
        case 'l':
          line_width = atoi(optarg);
          if(line_width < 1) {
            status = ERROR_INVALID_LINE_WIDTH;
          }
          break;
        case '?':
          return ERROR_OPTION_GENERIC;
        }
      }
    
      if(status == 0 && in_name) {
        in  = fopen(in_name , "rb");
        if(!in ) { status = ERROR_CANT_OPEN_INFILE ; }
      }
    
      /* Ausgabedatei zuletzt öffnen, damit im Fehlerfall nicht unnötigerweise
       * Dateien erstellt werden. */
      if(status == 0 && out_name) {
        out = fopen(out_name, "w");
        if(!out) { status = ERROR_CANT_OPEN_OUTFILE; }
      }
    
      if(status == 0)  {
        status = write_hexdump(in, out, line_width);
      }
    
      if( in && in  != stdin ) fclose(in );
      if(out && out != stdout) fclose(out);
    
      fputs(error_messages[status], stderr);
      return status;
    }
    


  • Danke, getopt.h ist genau das was ich brauche. Hab mich immer gefragt wie man das mit den Parametern am besten regelt. Gibt es einen Unterschied zwischen getopt()
    in getopt.h und unistd.h, ich weiß nur das bei getopt.h (is von GNU richtig?) es noch die die funktion mit zwei dasches gibt (was ich nicht brauche momentan).

    Irgendwelche Vorteile unistd.h oder getopt.h zu wählen?



  • getopt_long ist eh eine GNU-Erweiterung. Ansonsten scheint, wo ich grad noch mal in den POSIX-Standard kucke (der Link, den ich oben angegeben hatte), unistd.h eigentlich richtig zu sein, und meine Suns haben getopt.h auch gar nicht. Was die getopt.h-Funktion angeht, in der glibc bindet unistd.h getopt.h ein, und für den Linker ist das nachher sowieso ein und das selbe.

    Also besser unistd.h, und für Windows ggf. halt ein #ifdef _WIN32-Sonderfall.


Anmelden zum Antworten