In einem größeren Projekt stürzte mein Programm an einer bestimmten
Stelle konsequent ab. Ich konnte die Stelle lokalisieren und eingrenzen.
Folgender Code zeigt das Problem:
1
uint8_tPufferB[32],PufferB1[32];
2
3
4
voidSendZeich(uint8_tZeich)
5
{
6
UDR=Zeich;
7
}
8
9
intmain(void)
10
{
11
uint8_tzmax=25;
12
13
while(zmax--)
14
{// hier absichtlich 64 reingeschrieben, um PufferB1 auch zu bearbeiten
{// hier absichtlich 64 reingeschrieben, um PufferB1 auch zu bearbeiten
17
for (uint8_t j=0; j<64; j++) if (PufferB[j]>127) PufferB[j] = '.';
18
4e: 9e e2 ldi r25, 0x2E ; 46
19
50: e8 2f mov r30, r24
20
52: f0 e0 ldi r31, 0x00 ; 0
21
54: e0 5a subi r30, 0xA0 ; 160
22
56: ff 4f sbci r31, 0xFF ; 255
23
58: 20 81 ld r18, Z
24
5a: 27 fd sbrc r18, 7
25
5c: 90 83 st Z, r25
26
5e: 8f 5f subi r24, 0xFF ; 255
27
60: f7 cf rjmp .-18 ; 0x50 <main+0x4>
28
29
00000062 <_exit>:
30
62: f8 94 cli
Klar, das ist unsauber programmiert. Aber daß der Compiiler hier das
SendZeich wegoptimiert, ist schon komisch. Laß ich die innere Schleife
nur bis 32 laufen, wird der Code um 20Byte länger und alles scheint zu
stimmen. Was ist hier los?
Compiler: AVR-GCC-4.8.0, Optimierung -s, Target ATMEGA8
Harald
Du greifst in der for-schleife ausserhalb des Speichers zu!
PufferB[j] wenn j>31 ist, können komische sachen passieren.
Ausserdem wird sendZeichen nicht in der der for-schleife nicht
aufgreufen, sndern danach. Ich weiß nicht, ob das gewünscht ist. Hab mir
das nicht weiter angeguckt
Harald P. schrieb:> Klar, das ist unsauber programmiert.
Unsauber programmiert? Anzunehmen, dass zwei Arrays direkt
hintereinander in der gewünschten Reihenfolge im Speicher liegen, ist
nicht "unsauber programmiert", sondern schlicht Unsinn.
Harald P. schrieb:> Aber daß der Compiiler hier das> SendZeich wegoptimiert, ist schon komisch.
Du produzierst absichtlich "undefined behavior". Und bei "undefined
behavior" steht es dem Compiler frei, zu machen was immer er will. Da
ist es völlig müßig über den dann produzierten Code zu philosophieren.
In
http://gcc.gnu.org/gcc-4.8/changes.html
steht geschrieben:
"GCC now uses a more aggressive analysis to derive an upper bound for
the number of iterations of loops using constraints imposed by
language standards. This may cause non-conforming programs to no
longer work as expected, such as SPEC CPU 2006 464.h264ref and
416.gamess. A new option, -fno-aggressive-loop-optimizations, was
added to disable this aggressive analysis. In some loops that have
known constant number of iterations, but undefined behavior is known
to occur in the loop before reaching or during the last iteration, GCC
will warn about the undefined behavior in the loop instead of deriving
lower upper bound of the number of iterations for the loop. The
warning can be disabled with -Wno-aggressive-loop-optimizations.
GCC 4.8.0 habe ich nicht, aber GCC 4.8.1 gibt folgende Warnung aus:
for (uint8_t j=0; j<64; j++) if (PufferB[j]>127) PufferB[j] = '.';
3
^
Der Compiler erkennt also richtigerweise ein "undefined behavior" im 32.
Schleifendurchlauf (von 0 aus gezählt) und denkt sich mit verteufelt
hämischem Grinsen (zu dem sonst nur Dichter Paul in der Lage ist ;-)):
"Der Harald verlangt mit -Os nach möglichst kurzem Code. Den werde ich
ihm gerne liefern."
Er setzt also ganz frech die obere Schleifengrenze auf unendlich, denn
bis zum 32. Schleifendurchlauf entsteht dadurch keinerlei Unterschied
im Verhalten des Programms. Und Ab dem 32. Schleifendurchlauf ist das
Verhalten sowieso undefiniert, d.h. egal, welchen Unsinn das Programm
danach fabriziert: undefinierter als undefiniert kann das Verhalten
nicht werden.
Durch diesen schlauen Gedankengang wird die Abfrage des Schleifenendes
eingespart. Da die Schleife nun aber endlos läuft, wird der Aufruf von
SendZeich nie erreicht, so dass dieser ebenfalls entfernt werden kann.
Für diese geniale Kürzung des Programmcodes (auf die man erst einmal
kommen muss) klopft sich der Compiler zurecht auf die eigene Schulter
und hat dabei keinerlei schlechtes Gewissen, denn das Verhalten des
Programms entspricht immer noch zu 100% den Vorgaben des C-Standards.
Rechter kann's dir der Compiler kaum machen, oder? :)
Harald P. schrieb:> {// hier absichtlich 64 reingeschrieben, um PufferB1 auch zu> bearbeiten
Nö, ist nicht erlaubt.
Du mußt beide Puffer in einer Struct zusammen fassen und für den
Vollzugriff eine Union definieren.
Peter Dannegger schrieb:> Du mußt beide Puffer in einer Struct zusammen fassen und für den> Vollzugriff eine Union definieren.
Oder ein Array mit Größe 64 anlegen, und die Verwaltung der Arrayhälften
selber.
Oliver
Peter Dannegger schrieb:> Harald P. schrieb:>> {// hier absichtlich 64 reingeschrieben, um PufferB1 auch zu>> bearbeiten>> Nö, ist nicht erlaubt.>> Du mußt beide Puffer in einer Struct zusammen fassen und für den> Vollzugriff eine Union definieren.
Was allerdings formal genauso falsch ist. Nur die Wahrscheinlichkeit,
daß es trotzdem funktioniert, ist halt höher.
Rolf Magnus schrieb:> Was allerdings formal genauso falsch ist.
Kannst Du das mal näher erläutern?
Unions aus Structs findet man doch wie Sand am Meer.
Z.B. der Klassiker:
Natürlich ist es legal, sowas zu bauen.
Nur darauf verlassen, daß die Elemente der struct direkt hintereinander
liegen, ist gewagt - es dürfen ja Füllbyte eingefügt werden.
Peter Dannegger schrieb:> Kannst Du das mal näher erläutern?
Formal erlaubt der Standard es eigentlich nur, genau den Union-Member zu
lesen, der auch zuletzt beschrieben wurde.
Peter Dannegger schrieb:> Unions aus Structs findet man doch wie Sand am Meer.> Z.B. der Klassiker:
Und weil dieser "Missbrauch" so extrem gebräuchlich ist, wird es
vermutlich auch kein Compiler jemals wagen, das nicht wie erwartet zu
übersetzen. Und der immer wieder erhobene Zeigefinger ist in dem Fall
dann auch eher akademischer Natur.
Stefan Ernst schrieb:> Formal erlaubt der Standard es eigentlich nur, genau den Union-Member zu> lesen, der auch zuletzt beschrieben wurde.
Mit einer Ausnahme: sofern alle Union-Mitglieder an ihrem Anfang
die exakt gleiche Sequenz haben, darf dieser gemeinsame Teil in
jeder beliebigen Konstellation gelesen werden.
Das ist aber hier nicht der Fall.
Ich hatte gerade nochmal nachgelesen. An vielen anderen Stellen ist
der Standard relativ streng und gestattet Füllbytes eigentlich nur,
wenn dies durch die notwendige Ausrichtung des Datentyps im Speicher
erforderlich ist (damit dürften bei “char” nie welche auftauchen,
denn das ist per defintionem die kleinste adressierbare Einheit).
Bei structs steht jedoch:
“There may be unnamed
padding within a structure object, but not at its beginning.”
Stefan Ernst schrieb:> Peter Dannegger schrieb:>> Unions aus Structs findet man doch wie Sand am Meer.>> Z.B. der Klassiker:>> Und weil dieser "Missbrauch" so extrem gebräuchlich ist, wird es> vermutlich auch kein Compiler jemals wagen, das nicht wie erwartet zu> übersetzen. Und der immer wieder erhobene Zeigefinger ist in dem Fall> dann auch eher akademischer Natur.
Bei GCC steht im Kleingedruckten, dass er solche "Überlagerungen"
behandelt wie erwartet.
Damit kann man es mit GCC zwar verwenden, aber es in nicht portabel.
Und das Gros der Anwender, die solche Hacks einbauen, machen es wohl
nicht, weil sie dieses Kleingedruckte kennen, sondern weil sie -- aus
welchen Gründen auch immer -- glauben, es sei legales C / C++.
Peter Dannegger schrieb:> Rolf Magnus schrieb:>> Was allerdings formal genauso falsch ist.>> Kannst Du das mal näher erläutern?> Unions aus Structs findet man doch wie Sand am Meer.
C schreibt für unions vor, daß man nur das Element lesen darf, das man
davor zuletzt geschrieben hat.
Man kann mit den Unions aus Structs sehr schön und vor allem
übersichtlich serielle Protokolle implementieren.
Man hat eine Struct, wo jedes Element eine bestimmte Bedeutung hat und
jagt diese dann als Byte-Stream über ein Interface.
Und die Gegenseite empfängt den Stream, legt ihn in genau eine gleiche
Struct ab und kann dann alle Elemente bequem zugreifen.
Peter Dannegger schrieb:> Man hat eine Struct, wo jedes Element eine bestimmte Bedeutung hat und> jagt diese dann als Byte-Stream über ein Interface.> Und die Gegenseite empfängt den Stream, legt ihn in genau eine gleiche> Struct ab und kann dann alle Elemente bequem zugreifen.
nein kann man nicht immer. Es gibt immer noch das Big/Little Endian
Problem. Man sollte immer die Protokoll von der Internen Darstellung
trennen.
Spätestens wenn man mehre Protokollversionen unterstützen muss, wird es
sehr unpraktisch.
Peter Dannegger schrieb:> Man kann mit den Unions aus Structs sehr schön und vor allem> übersichtlich serielle Protokolle implementieren.
Man macht das auch häufig so.
Das ändert aber nichts daran, daß dieses Vorgehen nicht konform zum
C-Standard ist.
Und in diesem Fall mit unterschiedlichen Systeme auf beiden Seiten darf
man sich dann doch Gedanken über alingment und byte order machen. Das
bekommt eine union nicht automatisch hin.
Oliver
Vielen Dank für die vielen Hinweise. Tatsächlich lagen die Puffer im
Originalprogramm in einer struct (für ein serielles Protokoll), und mit
einer alten Compilerversion hat auch alles wie gewünscht funktioniert.
Na ja, das C-Feature, daß keine Pufferüberläufe vom Compiler abgefangen
werden - wie z.B. in Pascal möglich -, sollte man tatsächlich nicht
strapazieren.
Richtig wäre es hier, wenn der Compiler eine Warnung herausgeben würde.
Das Verhalten im Kleingedruckten zu verstecken, ist meiner Meinung nach
etwas zu wenig.
Harald
Peter Dannegger schrieb:> Man hat eine Struct, wo jedes Element eine bestimmte Bedeutung hat und> jagt diese dann als Byte-Stream über ein Interface.> Und die Gegenseite empfängt den Stream, legt ihn in genau eine gleiche> Struct ab und kann dann alle Elemente bequem zugreifen.
Ich sehe in deiner Beschreibung keine Union.
Eher einen Cast auf eine Struktur.
Jörg Wunsch schrieb:> Hast du die denn auch alle eingeschaltet? (-Wall -Wextra)
Nein, -Wextra hatte ich nicht. Ändert aber auch nichts (gerade
getestet).
Stefan Rand schrieb:> Ich sehe in deiner Beschreibung keine Union.
Im Originalprogramm ist es eine union. Hier für den Beitrag, habe ich
extrem gekürzt. Aber auch bei union gibt es das beschriebene Verhalten.
Harald
Manchmal wundert es mich schon, um welche Dinge man lange
herumdiskutieren kann.
Das Zusammenfassen von BufferB und BufferB1 zu einem einzigen Array
hätte das Problem gelöst.
Der Zugriff auf BufferB1 erfolgt einfach um 32 Einträge versetzt, das
bringt nun wirklich keine Performance Nachteile.
Schlimmstenfalls könnte man immer noch einen Pointer definieren, der auf
BufferB[32] zeigt.
Jörg Wunsch schrieb:> Ich hatte gerade nochmal nachgelesen. An vielen anderen Stellen ist> der Standard relativ streng und gestattet Füllbytes eigentlich nur,> wenn dies durch die notwendige Ausrichtung des Datentyps im Speicher> erforderlich ist...> Bei structs steht jedoch:>> “There may be unnamed> padding within a structure object, but not at its beginning.”
Jep. Deswegen gibts seit Äonen
1
#pragma pack
und Freunde. Bei AVR-gcc ist das (erfreulicherweise) der Default.
Rolf Magnus schrieb:> C schreibt für unions vor, daß man nur das Element lesen darf, das man> davor zuletzt geschrieben hat.
Nein. Dann wären unions ja sinnlos. Wo soll das stehen?
Die einzige Einschränkung, die ich kenne ist wenn eine union längere und
kürzere Member hat. Dann ist undefiniert was man beim Lesen des längeren
Members bekommt, wenn man zuvor ein kürzeres geschrieben hat. Und zwar
ist genau genommen undefiniert was in den Teilen des längeren Members
steht, die vom kürzeren nicht belegt werden.
Noch anders ausgedrückt: beim Schreiben einer union ist es dem Compiler
gestattet, immer die ganze union zu überschreiben, auch wenn der Zugriff
über ein Member erfolgt, das kleiner ist als die gesamte union. Was
dabei in den zusätzlichen Platz geschrieben wird, ist nicht definiert.
Ganz ähnlich wie mit dem Inhalt von Padding.
XL
Axel Schwenke schrieb:> Nein. Dann wären unions ja sinnlos. Wo soll das stehen?
es steht in der doku. Und sie sind nicht sinnlos, sie sind einfach für
etwas andere gedacht. Man kann mit unions 2 verschiede arten von Daten
speicher ohne den Doppelten Platz zu verbrauchen.
http://msdn.microsoft.com/en-us/library/y9zewe0d.aspx
[...]
If a union of two types is declared and one value is stored, but the
union is accessed with the other type, the results are unreliable. For
example, a union of float and int is declared. A float value is stored,
but the program later accesses the value as an int. In such a situation,
the value would depend on the internal storage of float values. The
integer value would not be reliable.
[...]
andere Interpretieren den Text auch wieder anders, für mich ließt es
sich so das es nicht definiert ist.
Axel Schwenke schrieb:> Rolf Magnus schrieb:>> C schreibt für unions vor, daß man nur das Element lesen darf, das man>> davor zuletzt geschrieben hat.>> Nein. Dann wären unions ja sinnlos. Wo soll das stehen?
Im C-Standard. Das wurde hier schon X mal durchgekaut, inclusive der
entsprechenden Referenzen zum Standard. Etwa
Beitrag "Re: Programmierung von Strukturen in C"
Rolf Magnus schrieb:> C schreibt für unions vor, daß man nur das Element lesen darf, das man> davor zuletzt geschrieben hat.Jörg Wunsch schrieb:> Mit einer Ausnahme: sofern alle Union-Mitglieder an ihrem Anfang> die exakt gleiche Sequenz haben, darf dieser gemeinsame Teil in> jeder beliebigen Konstellation gelesen werden.
Und das kann man nutzen, um in der Union zu speichern, was (welche
Struct) man zuletzt geschrieben hat.
Axel Schwenke schrieb:> Nein. Dann wären unions ja sinnlos. Wo soll das stehen?
Die angedachte Rolle von unions ist nicht Zerlegung und Zusammenbau
von Daten, auch wenn sie oft dafür verwendet werden. Sondern die
Speicherung alternativer Inhalte, z.B. in Abhängigkeit von einer
Kennung. Also beispielsweise sinngemäss:
1
struct{
2
enum{Text,Number,Callback}tag;
3
union{
4
chartext[10];
5
longnumber;
6
void(*callback)(void);
7
};
8
};
In diesem Beispiels wird abhängig von <tag> der passende Inhalt
platzsparend verwendet. Aber es wird nie der falsche Zweig verwendet.
Manche Sprachen verbinden in den entsprechenden Konstrukten die
Varianten explizit mit dem Tag und kontrollieren das ggf. beim Zugriff.
Die angedachte Rolle von unions stammt aus der Urzeit der
Programmierung, und entsprach den Fortran-common-Blöcken. Damals musste
oder wollte man halt Hauptspeicher sparen, in dem man ihn mehrfach
verwenden konnte.
Oliver
Peter Dannegger schrieb:> Kannst Du das mal näher erläutern?> Unions aus Structs findet man doch wie Sand am Meer.> Z.B. der Klassiker:union b_w {> struct{> uint8_t l;> uint8_t h;> }b;> uint16_t w;> };
und die Reihenfolge (High-Byte/Low-Byte) ist z.B. auch schon wieder
Architekturabhängig.
Oliver schrieb:> und entsprach den Fortran-common-Blöcken.
Die natürlich genauso häufig als „typecast von hinten durch die Brust
ins Auge“ missbraucht worden sind, wie die unions in C. ;-)
Johann L. schrieb:> Axel Schwenke schrieb:>> Rolf Magnus schrieb:>>> C schreibt für unions vor, daß man nur das Element lesen darf, das man>>> davor zuletzt geschrieben hat.>>>> Nein. Dann wären unions ja sinnlos. Wo soll das stehen?>> Im C-Standard. Das wurde hier schon X mal durchgekaut, inclusive der> entsprechenden Referenzen zum Standard. Etwa>> Beitrag "Re: Programmierung von Strukturen in C"
Nur steht dort nirgends, was Rolf oben behauptet.
Ich copypaste das mal hierher:
> When a value is stored in a member of an object of union type,> the bytes of the object representation that do not correspond> to that member but do correspond to other members take unspecified> values,
das ist genau das, was ich oben geschrieben habe. Und eine wesentlich
weniger starke Einschränkung als das was Rolf behauptet.
Insbesondere sehe ich da keine Einschränkung, bei dieser
1
union{
2
doubled;
3
uint64_ti;
4
};
in beliebiger Reihenfolge auf union members zuzugreifen. Mit definierten
Resultaten.
XL
Und was ist daran zu deuteln?
Und in 6.7.2.1 steht es auch:
> [...] The value of at most one of the members can> be stored in a union object at any time.
Mal angenommen ich würde gerne diesen "Trick" fürs oben angesprochene
serielle Protokoll benutzen:
Was wäre denn "weniger unelegant": einen Zeiger auf einen Strukturzeiger
(natürlich gepackt) casten oder die unsachgemäse Verwendung einer Union?
Karlo schrieb:> Was wäre denn "weniger unelegant": einen Zeiger auf einen Strukturzeiger> (natürlich gepackt) casten oder die unsachgemäse Verwendung einer Union?
Die umgecasteten Zeiger werden unter Unix gerne genommen.
Beispiel:
1
structsockaddr{
2
sa_family_tsa_family;
3
charsa_data[14];
4
};
5
6
7
structsockaddr_un{
8
unsignedshortsun_family;/* AF_UNIX */
9
charsun_path[108];
10
};
11
12
13
structsockaddr_in{
14
shortsin_family;// e.g. AF_INET
15
unsignedshortsin_port;// e.g. htons(3490)
16
structin_addrsin_addr;// see struct in_addr, below
17
charsin_zero[8];// zero this if you want to
18
};
usw.
Gemeinsam haben sie nur den short (*) am Anfang, damit der aufgerufene
Code weis, wie er den Pointer casten muss, und eine
16-Byte-Minimallänge...
*) Der Code oben ist aus verschiedenen Quellen zusammenkopiert. Echt
keine Absicht, dass da drei verschiedene Typen auftauchen :)
Karlo schrieb:> Was wäre denn "weniger unelegant": einen Zeiger auf einen Strukturzeiger> (natürlich gepackt) casten
Womit du dann auch hier wieder bei undefined behaviour landest :-)
> oder die unsachgemäse Verwendung einer Union?
-> beides ist undefined behaviour. D.h. aus dieser Sicht betrachtet,
schenken sich diese beiden Möglichkeiten nix.
(Wobei ich mir jetzt nicht sicher bin, ob es undefined oder unspecified
behaviour ist. Ist aber im Grunde egal)
Nehmen wir mal an, es gäbe eine Maschine, die jedes Datenwort im
Speicher vorsorglich mit einem Typencode versieht. Also zwischen Code,
Ganzzahlen, Fliesskommzahlen und Text unterscheidet. Und bei falschem
Zugriff einen Alarm auslöst. Das könnte die gängige C Praxis ziemlich
durcheinander wirbeln. Egal ob man Unions oder gecastete Pointer
verwendet.
A.K.
> Nehmen wir an
Exakt
Im Grunde geht es doch darum:
(Annahme: sizeof(int) == sizeof(float))
1
inti=85;
2
float*ptr=(float*)&i;
Der C-Standard will nicht definieren, was in diesem Falle das Ergebnis
von
1
floatk=*ptr;
sein soll. Da der C-Standard über Dinge wie Byte-Order bzw. Floating
Point System keine Aussage macht, kann er das auch gar nicht. Das
Ergebnis kann eine gültige Floating Point Zahl sein, das Ergebnis kann
NaN sein, der Versuch des Auslesens dieser Bytesquenz kann aber auch zu
einem sofortigen Trap in einer Floating Point Hardware führen. All das
ist ausserhalb des Definitionsbereichs des C-Standards.
Daher wurde sehr darauf geachtet, alle Dinge, die in genau das gleiche
Horn stossen (eine in einem Datentyp gültige Bytesequenz muss nicht
notwendigerweise in einem anderen Datentyp ebenfalls eine gültige
Bytesequenz sein) als unspecified zu brandmarken.
Karlo schrieb:> Was wäre denn "weniger unelegant": einen Zeiger auf einen Strukturzeiger> (natürlich gepackt) casten oder die unsachgemäse Verwendung einer Union?
Was für mich entscheidend ist, ist, daß Unions im Gegensatz zu Casts
eigentlich für was völlig anderes gedacht sind. Meisten sind sie auch
umständlich zu schreiben und schlechter lesbar.
Ich hab keine Ahnung, warum da so oft und gerne Unions verwendet werden.
Ich habe das übrigens nicht erfunden. Solche Maschinen gab es wirklich,
die Telefunken-Grossrechner TR4 und TR440:
http://www.qslnet.de/member/dj4kw/tr4.htm
Obwohl C und die TR440 ungefähr gleichzeitig entstanden bezweifle ich
allerdings, dass es dafür jemals einen C Compiler gab.
Harald P. schrieb:> Richtig wäre es hier, wenn der Compiler eine Warnung herausgeben würde.> Das Verhalten im Kleingedruckten zu verstecken, ist meiner Meinung nach> etwas zu wenig.
Das sehen die GCC-Macher offensichtlich genauso, deswegen haben sie in
der aktuellen Version (4.8.1) eine entsprechende Warnung vorgesehen, für
die man sogar nicht einmal -Wall angeben muss (s. mein Beitrag weiter
oben). Zudem "optimiert" die neue Version dein Beispiel nicht so krass,
weswegen die Warnung eigentlich fast schon wieder überflüssig ist.
Johann L. schrieb:> Und was ist daran zu deuteln?>> Und in 6.7.2.1 steht es auch:>>> [...] The value of at most one of the members can>> be stored in a union object at any time.
Wenn man den Satz davor auch noch zitiert:
> The size of a union is sufficient to contain the largest of> its members. The value of at most one of the members can be> stored in a union object at any time.
dann wird klar, daß es hier um die Größe der union geht und daß man da
zu einer Zeit immer nur ein member drin speichern kann und nicht mehrere
gleichzeitig (im Gegensatz zu einer struct).
Die Behauptung war aber:
> C schreibt für unions vor, daß man nur das Element lesen darf, das man> davor zuletzt geschrieben hat.
Und ich hätte gern dafür einen Beleg aus dem Standard. Bis ich den
bekomme, halte ich das für eine UL.
XL
Axel Schwenke schrieb:>> C schreibt für unions vor, daß man nur das Element lesen darf, das man>> davor zuletzt geschrieben hat.>> Und ich hätte gern dafür einen Beleg aus dem Standard. Bis ich den> bekomme, halte ich das für eine UL.
Ist in C99 erlaubt, C11 habe ich zuhause nicht zur Hand.
Siehe informativen Annex J, Unspecified behavior:
- The value of a union member other than the last one stored into
(6.2.6.1)
Axel Schwenke schrieb:> Und ich hätte gern dafür einen Beleg aus dem Standard.
"The size of a union is sufficient to contain the largest of its
members."
hat mit
"The value of at most one of the members can be stored in a union
object at any time."
nichts zu tun. (Also im Sinne voneinander abhängig)
Wenn gesagt wird, dass nur ein Element der Union gespeichert werden darf
(das andere/ die anderen also nicht) und man daraus schlussfolgert, dass
das andere dafür aber gelesen werden kann...
Axel Schwenke schrieb:> Und ich hätte gern dafür einen Beleg aus dem Standard. Bis ich den> bekomme, halte ich das für eine UL.
Es gibt 2 Bereiche unspezifizierten Verhaltens:
(1) Jene, die ausdrücklich so bezeichnet werden,
(2) und jene, für die keine explizite Spezifikation existiert.
Wobei Punkt (2) dort von Bedeutung ist, wo das zu erwartende Verhalten
nicht selbstverständlich ist und sich nicht als logische Folgerung aus
andererer Spezifizikation ergibt.
Kurz: nicht spezifiziert = unspezifiziert.
Ralf G. schrieb:> und man daraus schlussfolgert, dass> das andere dafür aber gelesen werden kann...
Und diese Folgerung ist falsch, wie ich eine halbe Stunde vor deinem
Beitrag bereits klar gezeigt habe.
A. K. schrieb:> Es gibt 2 Bereiche unspezifizierten Verhaltens:
Die Behauptung war aber "undefiniert" ("darf nicht"), nicht
"unspezifiziert".
Das Verhalten beim Zugriff auf den "anderen" Unionmember ist aber eben
unspezifiziert, nicht undefiniert.
Stefan Rand schrieb:> Die Behauptung war aber "undefiniert" ("darf nicht"), nicht> "unspezifiziert".
Die Unterscheidung zwischen undefiniertem und unspezifiziertem Verhalten
wird dort müssig, wo es weder definiert noch spezifiziert ist. ;-)
Aber wenns dir so lieber ist: Eine Operation, bei der sich je nach zu
Grunde liegender Maschine ebendiese in den Crashdump verabschieden kann
darf wohl als undefiniert gelten.
Stefan Rand schrieb:> Ralf G. schrieb:>> und man daraus schlussfolgert, dass>> das andere dafür aber gelesen werden kann...>> Und diese Folgerung ist falsch, wie ich eine halbe Stunde vor deinem> Beitrag bereits klar gezeigt habe.
Deswegen die drei "Hmm-Ich-wundere-mich-sehr"-Punkte am Ende! ;-)
-----
Dass jetzt "jeder" solche "Spezial-Casts" verwendet, ist eine ganz
andere Sache. Nur darf er dann nicht rumheulen, falls es irgendwann aus
irgendwelchen Gründen nicht funktioniert!
Wie war doch gleich die Frage? ;-)
Mal zwischendurch einige weniger philosophische Gedanken dazu. Auf Daten
in anderer Form zuzugreifen, als der gespeicherte Datentyp vorgibt, wird
wohl nur auf eher exotischen Maschinen direkt zum Crash führen, es sei
denn es sind NaNs dabei. Aber es kann auch auf ganz normalen Maschinen
unangenehme Folgen haben:
- Im Zusammenhang mit Aliasing eröffnen sich ziemliche Untiefen.
- Es kann auf sehr real existierenden Maschinen grässlich langsam sein.
Reale Maschinen sind nämlich nicht dafür eingerichtet, so etwas
effizient auszuführen - ganz im Gegenteil.
Stefan Rand schrieb:> Axel Schwenke schrieb:>> ... ich hätte gern dafür einen Beleg aus dem Standard. Bis ich den>> bekomme, halte ich das für eine UL.> Siehe informativen Annex J, Unspecified behavior:> - The value of a union member other than the last one stored into> (6.2.6.1)
Danke. Dann habe ich das wohl richtig gesehen.
Zitat:
> If the member used to read the contents of a union object is not> the same as the member last used to store a value in the object,> the appropriate part of the object representation of the value is> reinterpreted as an object representation in the new type ...> This might be a trap representation.
Der letzte Satz meint, daß das Ergebnis der Re-Interpretation sicherlich
implementationsabhängig ist (mindestens architekturabhängig bzw. noch
genauer: abhängig von der internen Repräsentation der verschiedenen
Datentypen). Das ist aber genau nicht mein Problem. Denn in den Fällen
wo ich das anwenden will - z.b. um einen längeren Datentyp in eine
Bytefolge zu zerlegen (Marshalling) ist mir das entweder komplett
schnuppe oder ich kenne die interne Repräsentation genau genug für den
intendierten Zweck.
Ich möchte noch hinzufügen, daß der oben zitierte Text in C99 als
ergänzende Fußnote aufgenommen wurde, eben weil C90 hier eine viel zu
weit gefaßte Bedeutung von "unspecified" enthielt. Ich gehe davon aus,
daß das schon immer genau so gemeint, aber unglücklich ausgedrückt
war.
(http://stackoverflow.com/questions/11639947/is-type-punning-through-a-union-unspecified-in-c99-and-has-it-become-specified)
XL
A. K. schrieb:> Die Unterscheidung zwischen undefiniertem und unspezifiziertem Verhalten> wird dort müssig, wo es weder definiert noch spezifiziert ist. ;-)>> Aber wenns dir so lieber ist: Eine Operation, bei der sich je nach zu> Grunde liegender Maschine ebendiese in den Crashdump verabschieden kann> darf wohl als undefiniert gelten.
Wenn du nicht weißt, was undefiniert und unspezifiziert im C-Standard
bedeuten, dann halt dich doch einfach mal zurück. Man muß nicht zu jedem
Thema quaken.
Und selbstverständlich "verabschiedet" sich kein C-System bei Zugriff
auf den "falschen" Union-Member. Der C-Standard verbietet es.
Stefan Rand schrieb:> Und selbstverständlich "verabschiedet" sich kein C-System bei Zugriff> auf den "falschen" Union-Member. Der C-Standard verbietet es.
Es kann Datentypen mit unzulässige Werten/Bitpatterns geben, deren
Verwendung zu einer Exception führt. Da kommen Fliesskommadaten in
Frage, aber auch Pointer (vgl. Intel 286 Segmentierung). Auch eine
Maschine mit getaggten Speicherworten (wie oben mal skizziert) wäre m.E.
als Zielmaschine von C zulässig.
Und nun würde ich gerne mal sehen, wo der C Standard verbietet, dass
eine Maschine bei unzulässiger Befehls/Daten-Kombination das Programm
abwirft. Bei Kombinationen, die im korrekten Ablauf eines Programmes
überhaupt nicht auftreten können.
A. K. schrieb:> Es kann Datentypen mit unzulässige Werten/Bitpatterns geben, deren> Verwendung zu einer Exception führt.
Das ist das, was der Standard "trap representation" nennt. Und das ist
ebenfalls ausdrücklich ausgeschlossen.
Meine Güte, irgendwann reicht es aber wirklich mal. Wenn du mir ans Bein
pinkeln willst, dann nimm den Standard zur Hand. Axel hat ausdrücklich
nach dem Standard gefragt, nicht nach deiner Intuition.
Stefan Rand schrieb:> Meine Güte, irgendwann reicht es aber wirklich mal. Wenn du mir ans Bein> pinkeln willst, dann nimm den Standard zur Hand. Axel hat ausdrücklich> nach dem Standard gefragt, nicht nach deiner Intuition.
Und ich habe geantwortet, dass Dinge, die vom Standard nicht explizit
ausgeschlossen werden, dennoch ausgeschlossen sein können. Wo habt ihr
da ein Problem?
Abgesehen davon will ich hier niemandem ans Bein pinkeln.
A. K. schrieb:> Und ich habe geantwortet, dass Dinge, die vom Standard nicht explizit> ausgeschlossen werden, dennoch ausgeschlossen sein können. Wo habt ihr> da ein Problem?
Der Standard schließt aber explizit aus, daß es nicht funktioniert.
Er sagt ausdrücklich, wenn du den "falschen" Member liest, ist der Wert
unspezifiziert.
Und das bedeutet, da kann jeder "funktionierende" (nicht
exception-auslösende Wert) rauskommen.
Und das schließt deine Fantasien von wegen "stürzt ab" schlicht und
ergreifend aus.
Stefan Rand schrieb:> Und das schließt deine Fantasien von wegen "stürzt ab" schlicht und> ergreifend aus.
Nochmal zur Klarstellung: Deiner Ansicht darf also keine vom Compiler
erzeugte Ladeoperation jedweder Art abstürzen, wenn sie Daten aus einer
Union läd? Egal wie schräg die geladenen Daten sind? Weil das vom
Standard zwar als unspezifiziert aber nicht als undefiniert bezeichnet
wird. Na dann...
Korrektur: Deiner Ansicht darf also keine vom Compiler erzeugte
Ladeoperation jedweder Art abstürzen, wenn sie Daten aus einer Union
läd? Egal wie schräg die geladenen Daten sind? Weil das vom Standard an
der erwähnten Stelle ausdrücklich als unspezifiziert bezeichnet wird.
Habe ich das so richtig verstanden?
Stefan Rand schrieb:> A. K. schrieb:>> Es kann Datentypen mit unzulässige Werten/Bitpatterns geben, deren>> Verwendung zu einer Exception führt.>> Das ist das, was der Standard "trap representation" nennt. Und das ist> ebenfalls ausdrücklich ausgeschlossen.
Nein, ist es eben nicht. Schau doch, was Axel Schwenke oben zitiert
hat: “This might be a trap representation.”
Es ist also sicherlich OK, das zu benutzen (auf unportable Weise),
solange man genug über die Interna kennt, dass man sich sicher
sein kann, dass es im konkret vorliegenden Fall eben keine
“trap representation” darstellt.
Exakt.
Im Annex J steht zwar, dass es unspezifiziert ist, aus einer Union etwas
anderes zu lesen, als man vorher reingeschrieben hat. Aber das macht es
nicht in jeder Situation definiert. Es kann sowohl unspezifiziert als
auch undefiniert sein. Gemäss C99 6.2.6.1.5 kann es Bitpatterns geben,
die beim Zugriff eine Exception auslösen - besagte trap representations.
Und dieser Passus gilt universell, Unions machen da keine Ausnahme.
Stefan Rand schrieb:> Er sagt ausdrücklich, wenn du den "falschen" Member liest, ist der Wert> unspezifiziert.
Genau genommen sagt er, dass die Operation unspezifiziert ist.
> Und das bedeutet, da kann jeder "funktionierende" (nicht> exception-auslösende Wert) rauskommen.
Das ist nicht die Bedeutung von unspecified.
unspecified bedeutet, dass sich der Standard raushält, was genau
passiert, dass aber der Compilerhersteller dokumentieren muss, was
konkret passsiert.
Im Gegensatz zu undefined. Da darf auch alles mögliche passieren, aber
der Compilerhersteller ist nicht in der Pflicht dafür ein garantiertes
Verhalten (welches auch immer das ist) zu implementieren oder zu
dokumentieren.
> Und das schließt deine Fantasien von wegen "stürzt ab" schlicht und> ergreifend aus.
Wenn der Compilerbauer dokumentiert, dass eine derartige Operation
abstürzt, dann ist das aus Sicht des Standards ok.
Karl Heinz Buchegger schrieb:> unspecified bedeutet, dass sich der Standard raushält, was genau> passiert, dass aber der Compilerhersteller dokumentieren muss, was> konkret passsiert.
Och, Karl-Heinz! Das weißt du aber besser.
Was du meinst ist "implementation-defined".
"unspecified" bedeutet zwei oder mehrere Möglichkeiten, C-System sucht
sich eine raus, ohne das dokumentieren zu müssen.
Stefan Rand schrieb:> A. K. schrieb:>> Es gibt 2 Bereiche unspezifizierten Verhaltens:>> Die Behauptung war aber "undefiniert" ("darf nicht"), nicht> "unspezifiziert".
Das hängt nun davon ab, wie du "darf nicht" definierst, was offenbar
anders ist als meine Definition. Unspezifiziert heißt in dem Kontext,
daß jeder Compiler es manchen kann, wie er lustig ist, es in jeder neuen
Version ändern kann und daß das konkrete Verhalten auch nirgends
dokumentiert sein muss.
Undefiniert heißt zusätzlich noch, daß ab dieser Stelle beliebiges
passieren darf, bis hin zur Selbstzerstörung des Universums.
"Darf nicht" schließt für mich erstmal beides ein, sofern ich nicht
einen guten Grund habe und mir darüber klar bin, was meine
Zielplattformen draus machen. Nun ist ein Cast in der Hinsicht auch
nicht so viel besser, als eine union, aber warum ich ihn dennoch
bevorzuge, hab ich schon geschrieben.
Die dritte Möglichkeit, die auch explizit in der ISO-Norm erwähnt wird,
wurde aber noch gar nicht angesprochen, nämlich memcpy. Jedes beliebige
Objekt (außer Bitfield-Members) kann als Array aus unsigned char
interpretiert werden, indem es per memcpy() in ein solches kopiert wird.
Das ist dann meiner Ansicht nach implementation defined und nicht mehr
unspecified.
Rolf Magnus schrieb:> Das hängt nun davon ab, wie du "darf nicht" definierst, was offenbar> anders ist als meine Definition.
Wir sind uns doch wohl einig, daß Programme, die unspecified behavior
auslösen, konforme Programme sind, während Programme, die undefined
behavior auslösen, eben gar keine C-Programme sind?
Das ist meine Richtschnur bei "darf nicht".
unspecified ist okay, wenn man weiß, daß man sich das verhalten
hereinholt (in der praxis programmiert man meistens für eine
überschaubare Zahl von Zielplattformen). undefined ist nicht okay (auch
wenn mans manchmal trotzdem macht).
Es steht in 6.2.6.1.5 auch ausdrücklich drin, dass man mit character
types an alle Daten lesend ran darf. Aber eben nur mit denen. Wie auch
beim Aliasing haben chars eine Sonderrolle.
Weshalb
u.i = 1; ... = u.x[1];
bei
union { uint32_t i; uint8_t x[4]; } u;
etwas anders zu bewerten ist als
union { uint32_t i; uint16_t x[2]; } u;
Stefan Rand schrieb:> Karl Heinz Buchegger schrieb:>> unspecified bedeutet, dass sich der Standard raushält, was genau>> passiert, dass aber der Compilerhersteller dokumentieren muss, was>> konkret passsiert.>> Och, Karl-Heinz! Das weißt du aber besser.>> Was du meinst ist "implementation-defined".
Damn.
Richtig.
Ich seh schon. Ich sollte mich da raushalten. Ist schon lange her, dass
ich den Standard gelesen habe und vieles davon hab ich vergessen bzw.
mag sich auch in den Details geändert haben.
1
3.17.3
2
1 unspecified value
3
valid value of the relevant type where this International Standard
4
imposes no requirements on which value is chosen in any instance
5
2 NOTE An unspecified value cannot be a trap representation.
Auch interessant
1
6.2.6.1
2
5 Certain object representations need not represent a value of the
3
object type. If the stored value of an object has such a representation
4
and is read by an lvalue expression that does not have character type,
5
the behavior is undefined.
zusammen mit einigen anderen Aussagen über unions sieht es wohl so aus,
dass man die Verwendung einer union zum Zwecke der Konvertierung in
Bytes legalisiert hat.
Stefan Rand schrieb:> Wir sind uns doch wohl einig, daß Programme, die unspecified behavior> auslösen, konforme Programme sind, während Programme, die undefined> behavior auslösen, eben gar keine C-Programme sind?
Programme mit unspecified behavior produzieren ein Ergebnis, das aber
nicht notwendigerweise vorhersagbar ist. Programme mit undefined
behavior sind nicht notwendigerweise übersetzbar und produzieren auch
nicht notwendigerweise ein Ergebnis.
Karl Heinz Buchegger schrieb:> zusammen mit einigen anderen Aussagen über unions sieht es wohl so aus,> dass man die Verwendung einer union zum Zwecke der Konvertierung in> Bytes legalisiert hat.
Wobei man dabei wohl nicht ausgerechnet die unions um Auge hatte, als
vielmehr die generelle Möglichkeit zur Byte-Serialisierung binärer
Daten. Egal ob mit unions oder mit der verbreiteteren Methode über
Pointer. Andernfalls liessen sich lowlevel Funktionen aus dem
I/O-Bereich oder der Speicherverwaltung oft nicht in C formulieren.
In diesem Zusammenhang darf man auch die Ausnahmestellung von
char-Pointern beim Aliasing sehen.
Stefan Rand schrieb:> Rolf Magnus schrieb:>> Das hängt nun davon ab, wie du "darf nicht" definierst, was offenbar>> anders ist als meine Definition.>> Wir sind uns doch wohl einig, daß Programme, die unspecified behavior> auslösen, konforme Programme sind, während Programme, die undefined> behavior auslösen, eben gar keine C-Programme sind?
Konform ja, aber (nach ISO-C) nicht strikt konform.
> Das ist meine Richtschnur bei "darf nicht".>> unspecified ist okay, wenn man weiß, daß man sich das verhalten> hereinholt
Man weiß halt nicht unbedingt, welches Verhalten man sich reinholt. Es
muß ja nicht mal dokumentiert sein, was passiert. Manchmal kommt man
halt nicht drum herum, gerade in der µC-Programmierung. Es ist also
insofern ok, als daß einem halt nichts anderes übrig bleibt. Also
ungefähr in dem Maße, wie es auch ok ist, wenn einem auf der Landstraße
ein Überholer entgegenkommt, die Straße zu verlassen und ins Gemüse zu
fahren. ;-)
A. K. schrieb:> Es steht in 6.2.6.1.5 auch ausdrücklich drin, dass man mit character> types an alle Daten lesend ran darf.
Ich hab jetzt nur C99 da. Dort steht in 6.2.6.1.4, daß man sie in ein
Array aus unsigned char kopieren darf, nicht aber, daß man sie direkt
lesen darf. Das Kopieren kann man per memcpy() erledigen. Das wird
zugegebenermaßen in der Regel auch nichts anderes machen, als die Daten
byteweise lesen, aber da es eine Standardfunktion ist, kann die
Implementierung auch beliebige Tricks anwenden, die dem Benutzer nicht
zur Verfügung stehen.
Rolf Magnus schrieb:>> Es steht in 6.2.6.1.5 auch ausdrücklich drin, dass man mit character>> types an alle Daten lesend ran darf.>> Ich hab jetzt nur C99 da.
Ich bezog mich ebenfalls auf C99. Allerdings steht auch in 6.2.6.1.4
nur, dass man per memcpy lesend in uchars kopieren darf. Davon dass
dies auch schreibend zulässig sei, steht dort nichts.
C11 wirkt auf den ersten Blick unverändert.
Harald P. schrieb:> Im Originalprogramm ist es eine union. Hier für den Beitrag, habe ich> extrem gekürzt.
Problem exists between keyboard and chair. ;-)
Also, um auf das Ausgangsproblem zurückzukommen. Wünschenswert wäre ein
Compilerverhalten, bei dem der Übersetzer eine Warnung (oder sogar
Error) bei erkannten Programmierfehlern (wie hier eine
Feldgrenzenüberschreitung) ausgibt, anstatt Code zu erzeugen, der ins
Nirwana führt.
Harald P. schrieb:> Also, um auf das Ausgangsproblem zurückzukommen. Wünschenswert wäre ein> Compilerverhalten, bei dem der Übersetzer eine Warnung (oder sogar> Error) bei erkannten Programmierfehlern (wie hier eine> Feldgrenzenüberschreitung) ausgibt, anstatt Code zu erzeugen, der ins> Nirwana führt.
Dann verwendest Du vielleicht die für Dich falsche Programmiersprache?
In C darf man gnadenlos über die Grenzen von Arrays hinaus schreiben.
Niemand hindert Dich daran. In Java z.B. sieht das anders aus.
Harald P. schrieb:> Also, um auf das Ausgangsproblem zurückzukommen. Wünschenswert wäre ein> Compilerverhalten, bei dem der Übersetzer eine Warnung (oder sogar> Error) bei erkannten Programmierfehlern (wie hier eine> Feldgrenzenüberschreitung) ausgibt, anstatt Code zu erzeugen, der ins> Nirwana führt.
ACK. -Warray-bounds sollte hier m.E. meckern, tut es aber nicht
(getestet mit 4.7 und 4.8).