cat-like tool with fread() and write() in C

mogbo

Banned
Guten Morgen,
gleich vorne Weg, ich möchte die Welt nicht neu erfinden, war blos ein wenig am rumprobieren:

Code:
#include <stdio.h>
#include <err.h>
#include <unistd.h>

int
main(int argc, char *argv[])
{
  if (argc != 2)
      err(1, "argc needs 1 extra argument (not more)");

  FILE *read_file;
  read_file = fopen(argv[1], "r");

  char s_buffer[1024];

  while (!feof(read_file)) {
      fread(s_buffer, sizeof(s_buffer), 1, read_file);
      write(1, s_buffer, sizeof(s_buffer));
  }

  fclose(read_file);
  return 0;
}
Logischerweise gibt das Tool am Ende nochmal den vollen Inhalt von s_buffer aus, da write eine vorgefertigte Größe ausgibt, quasi es gibt alles aus wie es soll, nur leider mit den Überbleibseln aus dem vorletzten fread(), was im array nicht überschrieben wurde.

Fällt hier jemand eine sinnvolle Möglichkeit ein, eine '\0' zu setzen um es mit write(1, s_buffer, strlen(s_buffer)) lesen zu können ohne nochmal den ganzen Inhalt anzuhängen?

Ein Tipp reicht mir, will ja was lernen :)

Gibt es eine Standartfunktion mit der man einen array vollständig nullen kann?
 
Gibt es eine Standartfunktion mit der man einen array vollständig nullen kann?
Code:
#include <stdio.h>
#include <err.h>
#include <unistd.h>
#include <string.h>

int
main(int argc, char *argv[])
{
  if (argc != 2)
      err(1, "argc needs 1 extra argument (not more)");

  FILE *read_file;
  read_file = fopen(argv[1], "r");

  char s_buffer[1024];

  while (!feof(read_file)) {
      fread(s_buffer, sizeof(s_buffer), 1, read_file);
      write(1, s_buffer, sizeof(s_buffer));
      memset(s_buffer, 0, sizeof(s_buffer));
  }

  fclose(read_file);
  return 0;
}
memset() scheint das Problem zu lösen, gibts noch eine "billigere" Lösung?
 
memset() scheint das Problem zu lösen, gibts noch eine "billigere" Lösung?

Ja, fread(3) gibt dir die Anzahl gelesener Bytes zurück.
Du musst also nur eine Zahl mitlaufen lassen und dort dann das Nullbyte setzen.

Ich würde mir auch den Test auf EOF sparen. Einfach solange fread() absetzen, bis es 0 zurückgibt.

Rob
 
Ja, fread(3) gibt dir die Anzahl gelesener Bytes zurück.
Du musst also nur eine Zahl mitlaufen lassen und dort dann das Nullbyte setzen.

Ich würde mir auch den Test auf EOF sparen. Einfach solange fread() absetzen, bis es 0 zurückgibt.

Rob
Vielen Dank, hatte fread() falsch genutzt:
Code:
#include <stdio.h>
#include <err.h>
#include <unistd.h>

int
main(int argc, char *argv[])
{
  if (argc != 2)
      err(1, "argc needs 1 extra argument (not more)");

  FILE *read_file;
  read_file = fopen(argv[1], "r");

  char s_buffer[1024];
  unsigned long i, c;

  while ((i = fread(s_buffer, 1, sizeof(s_buffer), read_file)) != 0) {
      write(1, s_buffer, i);
  }

  fclose(read_file);
  return 0;
}
Bei meiner obrigen Lösung gab fread() immer den Wert 1 zurück, wodurch ich nicht auf das Ende schließen konnte.

In meiner "Lösung" hätte ich mir nun sogar das '\0' gespart oder gibt es einen Grund es trotzdem zu setzen?
 
Weil es irgendwie passt, ein bisschen Literatur bzw. ein wertvolles Nachschlagewerk, was stets in greifbarer Naehe sein sollte. :)
 
Ich würde grundsätzlich dafür sorgen, dass ein String vor der Ausgabe nullterminiert ist.
Quasi:
Code:
#include <stdio.h>
#include <err.h>
#include <unistd.h>

int
main(int argc, char *argv[])
{
  if (argc != 2)
      err(1, "argc needs 1 extra argument (not more)");

  FILE *read_file;
  read_file = fopen(argv[1], "r");

  char s_buffer[1024];
  unsigned long i;

  while ((i = fread(s_buffer, 1, sizeof(s_buffer) - 1, read_file)) != 0) {
      s_buffer[i] = '\0';
      write(1, s_buffer, i);
  }

  fclose(read_file);
  return 0;
}
 
