Wie mache ich in C einen Typecast, mit dem ich ein Array in eine Struktur umwandle?
1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
|
Anzeige
|
C typecast array nach structWie mache ich in C einen Typecast, mit dem ich ein Array in eine Struktur umwandle?
Moin, Nach meinem dafuerhalten: 1.) Am besten garnicht. ... ... 10001.) immernoch nicht. 10002.) memcpy(&ds, da, 3 * sizeof(int)); 10003.) irgendwas mit ner union, die die beiden verschiedenen Typen zusammenmauschelt. Gruss WK Man könnte einen Umweg über void* versuchen. Wenn der Cast irgendwie vermeidbar ist, dann vermeide ihn. 10002 verwende ich auch. Eine Notlösung, die überflüssige Laufzeit generiert, obwohl doch zum Zeitpunkt des Kompilierens bereits alles bekannt ist. Wenn es unbeding sein muss: Mach ds zu einem pointer und caste zumindest ein mal über char *. Ungefähr so:
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.
Nicht auf vernünftigen Compilern. Die wissen was memcpy macht und optimieren das entsprechend.
Gast
#7923834
ds = (struct dat_struct)da; memcpy mit konstanter Größe wird bei modernen Compilern mit -O2 auch wegoptimiert. Was willst du tun? - nicht deine Lösung mit strukt und Typcast. Vielleicht ein Union mit strukt und array? Sg
Geht alles. Nur warum? Oliver :
Bearbeitet durch User
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:
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:
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
Dafür liest man halt über das Ende des Quellobjekts hinaus, falls es versehentlich kleiner ist. Das ist auch nicht so viel besser.
Warum nicht gleich
Gast
#7923945
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.
Nicht viel, aber vielleicht ein wenig, weil Lesen normalerweise zerstörungsfrei ist.
Ja, natürlich. Ich habe mich durch das
im zitierten Programmcode blenden lassen. Auch dort hätte man einfacher
schreiben können.
Auf jeden von ISO bzw. ANSI. In C90 stand die Regel allerdings noch in Kapitel 6, Abschnitt 3, Absatz 6.
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.
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.
Alles was nach C99 kam, ist hoch modern, nicht original, nicht vertrauenswürdig und daher in ersthaften Projekten "verboten" - könnte man meinen. Ich stelle mir vor, wie diese Leute auf dem Trödel verzweifelt nach Schellack Platten für ihr Grammophon suchen. 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.
Geht es Dir
:
Bearbeitet durch User
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.
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.
In Kurzform: gar nicht. Oliver 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:
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 Ok, ich werde es mir zu Herzen nehmen und memcpy verwenden - in der Hoffnung, dadurch eventuelle Probleme mit Alignment und Cell Padding zu vermeiden. Moin,
Diese Hoffnung kannst du getrost fahren lassen... Dagegen hilft nur die timgabinski-Methode. Gruss WK
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.
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
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.
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.
Wenn ich den Wert eines int in einen anderen kopieren will, warum sollte da memcpy besser sein als eine einfache Zuweisung?
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.
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
Warum denn ein "const"-Array? Dann kann man auch gleich eine struct-Variable initialisieren. Ja, jetzt kommen "wir" zur Wurzel des Problems! Anstelle an den Symptomen der "Krankheit" rum zu basteln.
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.
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.
Hier gehts um ein uint8_t Array, welches zu größere Datentypen zusammengesetzt wird. Oliver Ich lege du die Sollwerte in einem const uint8_t Array ab, weil ich nicht schreiben kann
Dass du nicht schreiben kannst, sieht man an dem Satz. SCNR Wie wäre es, wenn du dich mal etwas weniger abstrakt äußern würdest? Zur Programmierumgebung hast du doch auch noch nichts geschrieben. Ist das alles so geheim?
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
und lässt das den compiler ggf. optimieren. Obwohl deine gleichen Datentxpen wohl glatt gehen Aber schon mit
kann das derb in die Hose gehen. Vielleicht kann der TO auch C++, obwohl er nach C gefragt hat. Denn damit lässt es sich elegant lösen. Man könnte von Arduino abgucken: https://docs.arduino.cc/learn/programming/eeprom-guide/ https://github.com/arduino/ArduinoCore-avr/blob/master/libraries/EEPROM/src/EEPROM.h
Man kann aber bspw. schreiben:
Hilft das nicht weiter? Es hilft nur weiter, wenn die Struktur so einfach wie in meinem Beispiel ist.
Sehen wir denn die original Daten/Arrays/Strukturen, oder nichts sagende Fantasiegebilde? 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. Es ist schon ein Wunder, dass meine Programme überhaupt funktionieren, zumindest so ein bischen...
Nein. Liest du dieselbe Struktur mit demselben Programm, geht es noch, aber wenn das Programm mit anderen Compileroptionen neu kompiliert wird oder gar auf einer anderen Plattform läuft, geht das in die Hose. Ok, vielen Dank an alle, ich habe viel gelernt!
Gast
#7924242
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.
Das C nicht einfach ist, sieht man auch am memcpy:
Hängt halt davon ab wo und mit welchen Tools du arbeitest.
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.
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.
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.
Nein, ich hätte mich etwas klarer ausdrücken müssen.
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: ..."
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.
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.
Warum? In der Regel ist diese Funktion stark optimiert, und da ist es oft - genau wie in diesem Fall - kontraproduktiv, die selbst zu schreiben.
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?
Das ist kein Hack, sondern eine Optimierung.
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.
Ein solche OS wäre mir nicht bekannt.
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.
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.
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.
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.
Diese Aussage stimmt so nicht. (Dein Code suggests, dass Arrays by-value übergeben werden könnten.)
Das memcpy ist (wie oben beschrieben) als Hack "besser" als das struct assignment (via type cast), weil es Aliasing berücksichtigt.
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.
Da gibt es 2 getrennte Aufgaben:
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.:
alternativ
Zu B: Wenn ds.a als 2 Byte int gefüllt werden soll, brauchst Du etwas wie
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:
(aber nicht so geschwätige Funktionsnamen nehmen! Deine Beispiele sind bezüglich Namensgebung imho sehr gut und zeugen von Erfahrung. ) :
Bearbeitet durch User
Was wäre ein komplizierteres Beispiel, wo das nicht mehr funktioniert? Auch Arrays und verschachtelte Datentypen können problemlos auf diese Weise initialisiert werden,
Man kann doch sogar die Namen der Komponenten verwenden (so ähnlich wie "with" in Pascal ;))
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:
Die Zuordnung ist natürlich Target und Compiler abhängig, d.h. man muß sie prüfen. :
Bearbeitet durch User
Naja, eben nur genau dann, wenn die anderen genannten Hacks auch funktionieren.
Es ist in C99 erlaubt; in C++ nicht. [type punning] https://stackoverflow.com/questions/17789928/whats-a-proper-way-of-type-punning-a-float-to-an-int-and-vice-versa (Eine von vielen Diskussionen zum Thema.) :
Bearbeitet durch User
Wie wäres damit, packe das Array in das Struct, und definiere dir ein paar Enums oder Defines für die Feldnamen.
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 schreibenBitte melde dich an, um einen Beitrag zu schreiben. |
Anzeige
|