Hypercell ein ] Hypercell aus ] Zeige Navigation ] Verstecke Navigation ]
c++.net  
   

Die mobilen Seiten von c++.net:
https://m.c-plusplus.net

  
C++ Forum :: C++ (alle ISO-Standards) ::  union -> struct inside     Zeige alle Beiträge auf einer Seite Auf Beitrag antworten
Autor Nachricht
breakpoint
Mitglied

Benutzerprofil
Anmeldungsdatum: 09.01.2017
Beiträge: 1
Beitrag breakpoint Mitglied 20:19:50 10.01.2017   Titel:   union -> struct inside            Zitieren

Hi,
ich habe folgenden Code:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
union myColor{
struct{ //anonymer Struct
    unsigned char a,b,g,r; //falsch herum ist absicht!
};
unsigned int rgba;
};
 
int main(int argc, char** argv){
    myColor color;
    color.rgba = 0xFFC080AA;
    std::cout << +color.r << " " << +color.g << " " << +color.b << " " +color.a << std::endl; //+ bedeutet: als Zahl interpretieren
 
}



Wie weiß ich, dass im struct die verschiedenen a,b,g,r sind?.
Ich weiß, dass das rückwärts wegen dem Big Endian ist.. aber wie kann er es in einem struct verpackt erkennen und zuordnen? So viel Intelligenz traue ich dem Compiler nicht zu ;)

Vielen Dank für Antworten :)
manni66
Unregistrierter




Beitrag manni66 Unregistrierter 20:40:47 10.01.2017   Titel:              Zitieren

Warum nimmst du an, dass hier Compilerintelligenz nötig wäre?
Das Ganze ist undefiniertes Verhalten. Wenn die Bytes des unsigned richtig "unter" den Bytes der char liegen passt es halt.
hustbaer
Mitglied

Benutzerprofil
Anmeldungsdatum: 27.10.2006
Beiträge: 23321
Beitrag hustbaer Mitglied 23:12:34 10.01.2017   Titel:   Re: union -> struct inside            Zitieren

@manni66
Was ist hier deiner Meinung nach UB?
Strict-Aliasing greift mMn. nicht weil Ausnahme für unsigned char. IB ist es natürlich, weil der Standard nicht festlegt wie die Bits in einem unsigned int angeordnet zu sein haben. Aber IB ist eben nicht UB.

breakpoint schrieb:
aber wie kann er es in einem struct verpackt erkennen und zuordnen? So viel Intelligenz traue ich dem Compiler nicht zu ;)

Du traust dem Compiler nicht zu zu wissen dass er Variablen in POD-Structs in der Reihenfolge in den Speicher legt wie sie im Source-File stehen? Oder dass er bei Unions für jeden "Teil" der Union an der selben Adresse anfängt? Oder dass er bei Integers die grösser als ein Byte sind die native Byte-Order verwendet? Was traust du dem Compiler noch alles nicht zu, dass er 1 + 1 nicht zusammenzählen kann?

Anders gesagt: der Compiler muss hier nix über die Byte-Order der Maschine wissen. Der Code wird sich auch hübsch reproduzierbar auf Little-Endian anders verhalten als auf Big-Endian. Davon abgesehen weiss der Compiler üblicherweise sehr genau was "seine" CPU so macht.
Guggsdu: https://godbolt.org/g/u0cZan
Sieh dir den Code an der für diese Funktionen generiert. Und dann überleg dir nochmal was du dem Compiler zutraust und was nicht. :)
Ich persönlich finde das schon recht beeindruckend. Vor allem das letzte Beispiel (quux).

_________________
Until every person can enjoy all their human rights, we will not stop. I support Amnesty International. Will you?
https://www.amnesty.org / https://www.amnesty.de / https://www.amnesty.at
manni66
Unregistrierter




Beitrag manni66 Unregistrierter 23:45:18 10.01.2017   Titel:   Re: union -> struct inside            Zitieren

hustbaer schrieb:
@manni66
Was ist hier deiner Meinung nach UB?

Der lesende Zugriff auf ein Member, dem nicht vorher ein Wert zugewiesen wurde.
hustbaer
Mitglied

