Machen kannst du in C (zu) viel. Ich denke es kompiliert und in deinem
einfachen Fall könnte es sogar gehen. Was machst du aber nun wenn du ne
zweite Zahl hast?
Schön ist es daher nicht.
Wenn du schon unbedingt nen void-Pointer nutzen musst caste es wieder
auf den richtigen Typ zurück.
Ja, theoretisch ists erlaubt.
Nein, es ist nicht sauber.
Hat es einen Grund warum du deinen Zeiger zu void* konvertierst?
Und warum kann die Rückkonvertierung nicht zu Struktur sein?
Heute grad in Rätsellaune? Hab ich auch noch eins: Ändert sich etwas,
wenn man im obigen Beispiel uint8_t zu uint16_t verändert? Also am
Prinzip der Frage, nicht am Wertebereich.
Linda schrieb:> Hallo,> ich habe eine kleine Frage zu programmiersparech C:> Ist es in C erlaubt bzw. sauber so etwas zu schreiben?>
1
>typedefstruct
2
>{
3
>uint8_tzahl;
4
>// Noch ein paar Member
5
>}Struktur;
6
>
7
>
8
>Strukturtest;
9
>void*test_ptr=&test;
10
>
>> Dann würde ich mit>
1
*((uint8_t*)test_ptr)
> auf zahl zugreifen.
Man kann das auch etwas ausführlicher kommentieren:
Das wird funktionieren, aber nur für den besonderen Fall, des Zugriffs
auf das erste Element der Struktur. Es gibt noch ein paar
Nebenbedingungen, wie die Frage ob das erste Element wirklich an der
selben Adresse liegt, wie die Struktur insgesamt. Das ist nämlich im
Sprachstandard nicht zugesichert. (Siehe auch pragma pack oder was es
das sonst an netten gcc-Schmankerln gibt - ich verliere da so langsam
den Überblick).
Sowie, Du auf eines der Nachfolgeelemente zugreifen willst, geht das
nicht mehr so.
Sauberer insgesamt ist das hier:
1
typedefstruct
2
{
3
uint8_tzahl;
4
// Noch ein paar Member
5
}Struktur;
6
7
8
Strukturtest;
9
Struktur*test_ptr=&test;
10
11
uint8_tx=test_ptr->zahl
Solche void-Tricks kann man machen und auch relativ sauber.
Dann aber irgendwie so:
1
void*test_void_ptr=(void*)&test;
2
3
uint8_ty=((Struktur*)test_void_ptr)->zahl;
Sieht zwar geschwätzig aus, ist aber ein Idiom, dass jeder versteht.
Und wie gesagt: Immer auf "packing" aufpassen, sonst gibts Beulen. :-)
Klaus schrieb:> Es gibt noch ein paar> Nebenbedingungen, wie die Frage ob das erste Element wirklich an der> selben Adresse liegt, wie die Struktur insgesamt. Das ist nämlich im> Sprachstandard nicht zugesichert. (Siehe auch pragma pack oder was es> das sonst an netten gcc-Schmankerln gibt - ich verliere da so langsam> den Überblick).
Doch genau das ist zugesagt.
6.7.2.1 Absatz 13 des C99 Standard
> A pointer to a structure object, suitably converted, points to> its initial member (or if that member is a bit-field, then to> the unit in which it resides)
Prinzipiell dürfen Implementationen zwar ziemlich viel alignen, aber am
Anfang alignen ist untersagt.
Ano schrieb:> Klaus schrieb:>>... Das ist nämlich im>> Sprachstandard nicht zugesichert.> Doch genau das ist zugesagt.> 6.7.2.1 Absatz 13 des C99 Standard>> A pointer to a structure object, suitably converted, points to>> its initial member (or if that member is a bit-field, then to>> the unit in which it resides)
Ah. Jetzt wo Du es sagst :-) Hatte ich vergessen. Vielen Dank.
@Linda
Falls das fraglich ist:
Meine Ansicht, dass es mit einem Struktur * "offensichtlicher" ist,
halte ich aufrecht.
Ich hatte ja auch geschrieben, dass es auf dem anderen Weg durchaus
geht. Allerdings war, wie Ano richtig festgestellt hat, meine
Einschränkung falsch.
Damit geht es so grundsätzlich immer. "Schön" finde ich es trotzdem
nicht und notwendig ist es erst recht nicht.
Harold schrieb:> xzq = *((Struktur*)test_ptr).zahl;> oder> xzq = ((Struktur*)test_ptr)->zahl;
Das obere geht nicht und das untere kenne ich schon. Mit geht es um
genau um den oben genannten Ausdruck.
Ano schrieb:> Und warum kann die Rückkonvertierung nicht zu Struktur sein?
Weil ich zu der Zeit noch nicht weiß was sich hinter dem void-Pointer
verbirgt.
Tom schrieb:> Bis in ein paar Wochen jemand ein Structmember vor zahl einfügt.
Wenn jemand an meinen Code rumpfuscht ohne zu wissen was er tut
geschieht es ihm eh recht wenn dann nichts mehr funktioniert.
Ano schrieb:> Prinzipiell dürfen Implementationen zwar ziemlich viel alignen, aber am> Anfang alignen ist untersagt.
Das ist mal eine gute Nachricht. Danke :-)
Jetzt hab ich es noch kurz durch den GCC mit -pedantic gejagt. Die
einzige Warnung war der // Kommentar. Sollte also passen.
Ein paar Fragen hätt ich noch:
Mit einem Enum an Stelle der uint8_t würde das auch klappen?
Was genau ist so eine Enum eigentlich, wie viele bits ist es breit, sind
alle Enums gleich breit?
Linda schrieb:> Wenn jemand an meinen Code rumpfuscht ohne zu wissen was er tut
Du baust da eine dämliche und völlig unnötige Falle ein. Aber es soll
sich jeder für schlauer halten und pfuschen, wie er meint...
Was spricht gegen Klaus' Lösung oder ein
void *foo = &test.zahl;
?
> Was spricht gegen Klaus' Lösung oder ein> void *foo = &test.zahl;> ?
Wenn es so einfach wäre hätte ich auch einfach den Struktur* Pointer
verwenden können. Ich will die Struktur zusammen mit ein paar anderen in
einem Array aus void-Pointer halten und dann anhand der ID im ersten
Element herausfinden können welche Struktur sich hinter dem Pointer
verbirgt.
Dabei fällt mir ein, eigentlich konnte ich auch ein Array aus
1
struct
2
{
3
uint8_tid;
4
}
Pointer machen.
P.S. Nur weil du es nicht verstehst ist es noch lange nicht gepfuscht.
Linda schrieb:> Dabei fällt mir ein, eigentlich konnte ich auch ein Array aus>
1
>struct
2
>{
3
>uint8_tid;
4
>}
5
>
> Pointer machen.
Das ist schon wesentlich besser.
Merkregel: du willst um void Pointer einen grossen Bogen machen.
void Pointer willst du nur benutzen wenn:
* du ein Modul hast, das Pointer zu internen Datenstrukturen ausliefern
muss, die so zu verwenden sind, dass derselbe Pointer für jegliche
Bearbeitung wieder in eine Funktion aus dem Modul zurück gegeben werden
muss. Der void Pointer ist also nur ein Transportmedium, mit dem sich
ein Modul selber Daten in verschiedenen Funktionen zuspielen kann. Für
den Benutzer dieses Moduls hat der Pointer kein besondere Bedeutung,
insbesondere soll er ihn nicht dereferenzieren
* wenn du Funktionen hast, die eine allgemeine Byteschnittstelle haben
sollen, wie zb memcpy. Aus praktischen Gründen hat memcpy als Argumente
void Pointer, weil das der einzige Pointer Typ ist, an den alle anderen
Pointer Typen zuweisbar sind.
> P.S. Nur weil du es nicht verstehst ist es noch lange nicht gepfuscht.
Du hast ja auch erst jetzt erläutert was der Sinn der Sache ist.
Eine andere Möglichkeit wäre es, alle möglichen Strukturen in einer
union zu vereinen und dann aus dieser union (oder einem Pointer darauf)
und dem Typ-Member eine weitere Struktur zu bauen, die immer gleich
aufgebaut ist. Damit hast du deinen konstanten Pointer Datentyp, den du
in ein Array aufnehmen kannst. Aber wenn irgendwie möglich, willst du
die Datentypprüfung des Compiler nicht abschalten. Mit void Pointern und
wildem rumgecaste tust du aber genau das.
Linda schrieb:> Ein paar Fragen hätt ich noch:> Mit einem Enum an Stelle der uint8_t würde das auch klappen?
Ja.
> Was genau ist so eine Enum eigentlich, wie viele bits ist es breit, sind> alle Enums gleich breit?
Um mal wieder den C-Standard zu zitieren (6.7.2.2 Absatz 4)
> Each enumerated type shall be compatible with char,> a signed integer type, or an unsigned integer type.> The choice of type is implementation-defined, 110)> but shall be capable of representing the values of> all the members of the enumeration. The enumerated> type is incomplete until after the } that terminates> the list of enumerator declarations.
Im Klartext: Der Compiler darf entscheiden wie breit er enums macht. Das
heißt insbesondere auch verschiedene enums dürfen verschieden breit
sein. Du kannst nur eine Minimalbreite vorgeben, da alle Werte mit dem
gewählten Typen darstellbar sein müssen.
Randnotiz: In C++ gibts seit C++11 mit enum classes die Möglichkeit dort
breiten selbst festzulegen, hilft aber in C nicht weiter.
Linda schrieb:> Dabei fällt mir ein, eigentlich konnte ich auch ein Array aus> struct> {> uint8_t id;> }> Pointer machen.
Müsste da nicht zumindest noch ein separater Zeiger auf den Inhalt drin
sein?
Karl H. schrieb:> Eine andere Möglichkeit wäre es, alle möglichen Strukturen in einer> union zu vereinen und dann aus dieser union (oder einem Pointer darauf)> und dem Typ-Member eine weitere Struktur zu bauen, die immer gleich> aufgebaut ist. Damit hast du deinen konstanten Pointer Datentyp, den du> in ein Array aufnehmen kannst. Aber wenn irgendwie möglich, willst du> die Datentypprüfung des Compiler nicht abschalten. Mit void Pointern und> wildem rumgecaste tust du aber genau das.
Unions haben den unschönen Nachteil, dass immer mindestens der Platz des
größten im union liegenden Elements gebraucht wird. Möchte man nicht
unbedingt immer.
Karl H. schrieb:> Linda schrieb:>> P.S. Nur weil du es nicht verstehst ist es noch lange nicht gepfuscht.>> Du hast ja auch erst jetzt erläutert was der Sinn der Sache ist.
Was mich darin bestärkt, Fragen nur noch beantworten, wenn die Absicht
im ersten Posting vollständig und klar erklärt ist - wie es übrigens
auch in der Netiquette steht.
Man bekommt immer dumme Antworten, egal ob man nun den einfachsten Fall
annimmt und eine dazu passende Antwort gibt oder ob man nachfragt.
> Müsste da nicht zumindest noch ein separater Zeiger auf den Inhalt drin> sein?
Nein, ich hätte den Pointer auf die Struktur aus dem Eröffnungspost in
einen Pointer auf diese Struktur gecastet und sobald ich anhand der id
weiß welche Struktur das war wieder zurückcasten. Da das erste Element
immer gleich aligned sein muss sollte das auch funktionieren.
> Was mich darin bestärkt, Fragen nur noch beantworten, wenn die Absicht> im ersten Posting vollständig und klar erklärt ist
Oder du beantwortest die Frage beantworten ohne lang zu versuchen
irgendetwas hineinzuinterpretieren. Wenn ich wissen will ob ich den Cast
so machen darf, dann ist das (in diesem Fall) mit einem einfachen "Ja",
ab besten mit Begründung wie zum Beispiel dem Zitat aus dem C-Standard,
vollständig beantwortet.
Linda schrieb:> Oder du beantwortest die Frage beantworten ohne lang zu versuchen> irgendetwas hineinzuinterpretieren.
dann könnten wir mindestens 80% aller Fragen nicht beantworten.
Linda schrieb:> Oder du beantwortest die Frage beantworten ohne lang zu versuchen> irgendetwas hineinzuinterpretieren.
Kennst du diesen Dialog?
A: "Haben Sie die Uhrzeit?"
B: "Ja", und geht weiter.
Ein Forenklassiker: Es kommt recht oft vor, dass die falsche Frage
gestellt wird. Also jemand eine scheinbar fast fertige Lösung im Kopf
hat, zu der nur eine Kleinigkeit fehlt, und danach wird nun gefragt.
Dann aber partout nicht akzeptieren will, dass er völlig auf dem Holzweg
ist und der richtige Weg ein paar Abzweige früher abging. Und nun
stinksauer wird, wenn man vorsichtig versucht, darauf hinzuweisen.
Deshalb ist es recht nützlich, etwas Kontext mitzuliefern. Damit die
Frage einen Sinn ergibt. Denn meistens erntet man mit der Frage "ist
Blau lauter als Gelb" bloss dumme Gesichter.
Casts sind zwar Teil der Sprache, aber man sollte sich darüber klar
sein, dass man sich damit selbst Fallstricke spannt. So kann der
Optimizer für Überraschungen sorgen, weil der Compiler davon ausgehen
darf, dass Pointer auf verschiedene Typen nicht auf das gleiche Objekt
zeigen.
Mir scheint, was hier zu implementieren versucht wird, wäre in einer
union besser untergebracht als in Casts.
Linda schrieb:>> Müsste da nicht zumindest noch ein separater Zeiger auf den> Inhalt drin>> sein?> Nein, ich hätte den Pointer auf die Struktur aus dem Eröffnungspost in> einen Pointer auf diese Struktur gecastet und sobald ich anhand der id> weiß welche Struktur das war wieder zurückcasten. Da das erste Element> immer gleich aligned sein muss sollte das auch funktionieren.
Das verstößt wiederum gegen die strict aliasing rule. Verschiedene
structs sind verschiedene Typen und Zeiger verschiedener Typen dürfen
nur in Ausnahmefällen auf die gleiche Speicherstelle zeigen. Und damit
wird dir der Optimierer einen Bug einbauen wenn du es am wenigsten
erwartest.
Ausnahme ist alles was im Standard als "Compatible Type" bezeichnet
wird. Deine beiden struct sind zwar jeweils compatible zu uint8_t, aber
trotzdem nicht zueinander. Structs sind nur zueinander compatible wenn
du wirklich das gleiche hast. (Gleicher Name, gleiche Member)
Vgl. http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf
6.2.7 Absatz 1 (tag meint den Namen eines structs und erlaubt typedefs)
6.7.2.2 (Dort ist compatible zu uint8_t beschrieben, schonmal hier
zitiert)
http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html
Mein Vorschlag wäre entweder die von Karl Heinz vorgeschlagene Methode
> struct message> {> uint8_t id;> void* data;> };
Das würde sich von der ursprünglichen Idee mit dem Array aus
void-Pointern auch nicht sehr unterscheiden, ich glaube ich werde dabei
bleiben.
> lern erstmal deutsch und dann erst C.
Dir würde eine Lektion in Groß- und Kleinschreibung auch nicht schaden,
wie war das nochmal mit dem Glashaus und den Steinen?
Linda schrieb:>> Was mich darin bestärkt, Fragen nur noch beantworten, wenn die Absicht>> im ersten Posting vollständig und klar erklärt ist> Oder du beantwortest die Frage beantworten ohne lang zu versuchen> irgendetwas hineinzuinterpretieren. Wenn ich wissen will ob ich den Cast> so machen darf, dann ist das (in diesem Fall) mit einem einfachen "Ja",> ab besten mit Begründung wie zum Beispiel dem Zitat aus dem C-Standard,> vollständig beantwortet.
Es bleibt dabei aber das ungute Gefühl, daß da was falsch läuft, weil
das Vorhaben an sich schon unsinnig klingt. Wie die Frage: "Wie kann ich
Billard-Kugeln so abflachen, daß sie aufeinander stehen bleiben?" Man
kann sicher eine passende Antwort finden, aber es bleibt die Frage im
Hinterkopf, wozu um alles in der Welt der Fragesteller Billardkugeln
stapeln will und was er danach mit den bearbeiteten "Kugeln" noch
anfangen kann.
Wenn man nachfragt, lautet die Antwort dann z.B., daß derjenige sie
einfach nur lagern will und nicht den Platz hat, um sie nebeneinander zu
legen.
Das eigentliche Problem ist also nicht die Bearbeitung der Kugeln zum
Stapeln, sondern die Lagerung unter bestimmten Platzbedingungen. Der
vermeintliche Lösungsweg, der dann zur Frage geführt hat, ist bereits
unsinnig. Vergleichbarse erlebt man hier im Forum praktisch täglich.
Daher: Immer von Anfang an das eigentliche Problem posten und nicht eine
Frage, die schon auf einem kreativ erdachten Lösungsansatz basiert, denn
dieser Ansatz kann schon der falsche sein.
Ich finde auch nicht, daß man den Leuten hier übelnehmen sollte, wenn
sie nicht einfach nur stur wie eine Maschine die Frage beantworten,
sondern auch mal die Sinnhaftigkeit in Frage stellen, wenn diese
zweifelhaft ist.
Ano schrieb:> Mein Vorschlag wäre entweder die von Karl Heinz vorgeschlagene Methode>
1
>structmessage
2
>{
3
>uint8_tid;
4
>union
5
>{
6
>/* Hier alle typen */
7
>};
8
>};
9
>
> oder>
1
>structmessage
2
>{
3
>uint8_tid;
4
>void*data;
5
>};
6
>
> und dann ein Array von message zu speichern.
Ich halte beide Vorschläge für suboptimal. Wie bereits oben erwähnt,
wird bei der Verwendung einer Union, in die man sämtliche Daten
klatscht, der maximal notwendige Platz verschwendet.
Die Idee mit der Union ist ja schon richtig, aber da gehören nicht die
Daten, sondern Pointer auf die Daten rein.
Also:
1
structmessage
2
{
3
uint8_tid;
4
union
5
{
6
FOO*fooptr;
7
BAR*barptr;
8
...
9
};
10
};
Dann kann man sauber über fooptr, barptr usw. auf die einzelnen Daten
zugreifen, ohne einen Cast zu bemühen. Und es ist sehr ähnlich zu Lindas
Datenstruktur mit dem void-Pointer. Im Speicher sollte es sogar gleich
aussehen. Die Programm-Effizienz ist auch dieselbe.
Frank M. schrieb:> Die Idee mit der Union ist ja schon richtig, aber da gehören nicht die> Daten, sondern Pointer auf die Daten rein.
Dann brauchst du aber noch eine Speicherverwaltung, und malloc()
wird hier ja oft gescheut wie der Teufel das Weihwasser. ;-)
Hängt natürlich davon ab: wenn die Elemente alle so einigermaßen
gleich groß sind, wird man der Einfachheit (des Codes) wegen u. U.
lieber die union-Variante bevorzugen.
Jörg W. schrieb:> Frank M. schrieb:>> Die Idee mit der Union ist ja schon richtig, aber da gehören nicht die>> Daten, sondern Pointer auf die Daten rein.>> Dann brauchst du aber noch eine Speicherverwaltung, und malloc()> wird hier ja oft gescheut wie der Teufel das Weihwasser. ;-)
Nun, wenn man sich das eingehende Posting anschaut, liegen die Daten
offenbar (wie auch immer diese entstanden sind) bereits im Speicher vor.
Wenn Linda selber void-Pointer verwenden will, scheint sie für die
eigentlichen Daten ja schon eine Lösung zu haben. Wenn ich das richtig
verstehe, gehts nur noch um die Verwaltung der Daten und nicht mehr um
die Daten an sich.
Die union über die Pointer sorgt letztendlich nur dafür, dass Linda sich
die Casts spart. Das nimmt ihr dann der Compiler ab und der erzeugte
Code ist exakt derselbe.
> Hängt natürlich davon ab: wenn die Elemente alle so einigermaßen> gleich groß sind, wird man der Einfachheit (des Codes) wegen u. U.> lieber die union-Variante bevorzugen.
Ja, natürlich. Aber wenn Linda die Daten schon im Speicher hat, warum
dann jetzt alles umorganisieren?
Kann natürlich sein, dass ich mich irre. Dann fehlt da aber eine
entscheidende Info.