Die Frage ist natürlich auch, ob bei der hier im Beispiel teilweise unbekannten Blockgröße
( wir wissen ja nur: vorzeichenbehaftetes byte und 1024 Elelemente oder eben weniger )
die function fread() (also blockweises Lesen) der richtige Ansatz ist.
 
die function fread() (also blockweises Lesen) der richtige Ansatz ist.
Ging mir um den Benchmark, ich dachte mir einfach
Code:
...
FILE *read_file;
read_file = fopen(argv[1], "r");
int c = fgetc(read_file);

while (c != EOF) {
    write(1, c, 1);
    c = fgetc(read_file);
}
...
kann nicht sonderlich schnell sein, also bastelte ich mir einen buffer mit fgetc und gab strings aus, kam jedoch nur bedingt an cat ran.

Benchmark von cat ausgehend:
fgetc mit char Einzelausgabe 120 %
fgetc mit buffer und stringausgabe 105 %
fread mit buffer und stringausgabe 95 %

Ca. Angaben, war in meiner VM nicht sooo stabil mit der Zeit.
 
fread mit buffer und stringausgabe 95 %
Ein Punkt dürfte sein, dass dein Buffer zu klein ist. Die maximale Größe eines I/O-Requests wird durch das Makro MAXPHYS in sys/param.h definiert. Der Buffer sollte dann auch minimal so groß sein, damit jeder Syscall das mögliche Maximum an Daten verarbeitet. Alternativ könnte man die Datei auf per mmap() Syscall mappen und den Kernel die Arbeit machen lassen. Aber das dürfte hier Overkill sein, du liest ja sowieso nur linear und springst nicht quer durch die Datei.
 
AFAIK ist nicht gern gesehen, die beiden I/O-APIs gemischt einzusetzen.
Nutze lieber fwrite(), wenn du fread() einsetzt.

Rob
Ach nice, Manpage von fwrite kurz überflogen, stdout-Output gegoogelt und was dabei gelernt, guter Tipp :)

Ein Punkt dürfte sein, dass dein Buffer zu klein ist. Die maximale Größe eines I/O-Requests wird durch das Makro MAXPHYS in sys/param.h definiert. Der Buffer sollte dann auch minimal so groß sein, damit jeder Syscall das mögliche Maximum an Daten verarbeitet.
Finde zu param.h keine Manpage (kanns aber Includen) - somit ist der Tipp für mich von Interesse aber ich komm auf keinen grünen Zweig wenn ich was suche. Hättest du dazu einen Link, würde mir sicher helfen
 
Ich muss ja zugeben, dass ich das mit MAXPHYS einfach wusste... Allerdings war es aus didaktischer Sicht vielleicht auch gar nicht gut es überhaupt zu erwähnen, denn MAXPHYS ist ein systemspezifisches, offensichtlich nicht dokumentiertes Makro. Linux hat es zum Beispiel gar nicht. Von daher: Vergiss es. Wen du doch weitermachen willst sind solche internen Limits in der NOTES-Datei in den Sourcen dokumentiert. Also z.B. unter https://github.com/freebsd/freebsd/blob/master/sys/conf/NOTES#L123
 
Auch auf die Gefahr hin, dass das Thema nur mich interessiert, hier ein paar Ergebnisse:

Habe hiermit 1 File erstellt und 5 weitere male kopiert (quasi 6 Files)
Code:
cat test.c
#include <stdio.h>
#include <err.h>

int
main(void)
{
    FILE *write_file;
    if ((write_file = fopen("./test.txt", "w")) == NULL)
        err(1, "%s: No write_file", __func__);

    for (int i = 0; i < 1000000; i++) {
        (void) fprintf(write_file, "%i", i);
    }
    (void) fclose(write_file);
    return 0;
}

Getestet wurde:
cat von OpenBSD + 3 Eigenbauten:

qcat.c (MAP_POPULATE existiert bei OpenBSD mmap nicht)
War meine erste Anwendung von mmap() und wurde mehr oder weniger aus einem Tutorial + Manpage gebastelt
Code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <fcntl.h>
#include <sys/mman.h>

#include <err.h>
#include <sysexits.h>


