C++, Unions und UB

OP #4876944
Lesenswert?

Hallo zusammen,

folgendes kleines MVCE sei gegeben:
1
#include <stdint.h>
2

3
template<typename T>
4
struct C {
5
    union {
6
        volatile T a;
7
        volatile T b;
8
    };
9
    static constexpr uint8_t address = 0x28;
10
};
11

12
template<typename C>
13
constexpr inline C* getBaseAddr() {
14
    return reinterpret_cast<C*>(C::address);
15
}
16

17
volatile uint8_t g = 0;
18

19
int main() {
20
    constexpr auto c1 = getBaseAddr<C<uint8_t>>;
21
    
22
    c1()->a = 1;
23
    g = c1()->b;    
24
    
25
    while(true) {}
26
}

Mit z.B. avr-g++ (7.0.0) funktioniert es wie es soll.

Allerdings ist Datenelement b nicht das aktive Datenelement der union im 
Moment des Auswertens. Nach (oberflächlichem) Lesen der betreffenden 
Passagen des C++-Standards könnte das ein Grund für UB sein.
Bin mir aber unsicher ...
#4877030
Lesenswert?

Ist das absichtlich so konfus geschrieben: "C" als Bezeichner einer 
struct, sowie als Template Argument. "c1" als Bezeichner für eine 
Funktion die immer die Adresse 0x28 zurückliefert.

Was steht denn an Adresse 0x28 wo du deine struct hincastest? Bei meinem 
"normalen" System läuft es auf eine Seg fault.
OP #4877048
Lesenswert?

Mikro 7. schrieb:

> Was steht denn an Adresse 0x28 wo du deine struct hincastest? Bei meinem
> "normalen" System läuft es auf eine Seg fault.

Ja, das ist klar. Aber darum geht es ja auch nicht.

Habe das Beispiel etwas "entschärft":
1
#include <stdint.h>
2

3
struct Component {
4
    typedef uint8_t value_type;
5
    union {
6
        volatile value_type a;
7
        volatile value_type b;
8
    };
9
};
10

11
volatile Component c1;
12
volatile uint8_t g = 0;
13

14
int main() {
15
    c1.a = 1;
16
    g = c1.b;    
17
    
18
    while(true) {}
19
}

Das erste Beispiel hätte ich vllt im µC / Elektronik Forum posten 
sollen, weil die Adresse natürlich etwas mit einer Resgisteradresse zu 
tun hat.

Bei der obigen Version ist es aber egal. Die Frage ist dieselbe.
OP #4877134
Lesenswert?

Mikro 7. schrieb:
> Ich nehme an, du erwartest g==1? Mit gcc scheint das auch zu klappen.

In C11 ist ein sog. "typ-punning" explizit erlaubt.

In C++(98/11/14/17) explizit nicht. Allerdings gibt es eben Ausnahmen.

Eine Ausnahme besteht für Standard-Layout-Type mit einer gleichen 
Abfolge der Datenelement. In diesem Fall soll das Lesen über das 
nicht-aktive Datenelement (hier b) ok sein.

Der nächste Schritt wäre dann auch die Modifikation über das 
nicht-aktive Datenelement b (was damit zum aktiven wird, weil zulesetzt 
geschrieben) und dann Lesen über a.

Auch dies scheint in allen Fällen beim g++ zu funktionieren, stellt aber 
i.A. UB dar, jedenfalls so, wie ich das im Standard lese.
Gast #4877205
Lesenswert?

Raoul D. schrieb:
>> Was ist c1()?
>
> Steht doch da ...

Soweit ich sehe, ist es ein Pointer auf struct C. Seit wann kann man 
einen Pointer wie eine Funktion aufrufen?

Aber egal...

Raoul D. schrieb:
> Eine Ausnahme besteht für Standard-Layout-Type mit einer gleichen
> Abfolge der Datenelement. In diesem Fall soll das Lesen über das
> nicht-aktive Datenelement (hier b) ok sein.

Zumindest in C++ gibt es keine Ausnahme: Man darf nur den Union-Member 
auslesen, der zuletzt beschrieben worden ist. Alles andere ist UB. Es 
ist egal, ob die Members Standard-Layout-Typen sind oder nicht.
#4877311
Lesenswert?

Raoul D. schrieb:
> In C11 ist ein sog. "typ-punning" explizit erlaubt.
>
> In C++(98/11/14/17) explizit nicht. Allerdings gibt es eben Ausnahmen.

Ich würde die Frage auf Stackoverflow einstellen. Am besten mit 'nem 
(bereinigten) Pointer Cast Beispiel (aus dem ersten Post -- das ist wohl 
etwas komplexer), falls sich Experten wie KHB oder Yalu etc. hier nicht 
noch melden. -- Wenn du's dort einstellst wäre ein Link nett :-).
OP #4877369
Lesenswert?

tictactoe schrieb:
> Raoul D. schrieb:
>>> Was ist c1()?
>>
>> Steht doch da ...
>
> Soweit ich sehe, ist es ein Pointer auf struct C. Seit wann kann man
> einen Pointer wie eine Funktion aufrufen?
>
> Aber egal...
>
> Raoul D. schrieb:
>> Eine Ausnahme besteht für Standard-Layout-Type mit einer gleichen
>> Abfolge der Datenelement. In diesem Fall soll das Lesen über das
>> nicht-aktive Datenelement (hier b) ok sein.
>
> Zumindest in C++ gibt es keine Ausnahme: Man darf nur den Union-Member
> auslesen, der zuletzt beschrieben worden ist. Alles andere ist UB. Es
> ist egal, ob die Members Standard-Layout-Typen sind oder nicht.

Das lese ich etwas anders (C++, N4297, Kapitel 9.5 Unions):
1
In a union, at most one of the non-static data members can be active at any time, that is, the value of at
2
most one of the non-static data members can be stored in a union at any time. [ Note: One special guarantee
3
is made in order to simplify the use of unions: If a standard-layout union contains several standard-layout
4
structs that share a common initial sequence (9.2), and if an object of this standard-layout union type
5
contains one of the standard-layout structs, it is permitted to inspect the common initial sequence of any of
6
standard-layout struct members; see 9.2. — end note ]

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