Benutzerprofil
Anmeldungsdatum: 27.10.2006
Beiträge: 23321
Beitrag hustbaer Mitglied 00:34:49 11.01.2017   Titel:              Zitieren

* Der Speicher wurde initialisiert.
* Der Zugriff erfolgt über nen unsigned char.
Wäre es ein anderer Typ wie z.B. unsigned short wäre die Sache klar und du hättest natürlich Recht. So kann ich hier aber kein Problem sehen.

Lesen per *reinterpret_cast<unsigned char*>(&color)[0] wäre definitiv OK. Ich denke dann sollte auch Lesen per color.a OK sein.

Was sagen die Standard-Gurus? :confused:

_________________
Until every person can enjoy all their human rights, we will not stop. I support Amnesty International. Will you?
https://www.amnesty.org / https://www.amnesty.de / https://www.amnesty.at


Zuletzt bearbeitet von hustbaer am 00:36:52 11.01.2017, insgesamt 2-mal bearbeitet
nonpro
Unregistrierter




Beitrag nonpro Unregistrierter 03:01:43 11.01.2017   Titel:              Zitieren

hustbaer schrieb:
* Der Speicher wurde initialisiert.
* Der Zugriff erfolgt über nen unsigned char.

Es ist in obigem Code nicht sichergestellt, dass die Größe des structs gleich der Größe eines unsigned ints ist. Das klappt heute und der nächste Compiler meint vielleicht, Padding-Bytes einschieben zu müssen.
Klar, einfach die Member auf 1 Byte ausrichten lasen, dann bliebe noch das Schreiben eines und Lesen des anderen Werts...

Campers Meinung würde mich zwar auch interessieren, aber mit mit den passend definierten Masken zum Lesen der einzelnen Werte ist es doch auch nicht umständlicher und garantiert standardkonform, sodass der Code nicht wirklich nützlich ist und noch Fallstricke bereitstellt.
hustbaer
Mitglied

Benutzerprofil
Anmeldungsdatum: 27.10.2006
Beiträge: 23321
Beitrag hustbaer Mitglied 10:24:27 11.01.2017   Titel:              Zitieren

OK. Wenn unsigned int z.B. nur 16 Bit wäre, dann wäre es UB. Wohl wahr.
Wenn sizeof(unsigned int) >= 4, dann sehe ich hier kein UB. Es ist also sozusagen IB ob es UB ist oder nicht :D

nonpro schrieb:
(...) mit den passend definierten Masken zum Lesen der einzelnen Werte ist es doch auch nicht umständlicher und garantiert standardkonform, sodass der Code nicht wirklich nützlich ist und noch Fallstricke bereitstellt.

Klar, kein Einwand.

_________________
Until every person can enjoy all their human rights, we will not stop. I support Amnesty International. Will you?
https://www.amnesty.org / https://www.amnesty.de / https://www.amnesty.at
DirkB
Mitglied

Benutzerprofil
Anmeldungsdatum: 24.01.2016
Beiträge: 726
Beitrag DirkB Mitglied 10:35:45 11.01.2017   Titel:              Zitieren

Wenn sizeof(unsigned int) > 4 ist (das = ist weg), dann funktioniert die union bei Big-Endian-Systemen nicht mehr.

nonpro schrieb:
..., aber mit mit den passend definierten Masken zum Lesen der einzelnen Werte ist es doch auch nicht umständlicher und garantiert standardkonform, sodass der Code nicht wirklich nützlich ist und noch Fallstricke bereitstellt.
Und der der Compiler macht daraus auch keinen langsameren Code.


Zuletzt bearbeitet von DirkB am 10:41:06 11.01.2017, insgesamt 1-mal bearbeitet
manni66
Unregistrierter




Beitrag manni66 Unregistrierter 10:46:04 11.01.2017   Titel:              Zitieren

Leider ist der Standard erstaunlich vage bei der Definition von Unions.
Aber
Standard schrieb:

9.5 Unions [class.union]

In a union, at most one of the non-static data members can be active at any time, that is, the value of at
most one of the non-static data members can be stored in a union at any time.
[Note:
One special guarantee
is made in order to simplify the use of unions: If a standard-layout union contains several standard-layout
structs that share a common initial sequence (9.2), and if an object of this standard-layout union type
contains one of the standard-layout structs, it is permitted to inspect the common initial sequence of any of
standard-layout struct members; see 9.2.
— end note]