int
main(const int argc, char *argv[])
{
    if (argc < 2)
        err(EX_USAGE, "%s: No input", __func__);

    if (pledge("stdio rpath", NULL) == -1)
        err(EX_OSERR, "%s: pledge()", __func__);

    FILE *read_file, *fout = stdout;
    int i, fd, fs;
    char *map;

    for (i = 1; i != argc; i++) {
        if ((read_file = fopen(argv[i], "r")) == NULL)
            err(EX_NOINPUT, "%s: %s not found", __func__, argv[i]);

        if ((fd = open(argv[i], O_RDONLY)) == -1)
            err(EX_NOINPUT, "%s: %s not found", __func__, argv[i]);

        if (fseek(read_file, 0, SEEK_END) == -1)
            err(EX_OSERR, "%s: fseek() failure", __func__);

        fs = ftell(read_file);

        map = mmap(0, fs, PROT_READ, MAP_PRIVATE, fd, 0);
        (void) fwrite(map, fs, sizeof(char), fout);

        (void) fclose(read_file);
        (void) close(fd);
    }
    return EXIT_SUCCESS;
}

qcat2.c
Code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <fcntl.h>
#include <sys/mman.h>

#include <err.h>
#include <sysexits.h>


int
main(const int argc, char *argv[])
{
    if (argc < 2)
        err(EX_USAGE, "%s: No input", __func__);

    if (pledge("stdio rpath", NULL) == -1)
        err(EX_OSERR, "%s: pledge()", __func__);

    FILE *read_file, *fout = stdout;

    char buffer[BUFSIZ];
    int i, z;
    for (i = 1; i != argc; i++) {
        if (!(read_file = fopen(argv[i], "r")))
            err(EX_NOINPUT, "%s: %s not found", __func__, argv[i]);

        while ((z = fread(buffer, sizeof(char), BUFSIZ, read_file))) {
            (void) fwrite(buffer, z, sizeof(char), fout);
        }
        (void) fclose(read_file);
    }
    return EXIT_SUCCESS;
}

qcat3.c
Code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <err.h>
#include <sysexits.h>


int
main(const int argc, char *argv[])
{
    if (argc < 2)
        err(EX_USAGE, "%s: No input", __func__);

    if (pledge("stdio rpath", NULL) == -1)
        err(EX_OSERR, "%s: pledge()", __func__);

    int i, c;
    FILE *read_file;
    for (i = 1; i != argc; i++) {
        if ((read_file = fopen(argv[i], "r")) == NULL)
            err(EX_NOINPUT, "%s: %s is missing", __func__, argv[i]);

        while ((c = fgetc(read_file)) != EOF) {
            (void) putchar(c);
        }
        (void) fclose(read_file);
    }
    return EXIT_SUCCESS;
}

time cat test* -> 20.06 Sekunden
time ./qcat test* -> 20.09 Sekunden
time ./qcat2 test* -> 20.14 Sekunden
time ./qcat3 test* -> 20.12 Sekunden

In der Regel waren fread() und cat bzw. mmap() am schnellsten und putchar() ein klein wenig hinterher.

Will hiermit weder etwas fragen, noch eine besondere Aussage treffen, hatte reines Optimierungsinteresse und festgestellt, dass ichs noch nicht kann. Falls dennoch wer Fehler sieht bzw. direkt eine extreme Optimierung weiß, gerne per Nachricht direkt an mich :)
 
Posix mit open/read/write/close.

Hier die Tests ;)

Code:
georg@  /tmp  dd if=/dev/zero of=/tmp/test bs=1G count=1
1+0 records in
1+0 records out
1073741824 bytes transferred in 0.659039 secs (1629254714 bytes/sec)
georg@  /tmp  time ./mycat /tmp/test > /tmp/testout
./mycat /tmp/test > /tmp/testout  0,03s user 1,11s system 99% cpu 1,145 total
georg@  /tmp  time cat /tmp/test > /tmp/testout
cat /tmp/test > /tmp/testout  0,00s user 0,35s system 31% cpu 1,114 total
georg@  /tmp  time ./mycat /tmp/test > /tmp/testoutmycat
./mycat /tmp/test > /tmp/testoutmycat  0,02s user 1,09s system 99% cpu 1,105 total
georg@  /tmp  time cat /tmp/test > /tmp/testoutcat   
cat /tmp/test > /tmp/testoutcat  0,01s user 0,41s system 28% cpu 1,460 total
georg@  /tmp  sha1 test testoutmycat testoutcat
SHA1 (test) = 2a492f15396a6768bcbca016993f4b4c8b0b5307
SHA1 (testoutmycat) = 2a492f15396a6768bcbca016993f4b4c8b0b5307
SHA1 (testoutcat) = 2a492f15396a6768bcbca016993f4b4c8b0b5307

