C typecast array nach struct

#7923829
Lesenswert?

Wenn es unbeding sein muss: Mach ds zu einem pointer und caste zumindest ein mal über char *.

Ungefähr so:

1
struct dat_struct
2
{
3
  int a, b, c;
4
};
5
    
6
struct dat_struct * ds;
7
typedef int dat_array [3];
8
dat_array da = {1, 2, 3};
9
  
10
void test (void)
11
{
12
  ds = (struct dat_struct*) (char *) &da;
13
}

Der cast über char * sieht zwar nutzlos aus, sorgt aber dafür, das dem Compiler das aliasing der Daten erklärt wird. Er darf zum Beispiel nicht mehr davon ausgehen, das Änderungen in 'da' keine Auswirkungen auf *ds haben.

Wenn Du es irgendwie anders lösen kannst, dann lös es anders. Es funktioniert zwar, aber solcher Code ist heikel weil er die Typensicherung komplett außer Kraft setzt und davon ausgeht, das die struct member in dat_struct exakt zum Array passen.

Gast #7923834
Lesenswert?

Michael schrieb:

Wie mache ich in C einen Typecast, mit dem ich ein Array in eine Struktur umwandle?

1
> struct dat_struct
2
>   {
3
>   int a, b, c;
4
>   };
5
> 
6
> struct dat_struct ds;
7
> 
8
> typedef int dat_array [3];
9
> 
10
> dat_array da = {1, 2, 3};
11
> 
12
> ds = (???) da;
13
>

ds = (struct dat_struct)da;
da ist ja schon die Adresse des Feldes, und das Alignment ist identisch, macht also keine Probleme.
oder
memcpy(&ds, da, sizeof(da));

memcpy mit konstanter Größe wird bei modernen Compilern mit -O2 auch wegoptimiert.

Moderator #7923859
Lesenswert?

Michael schrieb:

ds = (???) da;

Der Zuweisungsoperator deutet darauf hin, dass du eine Kopie von /da/ mit einem anderen Datentyp (nämlich (struct dat_struct)) anlegen möchtest. Das geht am besten mit memcpy:

Dergute W. schrieb:

10002.) memcpy(&ds, da, 3 * sizeof(int));

Ich habe mir mal angewöhnt, als drittes Argument von memcpy immer die Größe des Zielobjekts (und nicht des Quellobjekts) anzugeben. Das vermeidet das Beschreiben von Speicherbereichen außerhalb des Zielobjekts, wenn das Quellobjekt versehentlich größer als das Zielobjekt ist:

1
  memcpy(&ds, da, sizeof (struct dat_struct));

Max H. schrieb:

ds = (struct dat_struct*) (char *) &da;

Der C-Standard verbietet es, auf das int-Array über den L-Value des nichtkompatiblen Typs (struct dat_struct) zuzugreifen. An dieser Regel ändert auch die mehrfache Casterei nichts (Kapitel 6, Abschnitt 5, Absatz 7). Für memcpy gilt diese Einschränkung nicht.

: Bearbeitet durch Moderator
#7923866
Lesenswert?

Yalu X. schrieb:

Ich habe mir mal angewöhnt, als drittes Argument von memcpy immer die Größe des Zielobjekts (und nicht des Quellobjekts) anzugeben. Das vermeidet das Beschreiben von Speicherbereichen außerhalb des Zielobjekts, wenn das Quellobjekt versehentlich größer als das Zielobjekt ist:

Dafür liest man halt über das Ende des Quellobjekts hinaus, falls es versehentlich kleiner ist. Das ist auch nicht so viel besser.

memcpy(&ds, da, sizeof (struct dat_struct));

Warum nicht gleich

1
   memcpy(&ds, da, sizeof ds);
Gast #7923945
Lesenswert?

Yalu X. schrieb:

Der C-Standard verbietet es, auf das int-Array über den L-Value des nichtkompatiblen Typs (struct dat_struct) zuzugreifen. An dieser Regel ändert auch die mehrfache Casterei nichts (Kapitel 6, Abschnitt 5, Absatz 7). Für memcpy gilt diese Einschränkung nicht.

Auf welchen Standard beziehst du dich da genau, und bist du dir sicher?