Es gibt also genau ein aktives Element, und auf die anderen darf nur in Ausnahmefällen zugegriffen werden.

Siehe ebenfalls http://en.cppreference.com/w/cpp/language/union:
Zitat:

...
it's undefined behavior to read from the member of the union that wasn't most recently written.
SeppJ
Global Moderator

Benutzerprofil
Anmeldungsdatum: 10.06.2008
Beiträge: 27266
Beitrag SeppJ Global Moderator 11:14:56 11.01.2017   Titel:              Zitieren

Ganz so einfach ist das nicht, manni66. hustbaer bezieht sich gerade auf diese Ausnahmefälle. Leider ist der Standard in dieser Hinsicht ziemlich schwer verständlich/ungeschickt formuliert. Die Intention des Standards ist vermutlich, dass es erlaubt sein sollte, denn in C ist es explizit erlaubt und man wollte beim Standard möglichst erreichen, dass C++ in Hinsicht des Speicherlayouts trivialer Objekte möglichst kompatibel zu C ist. Mit den ganzen Verklausulierungen des Standards ist am Ende aber meiner Einschätzung nach etwas heraus gekommen, das auf undefiniertes Verhalten in diesem Fall hinaus läuft.

Man kann sich praktisch aber darauf herausreden, dass der GCC (und vermutlich alle bekannten Compiler) ganz explizit sagt, dass dies funktionieren wird.

_________________
Korrekte Rechtschreibung und Grammatik sind das sprachliche Äquivalent zu einer Dusche und gepflegter Kleidung.


Zuletzt bearbeitet von SeppJ am 11:15:51 11.01.2017, insgesamt 1-mal bearbeitet
dot
Mitglied

Benutzerprofil
Anmeldungsdatum: 20.05.2004
Beiträge: 6750
Beitrag dot Mitglied 12:14:34 11.01.2017   Titel:              Zitieren

Meiner Interpretation des Standard nach handelt es sich bei obigem Code um UB. Es wird da aus einem inaktiven Member einer union gelesen, was laut Standard UB ist und fertig. Die Ausnahme von wegen common initial sequence greift hier nicht...

_________________
one point of view will never reveal the entire scene.


Zuletzt bearbeitet von dot am 12:16:38 11.01.2017, insgesamt 2-mal bearbeitet
SeppJ
Global Moderator

Benutzerprofil
Anmeldungsdatum: 10.06.2008
Beiträge: 27266
Beitrag SeppJ Global Moderator 12:16:33 11.01.2017   Titel:              Zitieren

dot schrieb:
Meiner Interpretation des Standard nach handelt es sich bei obigem Code streng genommen um UB; aus dem einfachen Grund, dass da aus einem inaktiven Member einer union gelesen wird, was laut Standard UB ist. Die Ausnahme von wegen common initial sequence greift hier nicht...
Und wo steht das im Standard? So einfach ist das wie gesagt nicht...

_________________
Korrekte Rechtschreibung und Grammatik sind das sprachliche Äquivalent zu einer Dusche und gepflegter Kleidung.
Finnegan
Mitglied

Benutzerprofil
Anmeldungsdatum: 09.12.2014
Beiträge: 457
Beitrag Finnegan Mitglied 16:48:40 11.01.2017   Titel:              Zitieren

Kein Standardreiterei, aber Hinweis in eine Richtung, in die man recherchieren kann, wenn mans genau wissen will:

Ich erinnere mich dass die Problematik in einem Vortrag zur Sprache kam (ich glaube es war einer der CppCon-Vorträge von Chandler Carruth über die Jahre).
Dort wurde erwähnt, dass derlesende Zugriff auf einen nicht aktiven Member wie in diesem Fall tatsächlich UB ist. Allerdings sei dieses Pattern derart weit
verbreitet - auch als Methode um vermeintlich strict-aliasing-konformen Code zu schreiben - dass hier wohl duch die Bank alle Compiler das "Richtige" machten.

Dennoch, für wirklich standardkonformen Code bleibt einem nur entweder die Werte über Bit-Schubserei oder via memcpy herauszufischen. Letzteres sieht
übrigens meist schlimmer aus als es ist: Die Compiler optimieren heutzutage gerade auch solche winzigen memcpy-Aufrufe recht gut - auch zu einem simplen
MOV zwischen Registern wenn möglich (trotz des "mem" im Namen).

