Abstrakte Datentypen in c deklarieren und in Dateien speichern

In c ist eine Datei sozusagen ein Abbild des Speichers und plattformübergeifend lesbar mit Perl und PHP

Abstrakte Datentypen sind auch in c nichts Neues. Neu ist nur der Begriff.

Untenstehender Code soll zeigen, wie einfach es ist, einen abstrakten Datentyp zu definieren und diesem Type entsprechend Daten in Dateien zu speichern. Dadurch dass ein c-struct in einer Datei genauso abgelegt wird wie im Hauptspeicher, ist ein expliziter Serializer praktisch überflüssig.

Der Struktur des Datentypes entsprechend werden Eigenschaften von Personen (Name, Vorname usw.) in einem vorher definierten struct erfasst und damit eine Datei blockweise beschrieben. Die Umkehrung, also das blockweise Lesen der Datei erlaubt die Wiederherstellung der erfassten Daten und: Die Datei ist auch mit Perl oder PHP lesbar.

Der Grundgedanke der dahinter steckt ist der, dass sowohl primitive als auch abstrakte Datentypen eine Länge haben, die von vornherein feststeht. Das ist insbesondere in c wichtig, damit entsprechend Speicher allocated werden kann, hierzu ist die Funktion sizeof() eine unentbehrliche Hilfe.

#include <stdio.h>
#include <string.h>
#include <stdint.h>


/* Definiere einen abstrakten Datentyp */
typedef struct{
    char name[50];
    char vname[50];
    char plz[10];
    char str[50];
    char ort[50];
    uint8_t age;
} ADDR;


int main(){
    /* Datei im Binary mode zum Schreiben öffnen */
    FILE *fh = fopen("addr", "wb");
    if( fh == NULL ){
        printf("Error open file for write\n");
        return 255;
    }

    /* 2 Personen, 2 Addressen und die Daten einkopieren */
    ADDR addr[2];
    strcpy( addr[0].name,  "Pfotenhauer Vater" );
    strcpy( addr[0].vname, "Fritz" );
    strcpy( addr[0].plz,   "99099" );
    strcpy( addr[0].str,   "Guddendorfer Str. 123" );
    strcpy( addr[0].ort,   "Berke" );
    addr[0].age = 83;


    strcpy( addr[1].name,  "Pfotenhauer Sohn" );
    strcpy( addr[1].vname, "Franz" );
    strcpy( addr[1].plz,   "99091" );
    strcpy( addr[1].str,   "Berksche Str. 2" );
    strcpy( addr[1].ort,   "Guddendorf" );
    addr[1].age = 55;

    /* Genau 2 stuct-Elemente in die Datei schreiben */
    fwrite( addr, sizeof(ADDR), 2, fh);
    fclose(fh);

    /* Neues FileHandle erstellen,
       Datei im Binmode zum Lesen öffnen
       und 2 Personen auslesen */
    FILE *fr = fopen("addr","rb");
    if( fh == NULL ){
        printf("Error open file for read\n");
        return 255;
    }

    /* 2 Personen dem typedef struct entsprechend */
    ADDR person[2];
    /* Genau 2 Elemente des abstrakten Datentyps auslesen */
    fread( person, sizeof(ADDR), 2, fr);

    /* Personen auf Konsole ausgeben */
    for(int i = 0; i < 2; i++)
        printf(
          "Name: %s\nVorname: %s\nStr: %s\nPLZ: %s\nOrt: %s\nAlter: %u\n\n",
            person[i].name,
            person[i].vname,
            person[i].str,
            person[i].plz,
            person[i].ort,
            person[i].age
        );


    return 0;
}

Abstrakte Datentypen sind plattformübergreifend verwendbar

Mit der richtigen, dem c-struct entsprechenden Schablone ist es kein Problem die in c erzeugte Datei mit Perl auszulesen, siehe Code untenstehend:

use strict;
use warnings;
use IO::File;
use Data::Dumper;

my $fh = IO::File->new;
$fh->open("addr", O_BINARY|O_RDONLY) or die $!;

my @fields = qw(name vname plz str ort age);
my @result = ();
while( read( $fh, my $buffer, 211) ){
    my @values = unpack "Z50Z50Z10Z50Z50C", $buffer;
    my %person = ();
    @person{@fields} = @values;
    push @result, \%person;
}

print Dumper \@result;
$VAR1 = [
          {
            'ort' => 'Berke',
            'str' => 'Guddendorfer Str. 123',
            'plz' => '99099',
            'name' => 'Pfotenhauer Vater',
            'age' => 83,
            'vname' => 'Fritz'
          },
          {
            'ort' => 'Guddendorf',
            'str' => 'Berksche Str. 2',
            'plz' => '99091',
            'name' => 'Pfotenhauer Sohn',
            'age' => 55,
            'vname' => 'Franz'
          }
        ];

Speicherreservierung und Stringterminierung

Die pack-Schablone Z bewirkt, dass ein String beim ersten Auftreten eines Null-Bytes terminiert wird. Andererseits wird für den String Speicher reserviert entsprechend dieser Schablone und was an Speicher nicht gebraucht wird, wird mit Null-Bytes aufgefüllt.

# Ausgegeben wird 123
print unpack "Z3", "123\045";

# Reserviert 3 Bytes
pack "Z3", "ab\0";

Wenn wir in die mit c erstellte Datei schauen, sehen wir, dass alle Strings mit Null-Bytes terminiert sind und was entsprechend der Platzresiervierung danach kommt, sind Bytes mit zufälligen Oktettenwertigkeiten, wie sie zum Zeitpunkt der Dateierstellung im Hauptspeicher lagen.

Eine Datei mit demselben Aufbau in Perl erzeugen

Dem c-struct entspricht in Perl ein Hash und damit die Reihenfolge der Datenfelder nicht verlorengeht, werden diese in einem gesonderten Array @fields festgehalten. Von besonderem Interesse ist hier natürlich auch wieder -- wie beim Lesen der Datei -- eine spezielle Schablone für die pack()-Funktion:

write_addr(
    'ort' => 'Guddendorf',
    'str' => 'Berksche Str. 2',
    'plz' => '99091',
    'name' => 'Pfotenhauer Sohn',
    'age' => 55,
    'vname' => 'Franz'
);

sub write_addr{
    my %person = @_;

    my $fh = IO::File->new;
    $fh->open("addr", O_BINARY|O_RDWR|O_TRUNC|O_CREAT) or die $!;
    my @fields = qw(name vname plz str ort age);

    $fh->print( pack "Z50Z50Z10Z50Z50C", @person{@fields} );
    $fh->close;
}

Auch diese Datei hat, je nach Anzahl der darin gespeicherten Personendaten eine genau definierte Länge, nämlich das ganzzahlige Vielfache von 211 Bytes (je Person). Der Unterschied zu c besteht lediglich darin, dass Perl nicht den Speicher in der Datei abbildet, sondern nicht benötigte Bytes mit Nullen auffüllt. Aber auch eine solche mit Perl erzeugte Datei ist mit c lesbar.


Anbieter: nmq​rstx-18­@yahoo.de, die Seite verwendet funktionsbedingt einen Session-Cookie und ist Bestandteil meines nach modernen Aspekten in Perl entwickelten Frameworks.