Alternative zu memcpy?



  • Mach dich mal klar darüber, dass das struct-Padding bei den Methoden...

    a) jedes struct direct über fread füllen
    b) mit fread gelesenes bytearray via memcpy in struct kopieren
    c) struct-Zeiger zeigt auf mit fread gelesenes bytearray

    ... immer genau das gleiche Problem ist. Entweder deine Definition der struct passt oder halt nicht.


  • Mod

    Weder memcpy, noch Casts, noch ein fread lösen auf magische Weise Portabilitätsprobleme. Das sind alles drei nur unterschiedliche Schreibweisen für die gleiche technische Aktion, die Rohdaten aus der Datei direkt so in den Speicher zu schreiben, wo sie dann als Elemente deines structs aufgefasst werden. Ein sehr cleverer Compiler könnte sogar für alle drei Varianten den gleichen Code erzeugen (ich glaube nicht, dass es so einen cleveren Compiler gibt, die Funktionsaufrufe sind eine ziemlich starke Optimierungshürde) oder zumindest sehr ähnlichen (dies kann ich mir realistisch vorstellen. Ich habe schon gesehen, wie Kopierschleifen zu memcpy-Aufrufen optimiert wurden).

    Wenn du diese Probleme grundlegend von der Ursache her lösen willst, dann musst du dich von all diesem Gefummel auf Byteebene verabschieden. Wutz ist zwar nicht immer höflich, hat aber meistens Recht: Geh das Problem andersherum an! Definier genau, wie dein Dateiformat aussieht. Wenn es ein Binärformat sein soll (warum überhaupt? Ich halte dies für einen großen Nachteil wenn es Portabel sein soll), dann leg genau die Byteorder und ähnliches fest. Dann schreibst du eine Parsefunktion, die diese Daten plattformunabhängig in dein Strukturformat einliest (indem du die Werte aus dem Binärformat berechnest, anstatt sie einfach in den Speicher zu schreiben, so wie es ein scanf/atoi/strotl bei einem menschenlesbaren Format tun würde). Falls es dir nicht schnell genug sein sollte, dann kannst du noch optimierte Lesefunktionen für bestimmte Plattformen schreiben, die, unter Kenntnis des Formates und der Eigenschaften der Plattform, die Daten direkt richtig in das struct lesen. Und dies wäre dann auf der einen Plattform, die genau kompatibel zu deinem Binärformat ist, gerade ein fread/memcpy/Cast.

    Oder du sparst dir das alles mit einem menschenlesbarem Format und machst es so wie die meisten Leute, denen es egal ist, ob das Programm am Anfang 0.0001 Sekunden zum Lesen oder 0.0002 Sekunden benötigt.

    ich hab noch nie irgendwo gesehen, dass jedes element einzeln eingelesen wird!

    Was hast du denn überhaupt schon gesehen? Ist das nicht deine lang gesuchte Alternative?



  • also soll ich es jetzt casten?



  • SeppJ schrieb:

    Weder memcpy, noch Casts, noch ein fread lösen auf magische Weise Portabilitätsprobleme. Das sind alles drei nur unterschiedliche Schreibweisen für die gleiche technische Aktion, die Rohdaten aus der Datei direkt so in den Speicher zu schreiben, wo sie dann als Elemente deines structs aufgefasst werden. Ein sehr cleverer Compiler könnte sogar für alle drei Varianten den gleichen Code erzeugen (ich glaube nicht, dass es so einen cleveren Compiler gibt, die Funktionsaufrufe sind eine ziemlich starke Optimierungshürde) oder zumindest sehr ähnlichen (dies kann ich mir realistisch vorstellen. Ich habe schon gesehen, wie Kopierschleifen zu memcpy-Aufrufen optimiert wurden).

    Wenn du diese Probleme grundlegend von der Ursache her lösen willst, dann musst du dich von all diesem Gefummel auf Byteebene verabschieden. Wutz ist zwar nicht immer höflich, hat aber meistens Recht: Geh das Problem andersherum an! Definier genau, wie dein Dateiformat aussieht. Wenn es ein Binärformat sein soll (warum überhaupt? Ich halte dies für einen großen Nachteil wenn es Portabel sein soll), dann leg genau die Byteorder und ähnliches fest. Dann schreibst du eine Parsefunktion, die diese Daten plattformunabhängig in dein Strukturformat einliest (indem du die Werte aus dem Binärformat berechnest, anstatt sie einfach in den Speicher zu schreiben, so wie es ein scanf/atoi/strotl bei einem menschenlesbaren Format tun würde). Falls es dir nicht schnell genug sein sollte, dann kannst du noch optimierte Lesefunktionen für bestimmte Plattformen schreiben, die, unter Kenntnis des Formates und der Eigenschaften der Plattform, die Daten direkt richtig in das struct lesen. Und dies wäre dann auf der einen Plattform, die genau kompatibel zu deinem Binärformat ist, gerade ein fread/memcpy/Cast.

    praktisches beispiel?

    also eigentlich gings mir hier hauptsächlich um dieses struct-padding, und nicht um die byte order (warum sollte ich mich um die scheißen?). auf den meisten pcs hat man doch sowieso little-endian.


  • Mod

    ............... schrieb:

    praktisches beispiel?

    Nur wenn du mir eine gute Begründung gibst, wieso du nicht den einfachen Weg nimmst.



  • SeppJ schrieb:

    ............... schrieb:

    praktisches beispiel?

    Nur wenn du mir eine gute Begründung gibst, wieso du nicht den einfachen Weg nimmst.

    ja was ist jetzt der einfache weg? casten?


  • Mod

    also jetzt doch casten? schrieb:

    ja was ist jetzt der einfache weg? casten?

    Menschenlesbares Format. Oder falls doch Binärformat (warum?) dann genau definiert ohne optimiertes Lesen sondern mit einem Parser im Stil eines menschenlesbaren Formates.*

    Wenn du wirklich auf Byteebene rumfrickeln möchtest, dann musst du auch genau verstehen, was du da tust. Ich (und wohl auch die anderen Antwortgeber in diesem Thread) habe hingegen den Eindruck, dass du noch ziemlicher Anfänger bist, der zudem die falschen Prioritäten setzt. Lern erst einmal besser zu programmieren! Tieferes Verständnis kommt dann schon noch. Es ist jedenfalls nicht damit getan, ein Beispiel aus einem Forum abzuschreiben und dieses dann für die eigenen Zwecke zu verändern. Das wird schiefgehen. Dies ist einer der Gründe, wieso ich so zögerlich bin, dir ein konkretes Beispiel für das Bytegefrickel zu geben.

    *: Bevor du mit einem Speicherplatzargument kommst: Es gibt komfortable Bibliotheken, die du einfach zwischen Lese-/Schreiblogik und eine komprimierte Datei schalten kannst.



  • SeppJ schrieb:

    also jetzt doch casten? schrieb:

    ja was ist jetzt der einfache weg? casten?

    Menschenlesbares Format. Oder falls doch (warum?) Binärformat dann genau definiert ohne optimiertes Lesen.

    Wenn du wirklich auf Byteebene rumfrickeln möchtest, dann musst du auch genau verstehen, was du da tust. Ich (und wohl auch die anderen Antwortgeber in diesem Thread) habe hingegen den Eindruck, dass du noch ziemlicher Anfänger bist, der zudem die falschen Prioritäten setzt. Lern erst einmal besser zu programmieren! Tieferes Verständnis kommt dann schon noch. Es ist jedenfalls nicht damit getan, ein Beispiel aus einem Forum abzuschreiben und dieses dann für die eigenen Zwecke zu verändern. Das wird schiefgehen. Dies ist einer der Gründe, wieso ich so zögerlich bin, dir ein konkretes Beispiel für das Bytegefrickel zu geben.

    dein menschenlesbarer weg ist mir zu kompliziert, keine ahnung was genau du meinst.

    naja ich glaub ich lass das mal lieber bei dem casten. andere scheinen sich da mit byte-padding auch keine gedanken zu machen, also warum?



  • Üblicherweise macht man das ungefähr so:

    #include <stdio.h>
    #include <stdint.h>
    #include <assert.h>
    
    typedef struct Record
    {
    	int32_t a, b, c;
    }
    Record;
    
    /*schreibt den Wert portabel in Big Endian in die Datei*/
    void serialize_int32(FILE *out, int32_t value)
    {
    	fputc((value >> 24) & 0xff, out);
    	fputc((value >> 16) & 0xff, out);
    	fputc((value >>  8) & 0xff, out);
    	fputc((value      ) & 0xff, out);
    }
    
    /*das gleiche in lesender Richtung*/
    int32_t deserialize_int32(FILE *in)
    {
    	int32_t result = 0;
    	/*Der Cast ist da, weil int nicht unbedingt 32 Bit haben muss.*/
    	result |= ((int32_t)fgetc(in) << 24);
    	result |= ((int32_t)fgetc(in) << 16);
    	result |= ((int32_t)fgetc(in) <<  8);
    	result |= ((int32_t)fgetc(in)      );
    	return result;
    }
    
    void serialize_record(FILE *out, Record const *record)
    {
    	serialize_int32(out, record->a);
    	serialize_int32(out, record->b);
    	serialize_int32(out, record->c);
    }
    
    void deserialize_record(FILE *in, Record *record)
    {
    	record->a = deserialize_int32(in);
    	record->b = deserialize_int32(in);
    	record->c = deserialize_int32(in);
    }
    
    static Record const TestData = {1, 2, 3};
    static char const * const FileName = "test.bin";
    
    int main(void)
    {
    	Record deserialized;
    	FILE *file = fopen(FileName, "wb");
    	if (!file)
    	{
    		return 1;
    	}
    	serialize_record(file, &TestData);
    	fclose(file);
    
    	file = fopen(FileName, "rb");
    	if (!file)
    	{
    		return 2;
    	}
    	deserialize_record(file, &deserialized);
    	if (ferror(file))
    	{
    		printf("File could not be read\n");
    		fclose(file);
    		return 3;
    	}
    	assert(TestData.a == deserialized.a);
    	assert(TestData.b == deserialized.b);
    	assert(TestData.c == deserialized.c);
    	fclose(file);
    	return 0;
    }
    

    Kein einziges fread oder memcpy ist nötig.
    Schnell genug ist das und vor allem lesbar und portabel.



  • struct-padding schrieb:

    wie machen es denn anwendungen, z.b. der linuxkernel? die dateisysteme dort werden ja auch durch structs implementiert. wird da etwas mit memcpy eingelsen oder direkt gecastet? weiß das jemand?

    Weder noch. Das On-Disk-Format eines Linux-Dateisystems spezifiert für jedes Feld die genaue Größe, die zu erwartende Byteorder und einen Offset zum Beginn der Struktur.

    Ein JBD2-Journal-Header (u.a. von Ext3 und Ext4 verwendet) ist etwa so definiert:

    Offset    Typ     Name
        0     be32    h_magic
        4     be32    h_blocktype
        8     be32    h_sequence
    

    Das heißt, dass jedes der drei Felder 32 Bit groß ist und in Big-Endian-Byteorder vorliegen muss. Außerdem schließen die Felder direkt aneinander an, es gibt kein Padding.

    An diese Spezifikation muss sich jeder halten. Tut er es nicht, ist das was er schreibt einfach kein gültiger JBD2-Blockheader mehr.

    Außerhalb des Journals nutzt Ext4 auch Strukturen mit LE-Byteorder. Wie bei Binärformaten üblich, muss man hier einfach genau darauf achten, was vereinbart wurde. Daher kann ich Sepp nur zustimmen: sofern es irgendwie möglich ist, benutze ein textuelles Format. Da ersparst du dir sehr viel Fummelei und bist trotzdem portabel.


Anmelden zum Antworten