P.S.: Ich glaube auch dass bezüglich Aliasing das von hustbaer vorgeschlagene reinterpret_cast in Ordnung wäre, allerdings nur aus dem Grund, dass ein
8-Bit-Farbkanal "zufällig" den richtigen Datentyp hat. Ich würde das eher vermeiden wollen, da zumindest bei den Sachen die ich selbst so mache nicht nur
8-Bit-Kanäle verwendet werden (und auch nicht immer für jeden Kanal gleich viele Bits).
hustbaer
Mitglied

Benutzerprofil
Anmeldungsdatum: 27.10.2006
Beiträge: 23321
Beitrag hustbaer Mitglied 01:20:47 12.01.2017   Titel:              Zitieren

Vergesst das aktive Element. Oder auch nicht, denn wenn es sich wie gesagt um etwas anderes als (unsigned) char handeln würde, dann wären diese Regeln natürlich wichtig -- und auch dieser Fall interessiert natürlich zu Recht viele Leute.

Hier sind es aber eben unsigned char, und damit wird der Fall IMO viel etwas einfacher. Denn man darf *jeden* (initialisierten) Speicher als char oder unsigned char auslesen, ganz egal was sich dort für ein Objekt befindet. Ebenso darf man jeden Speicher (initialisiert oder nicht) mit char bzw. unsigned char überschreiben. Das Überschreiben eines Objekts mit char bzw. unsigned char zerstört dieses -- natürlich ohne dass dabei der Destruktor aufgerufen würde. (Was natürlich auch bedeutet: man darf das nur machen wenn es für das korrekte Funktionieren des Programms nicht nötig ist dass der Destruktor aufgerufen wird.)

Weiters gibt es dann noch Sonderregeln, die es erlauben nach diesem "Überschreiben mit char" den Speicher wieder als ein Objekt eines anderen Typs zu verwenden -- so lange dieser andere Typ bestimmte Voraussetzungen erfüllt. Klassische C structs erfüllen z.B. diese Voraussetzungen. (Weiters sind speziell in C++11 noch etliche Dinge zusätzlich erlaubt, z.B. darf so ein struct IIRC nicht-virtuelle public Memberfunktionen haben etc. - aber die genauen Regeln dafür hab ich mir nie gemerkt.)

Diese Regeln führen u.A. dazu dass man eine memcpy-artige Funktion selbst implementieren kann, und verwenden um damit beliebige simple "Daten-structs" zu kopieren, ohne dabei in UB (oder auch nur IB) hineinzulaufen.

D.h. folgendes ist vollkommen definiert und vollkommen OK:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include <stdlib.h>
 
void MyMemCpy(void* dest, void const* src, size_t size)
{
    unsigned char* d = static_cast<unsigned char*>(dest);
    unsigned char const* s = static_cast<unsigned char const*>(src);
    while (size--)
        *d++ = *s++;
}
 
struct Foo
{
    int a;
    int b;
};
 
int main()
{
    Foo const f1 = { 1, 2 };
    Foo* fp = static_cast<Foo*>(malloc(sizeof(Foo)));
    if (!fp)
        exit(1);
    MyMemCpy(fp, &f1, sizeof(Foo));
    printf("a = %d, b = %d\n", fp->a, fp->b);
    free(fp);
}


Wenn jetzt jemand argumentieren möchte, dass - unter der Voraussetzung dass sizeof(int) >= 4 * sizeof(char), der Zugriff auf color.a UB sein soll, dann würde mich interessieren was der Unterschied zu meinem Beispiel sein soll. Also warum ist color.a Lesen verboten, wohingegen *s Lesen in meinem Beispiel OK ist.
In beiden Fällen wird ein char gelesen "wo kein char ist". Wie man die Adresse ermittelt hat sollte dabei keine Rolle spielen. So lange sichergestellt ist dass die Adresse "passt" (also auf ein Byte zeigt welches initialisiert wurde), ist es erlaubt.

_________________
Until every person can enjoy all their human rights, we will not stop. I support Amnesty International. Will you?
https://www.amnesty.org / https://www.amnesty.de / https://www.amnesty.at
hustbaer
Mitglied

