Forum: Compiler & IDEs Ist das in C erlaubt?


von Linda (Gast)


Lesenswert?

Hallo,
ich habe eine kleine Frage zu programmiersparech C:
Ist es in C erlaubt bzw. sauber so etwas zu schreiben?
1
typedef struct
2
{
3
  uint8_t zahl;
4
  // Noch ein paar Member
5
} Struktur;
6
7
8
Struktur test;
9
void *test_ptr = &test;

Dann würde ich mit
1
*((uint8_t*)test_ptr)
auf zahl zugreifen.

von Harold (Gast)


Lesenswert?

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.
1
xzq = *((Struktur*)test_ptr).zahl;
2
3
oder
4
5
xzq = ((Struktur*)test_ptr)->zahl;

von Ano (Gast)


Lesenswert?

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?

von (prx) A. K. (prx)


Lesenswert?

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.

von Tom (Gast)


Lesenswert?

Linda schrieb:
> Dann würde ich mit*((uint8_t*)test_ptr)auf zahl zugreifen.

Bis in ein paar Wochen jemand ein Structmember vor zahl einfügt.

von Klaus (Gast)


Lesenswert?

Linda schrieb:
> Hallo,
> ich habe eine kleine Frage zu programmiersparech C:
> Ist es in C erlaubt bzw. sauber so etwas zu schreiben?
>
1
> typedef struct
2
> {
3
>   uint8_t zahl;
4
>   // Noch ein paar Member
5
> } Struktur;
6
> 
7
> 
8
> Struktur test;
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
typedef struct
2
{
3
  uint8_t zahl;
4
  // Noch ein paar Member
5
} Struktur;
6
7
8
Struktur test;
9
Struktur * test_ptr = & test;
10
11
uint8_t x = 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_t y = ((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. :-)

von Ano (Gast)


Lesenswert?

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.

von Klaus (Gast)


Lesenswert?

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.

von Klaus (Gast)


Lesenswert?

@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.

von Linda (Gast)


Lesenswert?

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.

von Linda (Gast)


Lesenswert?

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?

von Tom (Gast)


Lesenswert?

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;
?

von Linda (Gast)


Lesenswert?

> 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_t id;
4
}
Pointer machen.


P.S. Nur weil du es nicht verstehst ist es noch lange nicht gepfuscht.

von Karl H. (kbuchegg)


Lesenswert?

Linda schrieb:

> Dabei fällt mir ein, eigentlich konnte ich auch ein Array aus
>
1
> struct
2
> {
3
>   uint8_t id;
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.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Linda schrieb:
> Was genau ist so eine Enum eigentlich,

Steht im C-Buch.

> wie viele bits ist es breit, sind
> alle Enums gleich breit?

http://stackoverflow.com/questions/1113855/is-the-sizeofenum-sizeofint-always

von Ano (Gast)


Lesenswert?

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.

von Klaus (Gast)


Lesenswert?

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.

von Linda (Gast)


Lesenswert?

> 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.

von Karl H. (kbuchegg)


Lesenswert?

Linda schrieb:

> Oder du beantwortest die Frage beantworten ohne lang zu versuchen
> irgendetwas hineinzuinterpretieren.

dann könnten wir mindestens 80% aller Fragen nicht beantworten.

von (prx) A. K. (prx)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

von Ano (Gast)


Lesenswert?

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
1
struct message
2
{
3
  uint8_t id;
4
  union
5
  {
6
  /* Hier alle typen */
7
  };
8
};
oder
1
struct message
2
{
3
  uint8_t id;
4
  void* data;
5
};
und dann ein Array von message zu speichern.

von Linda (Gast)


Lesenswert?

> 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?

von Rolf M. (rmagnus)


Lesenswert?

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.

von mec (Gast)


Lesenswert?

@ Rolf Magnus

die Beste Antwort bis jetzt ;)

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Ano schrieb:
> Mein Vorschlag wäre entweder die von Karl Heinz vorgeschlagene Methode
>
1
> struct message
2
> {
3
>   uint8_t id;
4
>   union
5
>   {
6
>   /* Hier alle typen */
7
>   };
8
> };
9
>
> oder
>
1
> struct message
2
> {
3
>   uint8_t id;
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
struct message
2
{
3
  uint8_t id;
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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.