Merkwürdige Compileroptimierung, wo liegt (mein) Denkfehler

#3317143
Lesenswert?

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_t PufferB[32], PufferB1[32];
2

3

4
void SendZeich(uint8_t Zeich)
5
{
6
 UDR = Zeich;
7
}
8

9
int main(void)
10
{
11
  uint8_t zmax = 25;
12

13
  while (zmax--)
14
  {//  hier absichtlich 64 reingeschrieben, um PufferB1 auch zu bearbeiten
15
   for (uint8_t j=0; j<64; j++) if (PufferB[j]>127) PufferB[j] = '.';
16
   SendZeich(PufferB[zmax]);
17
  }
18
  
19
 return(0);
20
}

Hier das dissaemblierte Ergebnis:
1
void SendZeich(uint8_t Zeich)
2
{
3
 UDR = Zeich;
4
  48:  8c b9         out  0x0c, r24  ; 12
5
  4a:  08 95         ret
6

7
0000004c <main>:
8
}
9

10
int main(void)
11
{
12
  4c:  80 e0         ldi  r24, 0x00  ; 0
13
  uint8_t zmax = 25;
14

15
  while (zmax--)
16
  {//               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
#3317218
Lesenswert?

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.
Moderator #3317468
Lesenswert?

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:
1
warning: iteration 32u invokes undefined behavior [-Waggressive-loop-optimizations]
2
    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? :)
Gast #3317655
Lesenswert?

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.
#3317698
Lesenswert?

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.
Moderator Persönliche Seite #3317722
Lesenswert?

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.”
Persönliche Seite #3317764
Lesenswert?

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++.
#3317821
Lesenswert?

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.
Gast #3317830
Lesenswert?

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.
Gast #3317836
Lesenswert?

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
#3317902
Lesenswert?

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
#3317921
Lesenswert?

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.
#3317951
Lesenswert?

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
#3318014
Lesenswert?

Harald P. schrieb:
>
1
> uint8_t PufferB[32], PufferB1[32];
2
> 
3
> 
4
> void SendZeich(uint8_t Zeich)
5
> {
6
>  UDR = Zeich;
7
> }
8
> 
9
> int main(void)
10
> {
11
>   uint8_t zmax = 25;
12
> 
13
>   while (zmax--)
14
>   {//  hier absichtlich 64 reingeschrieben, um PufferB1 auch zu 
15
> bearbeiten
16
>    for (uint8_t j=0; j<64; j++) if (PufferB[j]>127) PufferB[j] = '.';
17
>    SendZeich(PufferB[zmax]);
18
>   }
19
> 
20
>  return(0);
21
> }
22
>

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.
#3318189
Lesenswert?

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
Gast #3318205
Lesenswert?

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.
Persönliche Seite #3318226
Lesenswert?

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"
#3318235
Lesenswert?

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.
#3318236
Lesenswert?

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
      char text[10];
5
      long number;
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.
#3318277
Lesenswert?

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
  double d;
3
  uint64_t i;
4
};
in beliebiger Reihenfolge auf union members zuzugreifen. Mit definierten 
Resultaten.


XL
#3318372
Lesenswert?

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
struct sockaddr {
2
     sa_family_t sa_family;
3
      char        sa_data[14];
4
};
5

6

7
struct sockaddr_un {
8
    unsigned short sun_family;  /* AF_UNIX */
9
    char sun_path[108];
10
};
11

12

13
struct sockaddr_in {
14
    short            sin_family;   // e.g. AF_INET
15
    unsigned short   sin_port;     // e.g. htons(3490)
16
    struct in_addr   sin_addr;     // see struct in_addr, below
17
    char             sin_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 :)
#3318389
Lesenswert?

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)
#3318398
Lesenswert?

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.
#3318413
Lesenswert?

A.K.

> Nehmen wir an

Exakt


Im Grunde geht es doch darum:
(Annahme: sizeof(int) == sizeof(float))
1
  int i = 85;
2
  float * ptr = (float*) &i;

Der C-Standard will nicht definieren, was in diesem Falle das Ergebnis 
von
1
  float k = *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.
Gast #3318418
Lesenswert?

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.
Moderator #3318459
Lesenswert?

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.
#3318519
Lesenswert?

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
#3318587
Lesenswert?

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)
#3318635
Lesenswert?

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...
#3318649
Lesenswert?

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.
#3318712
Lesenswert?

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.
#3318715
Lesenswert?

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? ;-)
#3318731
Lesenswert?

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.
#3318865
Lesenswert?

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
#3318976
Lesenswert?

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.
#3318987
Lesenswert?

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.
#3318989
Lesenswert?

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.
#3319000
Lesenswert?

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.
#3319001
Lesenswert?

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.
#3319005
Lesenswert?

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...
Moderator Persönliche Seite #3319172
Lesenswert?

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.
#3319378
Lesenswert?

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.
#3319408
Lesenswert?

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.
#3319417
Lesenswert?

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.
Gast #3319426
Lesenswert?

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.
#3319443
Lesenswert?

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).
#3319453
Lesenswert?

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.
#3319465
Lesenswert?

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.
#3319473
Lesenswert?

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.
Gast #3319606
Lesenswert?

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.
#3319668
Lesenswert?

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.
#3319697
Lesenswert?

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.
Persönliche Seite #3319716
Lesenswert?

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

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