Das ist gängige Praxis seitdem es C gibt, und ich sehe keinen technischen Grund warum das ein Problem sein soll, solange das Alignment passt.
Der Hack mit memcpy() funktioniert zwar, setzt aber einen modernen Compiler mit aufgedrehten Optimierungen voraus und funktioniert in einer Freestanding Umgebung nicht. Der ASM Code ist jedenfalls identisch.

Moderator #7923970
Lesenswert?

Rolf M. schrieb:

Yalu X. schrieb:

Ich habe mir mal angewöhnt, als drittes Argument von memcpy immer die Größe des Zielobjekts (und nicht des Quellobjekts) anzugeben. Das vermeidet das Beschreiben von Speicherbereichen außerhalb des Zielobjekts, wenn das Quellobjekt versehentlich größer als das Zielobjekt ist:

Dafür liest man halt über das Ende des Quellobjekts hinaus, falls es versehentlich kleiner ist. Das ist auch nicht so viel besser.

Nicht viel, aber vielleicht ein wenig, weil Lesen normalerweise zerstörungsfrei ist.

memcpy(&ds, da, sizeof (struct dat_struct));

Warum nicht gleich memcpy(&ds, da, sizeof ds);

Ja, natürlich. Ich habe mich durch das

1
  3 * sizeof(int)"

im zitierten Programmcode blenden lassen. Auch dort hätte man einfacher

1
  sizeof da

schreiben können.

Udo K. schrieb:

Yalu X. schrieb:

Der C-Standard verbietet es, auf das int-Array über den L-Value des nichtkompatiblen Typs (struct dat_struct) zuzugreifen. An dieser Regel ändert auch die mehrfache Casterei nichts (Kapitel 6, Abschnitt 5, Absatz 7). Für memcpy gilt diese Einschränkung nicht.

Auf welchen Standard beziehst du dich da genau, und bist du dir sicher?

Auf jeden von ISO bzw. ANSI. In C90 stand die Regel allerdings noch in Kapitel 6, Abschnitt 3, Absatz 6.

Das ist gängige Praxis seitdem es C gibt, und ich sehe keinen technischen Grund warum das ein Problem sein soll, solange das Alignment passt.

Die Regel dient nicht nur dazu, Alignment-Probleme zu vermeiden, sondern dem Compiler Annahmen zu möglichem bzw. unmöglichem Aliasing zu erlauben, um damit den Code besser optimieren zu können.

#7924069
Lesenswert?

Udo K. schrieb:

Das ist gängige Praxis seitdem es C gibt, und ich sehe keinen technischen Grund warum das ein Problem sein soll, solange das Alignment passt. Der Hack mit memcpy() funktioniert zwar, setzt aber einen modernen Compiler mit aufgedrehten Optimierungen voraus und funktioniert in einer Freestanding Umgebung nicht. Der ASM Code ist jedenfalls identisch.

Jede der Lösungen ist ein Hack. memcpy() ist davon meiner Ansicht nach noch der sauberste. So modern muss der Compiler dafür übrigens auch nicht sein. gcc macht das schon seit mindesten 25 Jahren.

#7924094
Lesenswert?

Wenn das mein Code für irgendein Projekt zu Hause wäre, würde ich wohl den hack mit memcpy nehmen und einen Kommentar dazu schreiben, damit ich mich in 3 Jahren noch daran erinnere, nicht blind eine der beiden Datenstrukturen zu ändern zu können, ohne die Zuweisung zu überarbeiten.

Auf der Arbeit, in einem Projekt, das andere warten müssen, wäre mir das zu peinlich. Da würde ich

ds.a=da[0]; ds.b=da[1]; ds.c=da[2];

schreiben. Und in beiden Fällen würde ich noch ein assert(sizeof(ds)==sizeof(da)) mit einem passenden Kommentar hinzufügen. Code hält manchmal länger als man glaubt, da freut sich Dein Nachfolger.

Beitrag #7924105 wurde vom Autor gelöscht.
#7924114
Lesenswert?

Geht es Dir

  • um die Kopie (Zuweisung)? Dann ist memcpy gut genug.
  • um einen cast? Das geht über Ptr. Ist nur „frimelei“, probiers aus und frag erneut mit Deiner Lösung
  • um die Zulässigkeit des casts (oder memcpy oder per Union)? Damit kannst Du Bücher füllen. Viele tun es einfach erfolgreich. Und viele fallen irgendwann drauf rein