Benutzerprofil
Anmeldungsdatum: 27.10.2006
Beiträge: 23321
Beitrag hustbaer Mitglied 01:25:10 12.01.2017   Titel:              Zitieren

Was den "was wenn es kein (unsigned) char wäre?" Fall angeht: Hier glaube ich dass es tatsächlich UB wäre. Zumindest hab' ich das öfters gelesen, u.A. auch von Leuten wo ich den Eindruck hatte dass sie wissen wovon sie reden (schreiben).

Und es macht mMn. auch Sinn. Denn wenn man sich die klassische "strict Aliasing" Problemfunktion anguckt...
C++:
int Foo(int* a, short* b)
{
    *a = 0;
    *b = 1;
    return *a;
}

Die meisten Compiler optimieren das return *a hier (berechtigterweise) zu einem return 0. Weil strict aliasing ja besagt dass a und b unmöglich "aliasen" können (dürfen).
Und was soll nun passieren wenn man folgendes macht
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
 
int Foo(int* a, short* b)
{
    *a = 0;
    *b = 1;
    return *a;
}
 
union U
{
    int a;
    short b;
};
 
int main()
{
    U u;
    printf("Foo: %d\n", Foo(&u.a, &u.b));
}

Hier kommt mit optimiertem Code üblicherweise "Foo: 0" raus. Wenn es für enums ne Ausnahme (ohne weitere Einschränkungen) gäbe, dann müsste aber "Foo: 1" rauskommen. D.h. der Compiler dürfte in Foo nicht mehr das return *a zu return 0 optimieren. Was doof wäre, denn damit wäre die ganze strict aliasing Regel für die Katz. Was jetzt einige sicher freuen würde, aber das ist wieder ein anderes Thema -- ist halt einfach nicht so :)

D.h. entweder es gibt ne Ausnahme die wieder super krass eingeschränkt ist, und das Beispiel hier eben irgendwie ausschliesst, oder es ist schlichtweg einfach grundsätzlich verboten. Ich gehe wie gesagt davon aus dass es grundsätzlich verboten ist -- bzw. mehr noch, ich hoffe dass es grundsätzlich verboten ist. Weil alles andere die so schon super komplizierten Regeln in diesem Bereich noch komplizierter machen würde.

_________________
Until every person can enjoy all their human rights, we will not stop. I support Amnesty International. Will you?
https://www.amnesty.org / https://www.amnesty.de / https://www.amnesty.at
C++ Forum :: C++ (alle ISO-Standards) ::  union -> struct inside   Auf Beitrag antworten

Zeige alle Beiträge auf einer Seite




Nächstes Thema anzeigen
Vorheriges Thema anzeigen
Sie können Beiträge in dieses Forum schreiben.
Sie können auf Beiträge in diesem Forum antworten.
Sie können Ihre Beiträge in diesem Forum nicht bearbeiten.
Sie können Ihre Beiträge in diesem Forum nicht löschen.
Sie können an Umfragen in diesem Forum nicht mitmachen.

Powered by phpBB © 2001, 2002 phpBB Group :: FI Theme

c++.net ist Teilnehmer des Partnerprogramms von Amazon Europe S.à.r.l. und Partner des Werbeprogramms, das zur Bereitstellung eines Mediums für Websites konzipiert wurde, mittels dessen durch die Platzierung von Werbeanzeigen und Links zu amazon.de Werbekostenerstattung verdient werden kann.

Die Vervielfältigung der auf den Seiten www.c-plusplus.de, www.c-plusplus.info und www.c-plusplus.net enthaltenen Informationen ohne eine schriftliche Genehmigung des Seitenbetreibers ist untersagt (vgl. §4 Urheberrechtsgesetz). Die Nutzung und Änderung der vorgestellten Strukturen und Verfahren in privaten und kommerziellen Softwareanwendungen ist ausdrücklich erlaubt, soweit keine Rechte Dritter verletzt werden. Der Seitenbetreiber übernimmt keine Gewähr für die Funktion einzelner Beiträge oder Programmfragmente, insbesondere übernimmt er keine Haftung für eventuelle aus dem Gebrauch entstehenden Folgeschäden.