Code:
cc main.c -o mycat

Src
Code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

int main(int argc, char *argv[]) {
  int fd = -1;
  int bytes_written = 0;
  int bytes_read = 0;
  int bytes_left = 0;
  int pos = 0;

  char buffer[1638400];

  if (argc > 2) {
    printf("Usage:\n\nmycat [filename]\nif filename is not set, stdin is "
           "used\n\n");
    goto err;
  } else if (argc == 1) {
    fd = STDIN_FILENO;
  } else if (argc == 2) {
    fd = open(argv[1], O_RDONLY);
    if (fd < 0) {
      printf("Could not open file: %s\n", argv[1]);
      goto err;
    }
  }

  while ((bytes_read = read(fd, buffer, sizeof(buffer)))) {
    pos = 0;
    bytes_left = bytes_read;
    while (bytes_left > 0) {
      bytes_written = write(STDOUT_FILENO, buffer + pos, bytes_left);
      if (bytes_written < 0) {
        printf("Broken output");
        goto err;
      }
      bytes_left -= bytes_written;
      pos += bytes_written;
    }
  }

  close(fd);
  fd = -1;
  return EXIT_SUCCESS;

err:
  if (fd > -1 && fd != STDIN_FILENO) {
    close(fd);
  }
  return EXIT_FAILURE;
}
 
Zuletzt bearbeitet:
Das ist praktisch nur durch den Puffer limitiert.

Code:
georg@  /tmp  cc -O2 main.c -o mycat
georg@  /tmp  time ./mycat /tmp/test > /tmp/testoutmycat
./mycat /tmp/test > /tmp/testoutmycat  0,01s user 0,32s system 22% cpu 1,461 total
georg@  /tmp  time cat /tmp/test > /tmp/testoutcat     
cat /tmp/test > /tmp/testoutcat  0,01s user 0,30s system 21% cpu 1,477 total
 
Hab das ganze jetzt mal auf richtiger Hardware getestet:
In meinem Fall merke ich praktisch keinen Unterschied zwischen fread(), read(), und mmap() (OpenBSD in einer VirtualBox).
Auch das Beispiel von @schorsch_76 hebt sich mit Output in die Shell nicht von fgetc() + putchar() ab

Ohne VM sind die Sprünge deutlicher
 
@mogbo Ich hab das bei mir auf FreeBSD 11.1 amd64 und einer SSD getestet. Die Größe des Puffers bestimmt wie oft ein Kontextwechsel in den Kernel vorgenommen werden muss.
 
Wobei cat sowieso ein schlechter Benchmark ist, da linear gelesen wird und der Cache keine große Rolle spielt. Interessant wird es bei mehr oder weniger zufälligen Zugriffsmustern auf größere Dateien.
 
Die Größe des Puffers bestimmt wie oft ein Kontextwechsel in den Kernel vorgenommen werden muss.
Ist auch logisch, fällt nur bei der Ausgabe auf die Shell praktisch nicht ins Gewicht, da jegliche sichtbare Ausgabe wohl ziemlich teuer zu sein scheint.

Wobei cat sowieso ein schlechter Benchmark ist, da linear gelesen wird und der Cache keine große Rolle spielt. Interessant wird es bei mehr oder weniger zufälligen Zugriffsmustern auf größere Dateien.
Wieder was zum Rumspielen gefunden, danke :ugly:
 
Komme gerade nichtmehr aus der Faszination raus, wie und warum geht das?

Code:
int
zahl(void)
{
    int calc = arc4random() % 100 + 1;

    return calc;
}


int
main(void)
{
    char buffer[zahl()];
    printf("sizeof(buffer): %lu\n", sizeof(buffer));

    return EXIT_SUCCESS;
}
Code:
int
*zahl(void)
{
    void *calc = malloc(sizeof(int));
    *((int *) calc) = arc4random() % 100 + 1;

    return calc;
}


int
main(void)
{
    void *ptr;

    char buffer[*((int *) (ptr = zahl()))];
    freezero(ptr, sizeof(int));

    printf("sizeof(buffer): %lu\n", sizeof(buffer));

    return EXIT_SUCCESS;
}
Kann ich das C-technisch auch ohne sizeof() rausfinden?
 
Zurück
Oben