: Bearbeitet durch User
OP #7924131
Lesenswert?

Mir gehts um den Cast.

Das dahinter liegende praktische Problem ist, dass ich eine komplexe Struktur mit Sollwerten füllen möchte, die ich in einem const uint8_t Array abgelegt habe. Bislang habe ich dazu zwei Möglichkeiten in Verwendung, eine union, die beide Datentypen enthält, und memcpy. Allerdings interessiert mich wirklich, wie man es mit einem type cast macht, da habe ich noch Lernbedarf.

#7924135
Lesenswert?

Michael schrieb:

Das dahinter liegende praktische Problem ist, dass ich eine komplexe Struktur mit Sollwerten füllen möchte, die ich in einem const uint8_t Array abgelegt habe.

Das Problem alignment wurde ja nun schon öfter genannt. Das kann dir deinen Plan verhageln, wenn du nicht genau weißt, was du da tust.

Wie man es mit einem cast richtig macht, hat Yalu schon beschrieben.

Yalu X. schrieb:

Der C-Standard verbietet es, auf das int-Array über den L-Value des nichtkompatiblen Typs (struct dat_struct) zuzugreifen. An dieser Regel ändert auch die mehrfache Casterei nichts (Kapitel 6, Abschnitt 5, Absatz 7). Für memcpy gilt diese Einschränkung nicht.

In Kurzform: gar nicht.

Oliver

#7924141
Lesenswert?

Moin,

Nach mal drueberschlafen find ich auch die Loesung von timgabinski am vernuenftigsten. Bei einer union mit 2 verschiedenen Datentypen ist's iirc eigentlich auch schon eine Suende, wenn man da den einen reinschreibt und den anderen ausliest. casten mit Gewalt geht auch, ist aber unschoen:

1
printf("%d\n",((struct dat_struct*)da)->b);

Das Problem ist da immer, dass du dich nicht verlassen kannst, wie der compiler den struct im Speicher anlegt. Kann auch sein, dass der padding bytes zwengs besserem alignment einbaut, wenn dein struct verschieden grosse Elemente hat. Dann kommst du in die Hoelle...Und wenn du versuchst, ihn mittels irgendwelcher wilden #pragma davon abzubringen, erst recht.

Gruss WK

#7924148
Lesenswert?

Michael schrieb:

Das dahinter liegende praktische Problem ist, dass ich eine komplexe Struktur mit Sollwerten füllen möchte, die ich in einem const uint8_t Array abgelegt habe.

Dann mach das händisch wie im Beitrag "Re: C typecast array nach struct" beschrieben. Ist zwar mehr Tipparbeit und braucht auch zur Ausführungszeit ein paar Takte mehr. Aber du macht es ja wohl nur einmal.

#7924149
Lesenswert?

Tim schrieb:

Auf der Arbeit, in einem Projekt, das andere warten müssen, wäre mir das zu peinlich. Da würde ich

ds.a=da[0]; ds.b=da[1]; ds.c=da[2];

schreiben.

Der ganz saubere Weg wäre, auch in die einzelnen member per memcpy zu kopieren.

Aus cppreference „ Where strict aliasing prohibits examining the same memory as values of two different types, memcpy may be used to convert the values.“

Oliver

#7924159
Lesenswert?

Dergute W. schrieb:

Bei einer union mit 2 verschiedenen Datentypen ist's iirc eigentlich auch schon eine Suende, wenn man da den einen reinschreibt und den anderen ausliest.

Abgesehen davon ist das der umständlichste Weg, weil man dann extra für die Konvertierung erstmal noch einen neuen Datentyp definieren muss, dann erst den Input rein und den Output wieder rauskopieren.

Das Problem ist da immer, dass du dich nicht verlassen kannst, wie der compiler den struct im Speicher anlegt. Kann auch sein, dass der padding bytes zwengs besserem alignment einbaut, wenn dein struct verschieden grosse Elemente hat.

Dann sprechen wir aber eh von einer anderen Sache, weil dann die Elemente des Arrays ja auch nicht mehr alle vom selben Typ sein können wie die der Struct.

Oliver S. schrieb:

Tim schrieb:

Auf der Arbeit, in einem Projekt, das andere warten müssen, wäre mir das zu peinlich. Da würde ich

ds.a=da[0]; ds.b=da[1]; ds.c=da[2];

schreiben.

Der ganz saubere Weg wäre, auch in die einzelnen member per memcpy zu kopieren.

Wenn ich den Wert eines int in einen anderen kopieren will, warum sollte da memcpy besser sein als eine einfache Zuweisung?

Moderator #7924162
Lesenswert?

Michael schrieb:

Das dahinter liegende praktische Problem ist, dass ich eine komplexe Struktur mit Sollwerten füllen möchte, die ich in einem const uint8_t Array abgelegt habe.

Wieso legst du die Sollwerte in einem const uint8_t Array ab und nicht gleich im passenden Datentyp, nämlich als struct dat_struct? Durch das Vermischen von untereinander inkompatiblen Datentypen machst du dir das Leben doch nur unnötig schwer.

#7924180
Lesenswert?

Axel S. schrieb:

Yalu X. schrieb:

Wieso legst du die Sollwerte in einem const uint8_t Array ab und nicht gleich im passenden Datentyp, nämlich als struct dat_struct?

Wenn ich raten müßte: weil sie so im EEPROM liegen. Und das EEPROM ist ja schließlich ein Array von uint8_t !!1zwölf

Dann könnte es auch mit dem cast auf "(uint8_t*)" funktionieren, wenn man einen Compiler hat, der sowas wie "#pragma(pack,1)" unterstützt. Wenn er das nicht kann, muss man das Padding etweder händisch erledigen, oder man hat Leerstellen im EEPROM.

#7924184
Lesenswert?

Rahul D. schrieb:

Axel S. schrieb:

Yalu X. schrieb:

Wieso legst du die Sollwerte in einem const uint8_t Array ab und nicht gleich im passenden Datentyp, nämlich als struct dat_struct?

Wenn ich raten müßte: weil sie so im EEPROM liegen.

Dann könnte es auch mit dem cast auf "(uint8_t*)" funktionieren, wenn man einen Compiler hat, der sowas wie "#pragma(pack,1)" unterstützt.

Oder aber er verläßt sich auf das, was auch mit dem dümmsten Compiler schon geht. Er kopiert die struct Byte für Byte. Und die Länge ermittelt er mit sizeof(). Dann klappt das auch mit Padding und Pipapo.

Daß es nicht portabel ist, weil implemetation defined behavior, kann ihm dabei egal sein. Er liest und schreibt ja auf der gleichen Architektur.

#7924192
Lesenswert?

Michael schrieb:

Wie mache ich in C einen Typecast, mit dem ich ein Array in eine Struktur umwandle

Gar nicht, denn je nach Plattform, Compiler und Compileroptionen (Optimierungen, Alignment, Debug) kommt eine andere Bytebelegung des Arrays bei raus, sogar ggf. eine andere Grösse char x[sizeof(struct s)]. Man schreibt schön

1
ds.a= da[0];
2
ds.b= da[1];
3
ds.c= da[2];

und lässt das den compiler ggf. optimieren.

Obwohl deine gleichen Datentxpen wohl glatt gehen

Aber schon mit

1
struct dat_struct
2
  {
3
  int a;
4
  unsigned char b; 
5
  int c;
6
  };

kann das derb in die Hose gehen.

(Firma: Gast) #7924211
Lesenswert?

Michael schrieb:

Kann ich Strukturen in einem Binärfile abspeichern und wieder einlesen, ohne dass ich Probleme mit Alignment, Cell Padding o. ä. bekomme? Das scheint zu funktionieren, aber nach dem oben gesagten bin ich mir nicht mehr sicher, ob das nur Zufall ist.

Wenn Schreiben und lesen mit dem gleichen µC passiert. Lese und Schreibe Programm mit dem gleichen Compiler(Version) übersetzt wurden. Mit den gleichen Compilereinstellungen.

Sonst? Vielleicht, vielleicht auch nicht.

Gast #7924242
Lesenswert?

Yalu X. schrieb:

Auf jeden von ISO bzw. ANSI. In C90 stand die Regel allerdings noch in Kapitel 6, Abschnitt 3, Absatz 6.

Sorry, ich bin zu blöd um das zu finden. In meiner ANSI/ISO 9899-1990 Kopie steht unter 6.3.6 "Additive Operators" nichts dazu - oder mein Hirn denkt nicht in den Wegen des Standards.

Udo K. schrieb:

memcpy(&ds, da, sizeof(da));

Das C nicht einfach ist, sieht man auch am memcpy:
sizeof(da) ist im folgenden Beispiel in 50% der Fälle falsch. Merke: mache nie ein typedef mit einem Array fixer Grösse - nimm gleich ein typedef struct { int dat_array[3]; } dat_array_t;

1
typedef int dat_array[3];
2

3
void foo(dat_array da)
4
{
5
    // Surprise
6
    printf("%d\n", (int)sizeof(da));
7
}
8

9
int main()
10
{
11
    dat_array da;
12

13
    printf("%d\n", (int)sizeof(da));
14
    foo(da);
15
}

Rolf M. schrieb:

Jede der Lösungen ist ein Hack. memcpy() ist davon meiner Ansicht nach noch der sauberste. So modern muss der Compiler dafür übrigens auch nicht sein. gcc macht das schon seit mindesten 25 Jahren.

Hängt halt davon ab wo und mit welchen Tools du arbeitest.
Ich verwende gerne eine eigene memcpy Funktion, und damit geht der Hack schon nicht mehr. Ich will auch nicht, dass der Compiler nach Gutdünken Funktionen einbaut oder entfernt.
Und der MS Compiler vom Driver Development Kit ist erst 15 Jahre alt, kann aber den memcpy Hack nur mit voll aufgedrehten Optimierungen - was schlecht ist wenn ich kleine Programme möchte.
Ich weiss Linuxer verwenden überall und immer -O3 :-).
Wenn du in einer Freestanding Umgebung arbeitest, dann geht der memcpy Hack auch nicht.
Wenn du aber mit einem OS arbeitest, das Structs und Arrays in unterschiedlichen Segmenten abspeichert, dann geht nur memcpy.

Nemopuk schrieb:

Alles was nach C99 kam, ist hoch modern, nicht original, nicht vertrauenswürdig und daher in ersthaften Projekten "verboten" - könnte man meinen.

Zumindest lassen sich 25 Jahre alte C99 Programme auch heute noch relativ schmerzfrei übersetzen. Probiere das mal mit modernem C++ Zeugs, da kannst du froh sein wenn das nach 5 Jahren noch kompiliert. Oft brauchst du dazu nicht nur die richtige Compilerversion sondern auch noch die passende Version der Build Umgebung. Viel Spaß dabei.

Tim schrieb:

Auf der Arbeit, in einem Projekt, das andere warten müssen, wäre mir das zu peinlich. Da würde ich

ds.a=da[0]; ds.b=da[1]; ds.c=da[2];

Das ist keine gute Lösung. In 5 Jahren gibt es ziemlich sicher neue Datenelemente, die du damit nicht mit kopierst.

Das Übel liegt wohl schon darin, nicht das der TE nicht EINE EINZIGE Datenstruktur verwendet.

Michael schrieb:

Kann ich Strukturen in einem Binärfile abspeichern und wieder einlesen, ohne dass ich Probleme mit Alignment, Cell Padding o. ä. bekomme? Das scheint zu funktionieren, aber nach dem oben gesagten bin ich mir nicht mehr sicher, ob das nur Zufall ist.

Normalerweise geht das problemlos, auch mit unterschiedlichen Compilern und Optimierungen auf einer Plattform. Die Compiler einer Plattform sind meist binär kompatibel zum Platzhirsch. Es gibt auch spezielle Compileroptionen, die das Alignment und Padding festlegen.

Moderator #7924249
Lesenswert?

Udo K. schrieb:

Yalu X. schrieb:

Auf jeden von ISO bzw. ANSI. In C90 stand die Regel allerdings noch in Kapitel 6, Abschnitt 3, Absatz 6.

Sorry, ich bin zu blöd um das zu finden.

Nein, ich hätte mich etwas klarer ausdrücken müssen.

In meiner ANSI/ISO 9899-1990 Kopie steht unter 6.3.6 "Additive Operators" nichts dazu - oder mein Hirn denkt nicht in den Wegen des Standards.

Seite 38, Abschnitt 6.3 (noch vor 6.3.1), der entsprechende Absatz beginnt mit

"An object shall have its stored value accessed only by an lvalue that has one of the follwing types: ..."

#7924272
Lesenswert?

Oliver S. schrieb:

Rolf M. schrieb:

Wenn ich den Wert eines int in einen anderen kopieren will, warum sollte da memcpy besser sein als eine einfache Zuweisung?

Hier gehts um ein uint8_t Array, welches zu größere Datentypen zusammengesetzt wird.

Das kam aber auch gerade erst heraus. Ursprünglich ging es um ein Array aus int, das in eine Struktur mit lauter int-Feldern kopiert werden sollte. Jetzt geht es auf einmal um was komplett anderes.

Michael schrieb:

Kann ich Strukturen in einem Binärfile abspeichern und wieder einlesen, ohne dass ich Probleme mit Alignment, Cell Padding o. ä. bekomme? Das scheint zu funktionieren, aber nach dem oben gesagten bin ich mir nicht mehr sicher, ob das nur Zufall ist.

Du solltest dich mal entscheiden, was du eigentlich willst. Zuerst war es ein Array aus int, das du in int-Elemente einer Struktur kopieren wolltest, aber unbedingt mit einem Cast. Als nächstes war es ein Array aus Rohbytes, das in deinem Code initialisiert wurde. Und jetzt liest du die Daten aus einem File. Wäre schön, wenn du gleich von Anfang an dein Ziel nennen würdest statt es nach der Salamitaktik Scheibchen für Scheibchen zu offenbaren.

Udo K. schrieb:

Ich verwende gerne eine eigene memcpy Funktion, und damit geht der Hack schon nicht mehr.

Warum? In der Regel ist diese Funktion stark optimiert, und da ist es oft - genau wie in diesem Fall - kontraproduktiv, die selbst zu schreiben.

Ich will auch nicht, dass der Compiler nach Gutdünken Funktionen einbaut oder entfernt.

Wo ist in dem Fall das Problem? Am Ende ist das auch nicht viel anders als ein Funktions-inlining, nur mit noch mehr Optimierungspotenzial. Oder bist du dann auch grundsätzlich gegen inlining, weil das auch "nach Gutdünken" Funktionen entfernt?

Und der MS Compiler vom Driver Development Kit ist erst 15 Jahre alt, kann aber den memcpy Hack nur mit voll aufgedrehten Optimierungen - was schlecht ist wenn ich kleine Programme möchte.

Das ist kein Hack, sondern eine Optimierung.

Ich weiss Linuxer verwenden überall und immer -O3 :-). Wenn du in einer Freestanding Umgebung arbeitest, dann geht der memcpy Hack auch nicht.

Das ist richtig. Ich nutze keine Freestanding-Implementationen. Wäre das so, dann würde ich es dort entsprechend anders machen. Wobei man zumindest bei gcc auch im Freestanding-Mode noch Zugriff auf diese eingebauten Funktionen hat, wenn man sie nutzen möchte.

Wenn du aber mit einem OS arbeitest, das Structs und Arrays in unterschiedlichen Segmenten abspeichert, dann geht nur memcpy.

Ein solche OS wäre mir nicht bekannt.

Zumindest lassen sich 25 Jahre alte C99 Programme auch heute noch relativ schmerzfrei übersetzen. Probiere das mal mit modernem C++ Zeugs, da kannst du froh sein wenn das nach 5 Jahren noch kompiliert.

Warum? Die sind auch Backwards-Kompatibel. Wenn was nicht mehr compiliert, ist es fast immer so, dass das Programm schon von vorne herein falsch war und der Compiler das nur nicht bemerkt hat.

Oft brauchst du dazu nicht nur die richtige Compilerversion sondern auch noch die passende Version der Build Umgebung. Viel Spaß dabei.

Die Build-Umgebung hat aber mit der Sprache nichts zu tun. Wenn dein 25 Jahre altes C-Programm irgendwelche Sonderlösungen in den Autotools verwendet, kann das genauso Probleme machen.

Tim schrieb:

Auf der Arbeit, in einem Projekt, das andere warten müssen, wäre mir das zu peinlich. Da würde ich

ds.a=da[0]; ds.b=da[1]; ds.c=da[2];

Das ist keine gute Lösung. In 5 Jahren gibt es ziemlich sicher neue Datenelemente, die du damit nicht mit kopierst.

Nun, es muss eben klar sein, dass beim Hinzufügen neuer Felder natürlich auch dafür gesorgt werden muss, dass die ordnungsgemäß befüllt werden. Und gerade bei der (De)Serialisierung dann Anpassungen gemacht werden müssen.

Michael schrieb:

Kann ich Strukturen in einem Binärfile abspeichern und wieder einlesen, ohne dass ich Probleme mit Alignment, Cell Padding o. ä. bekomme? Das scheint zu funktionieren, aber nach dem oben gesagten bin ich mir nicht mehr sicher, ob das nur Zufall ist.

Normalerweise geht das problemlos, auch mit unterschiedlichen Compilern und Optimierungen auf einer Plattform. Die Compiler einer Plattform sind meist binär kompatibel zum Platzhirsch. Es gibt auch spezielle Compileroptionen, die das Alignment und Padding festlegen.

Am besten verwendet man die [u]nitX_t-Datentypen und baut seine Strukturen so auf, dass sie auf allen gängigen Plattformen automatisch das richtige Alignment haben, garniert mit ein paar static_asserts, die sicherstellen, dass man merkt, wenn's irgendwo doch Abweichungen geben sollte. Damit hat man die meisten Stolperstellen schon erledigt. Allerdings fehlt natürlich noch die Edianness.

#7924286
Lesenswert?

Udo K. schrieb:

Das C nicht einfach ist, sieht man auch am memcpy: sizeof(da) ist im folgenden Beispiel in 50% der Fälle falsch.

Diese Aussage stimmt so nicht. (Dein Code suggests, dass Arrays by-value übergeben werden könnten.)

Rolf M. schrieb:

Jede der Lösungen ist ein Hack.

Hängt halt davon ab wo und mit welchen Tools du arbeitest.

Das memcpy ist (wie oben beschrieben) als Hack "besser" als das struct assignment (via type cast), weil es Aliasing berücksichtigt.

Tim schrieb:

ds.a=da[0]; ds.b=da[1]; ds.c=da[2];

Das ist keine gute Lösung.

Das ist die einzige korrekte Lösung wenn man dem C Standard folgt.

btw: Die ersten Jahre in denen ich C programmiert hatte, habe ich C mehr oder weniger als "komfortablen" Assembler betrachtet. Das gab ein böses Erwachen. Da C an vielen Stellen optimieren könnte, muss man tatsächlich die (abstrakte) C Spezifikation verinnerlichen.

#7924291
Lesenswert?

Michael schrieb:

Ich lege du die Sollwerte in einem const uint8_t Array ab, weil ich nicht schreiben kann ds = 0x112233aabbcc;

Da gibt es 2 getrennte Aufgaben:

  • A) das Füllen der Strukturelemente (ds.a = ...)
  • B) das Zusammensetzen mehrerer Bytes zu einem Skalar

Nehmen wir "ds" aus Deinem Beispiel und das EEProm als da[1000].

Zu A: Nehmen wir an, jede Struktur wird nur mit einem uint8_t gefüllt. Dann geht z.B.:

1
uint8_t *p;
2
   p = &da[0]; /* das gleiche wie "p=da;"
3

4
   ds.a = *p; p++; /* Inhalt des pointers und pointer incrementieren*/
5
   ds.b = *p++;    /* oder dessen verkürzte Schreibweise */
6
   ds.c = *p++;

alternativ

1
struct dat_struct ds={da[0], da[1], da[2]};

Zu B: Wenn ds.a als 2 Byte int gefüllt werden soll, brauchst Du etwas wie

1
uint8_t *p=da[0];
2

3
   ds.a = p[0] + p[1]<<8; p+=2;

Abhängig davon, ob Du little- oder big-Endian hast (low-Byte zuerst oder Highbyte. Versuche nicht, hier *p++ mehrfach in einer Anweisung zu verwenden. Schreibe Dir Funktionen, die entsprechende Anzahl an Bytes in den gewünschten Typ verwandeln. Und übergib die Adresse des ersten Bytes. Dann sieht das z.B. so aus:

1
uint16_t bToInt16Little(uint8_t b[2]) /* die meisten nehmen hier einen pointer statt eines Array. ist faktisch das gleiche */
2
{
3
uint16_t ret = b[0]+b[1]<<8; /* little Endian */
4
   return ret;
5
}
6
   ds.a = bToInt16Little(da[0]);
7
   ds.b = tToUint32Little(da[2]);
8
   ds.c = bToUint16Little(da[6]);

(aber nicht so geschwätige Funktionsnamen nehmen! Deine Beispiele sind bezüglich Namensgebung imho sehr gut und zeugen von Erfahrung. )

: Bearbeitet durch User
Moderator #7924308
Lesenswert?

Yalu X. schrieb:

Man kann aber bspw. schreiben: struct dat_struct ds = { 123, // a 456, // b 789 // c };

Hilft das nicht weiter?

Michael schrieb:

Es hilft nur weiter, wenn die Struktur so einfach wie in meinem Beispiel ist.

Was wäre ein komplizierteres Beispiel, wo das nicht mehr funktioniert? Auch Arrays und verschachtelte Datentypen können problemlos auf diese Weise initialisiert werden,

#7924455
Lesenswert?

Yalu X. schrieb:

Michael schrieb:

Ich lege du die Sollwerte in einem const uint8_t Array ab, weil ich nicht schreiben kann ds = 0x112233aabbcc;

Man kann aber bspw. schreiben: struct dat_struct ds = { 123, // a 456, // b 789 // c };

Hilft das nicht weiter?

Man kann doch sogar die Namen der Komponenten verwenden (so ähnlich wie "with" in Pascal ;))

#7924764
Lesenswert?

Michael schrieb:

mit dem ich ein Array in eine Struktur umwandle?

Du kannst eine Union definieren, die aus dem Array und der Struct besteht. Rein formal ist es nicht erlaubt, beides anzusprechen, aber es funktioniert trotzdem. Es wird sogar höchst offiziell in Headerfiles verwendet, um einzelne Konfigurationsbits anzusprechen, die in Bytes zusammen gefaßt sind.

Hier mal ein Beispiel für eine Union für den AVR-GCC:

1
union b_w {
2
  struct{
3
    uint8_t l;
4
    uint8_t h;
5
  }b;
6
  uint16_t w;
7
};

Die Zuordnung ist natürlich Target und Compiler abhängig, d.h. man muß sie prüfen.

: Bearbeitet durch User
#7925074
Lesenswert?

Wie wäres damit, packe das Array in das Struct, und definiere dir ein paar Enums oder Defines für die Feldnamen.

1
struct dat_struct {
2
  int v[3];
3
};
4

5
#define DAT_a v[0]
6
#define DAT_b v[1]
7
#define DAT_c v[2]
8

9
int main(){
10
  struct dat_struct data = {
11
    .DAT_a = 42,
12
    .DAT_b = 666,
13
    .DAT_c = 123,
14
  };
15
  puts("data: ");
16
  for(int i=0; i<3; i++)
17
    printf(" - %d\n", data.v[i]);
18
  puts("a: %d\n", data.DAT_a);
19
  puts("b: %d\n", data.DAT_b);
20
  puts("c: %d\n", data.DAT_c);
21
}
#7925262
Lesenswert?

Bruno V. schrieb:

in ein Feld der Union schreiben und den Inhalt per anderem Feld herauslesen?

Ich wollte oben eigentlich diesen Link hier setzen: https://stackoverflow.com/questions/25664848/is-it-allowed-to-use-unions-for-type-punning-and-if-not-why

Dort werden u.a. auch die entsprechenden Stellen aus dem C Standard referenziert.

Es läuft auf folgendes hinaus: "In contrast to C++, in C, there is no concept of active member and it's perfectly fine to access the single stored value through an expression of an incompatible type."

Allerdings gibt es einige weitere kontroverse Diskussionen zum Thema... Ich zitiere mal: <This has always been "iffy"> -.- https://stackoverflow.com/questions/11639947/is-type-punning-through-a-union-unspecified-in-c99-and-has-it-become-specified (Bei stackoverflow gibt es noch mehr interessante Threads.)

Alter Code machte davon Gebrauch (bspw. in BSD). Dort lief das aber wohl noch unter implementation-defined behavior.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren