Forum: Compiler & IDEs ARM-GCC: No strict alias


von Walter T. (nicolas)


Lesenswert?

Guten Morgen,

die "strict aliasing rule" besagt ja, dass zwei Zeiger, die einer 
Funktion übergeben werden, vom Compiler als voneinander unabhängig 
betrachtet werden können, was gewisse Optimierungen (Variablen in 
Registern) erlaubt.

Eine Ausnahme ist ja die Standardfunktion memmove(), bei der sich die 
über die Zeiger bechriebenen Speicherbereiche überschneiden dürfen.

Ich habe gerade einen ähnlichen Fall, bei der einer Funktion einen 
Sende- und ein Empfangspuffer übergeben wird, wobei es durchaus nicht 
verboten sein soll, dass diese identisch sind.


1
/** Daten per SPI senden unter Zuhilfenahme des DMA
2
 *
3
 * Die funktion nutzt den DMA, um die maximale Datenrate zu erreichen. Sie 
4
 * ist allerdings nicht optimal schnell, da sie erst zurueckkehrt, wenn 
5
 * alles ubertragen ist. Optimierungpotenzial ist also vorhanden, aber 
6
 * momentan unnoetig, da selbst bei einem TFT das Update ausreichend
7
 * schnell ist.
8
 *
9
 * @param[out] receive: Daten von Slave
10
 * @param[in] send:  Daten an Slave
11
 * @param[in] len: Pufferlaenge, maximal 65536
12
 *
13
 * Gilt receive == send, wird der Puffer ueberschrieben
14
 * Gilt receive == NULL wird nur geschrieben, nichts gelesen
15
 * Gilt send == NULL werden nur Nullen geschrieben */
16
void spi_dma_transmitbuffer(uint8_t *receive, const uint8_t *send, uint16_t len)
17
{
18
  SPI_TypeDef* SPIx;
19
  DMA_Stream_TypeDef *DMA_StreamTx, *DMA_StreamRx;
20
  uint32_t            DMA_FlagTcif_Tx, DMA_FlagTcif_Rx;
21
22
  switch( SPI_HARD_DMA )
23
  {
24
    case spi_hard_none:
25
      /* Keine Hardware-SPI-Unterstuetzung */
26
      return;
27
28
    case spi_hard_STM32F4XX_SPI1_PA5_PA6_PA7:
29
      SPIx = SPI1;
30
      DMA_StreamTx = DMA2_Stream3;
31
      DMA_StreamRx = DMA2_Stream2;
32
      DMA_FlagTcif_Tx = DMA_FLAG_TCIF3;
33
      DMA_FlagTcif_Rx = DMA_FLAG_TCIF2;
34
      break;
35
36
    case spi_hard_STM32F4XX_SPI2_PB13_PB14_PB15:
37
      SPIx = SPI2;
38
      DMA_StreamTx = DMA1_Stream4;
39
      DMA_StreamRx = DMA1_Stream3;
40
      DMA_FlagTcif_Tx = DMA_FLAG_TCIF4;
41
      DMA_FlagTcif_Rx = DMA_FLAG_TCIF3;
42
      break;
43
44
    case spi_hard_STM32F4XX_SPI3_PC10_PC11_PC12:
45
      SPIx = SPI3;
46
      DMA_StreamTx = DMA1_Stream5;
47
      DMA_StreamRx = DMA1_Stream2;
48
      DMA_FlagTcif_Tx = DMA_FLAG_TCIF5;
49
      DMA_FlagTcif_Rx = DMA_FLAG_TCIF2;
50
      break;
51
  }
52
53
  /* Vorzeitig abbrechen, wenn nichts gesendet werden soll */
54
  if( len == 0 )
55
    return;
56
57
58
  /* Ungenutzte Puffer beruecksichtigen */
59
  const uint32_t CR_MINC = 1<<10;
60
  uint8_t tempRx;
61
  uint8_t tempTx = 0;
62
63
  if ( receive == NULL )
64
  {
65
    /* Keine Daten lesen */
66
    DMA_StreamRx->M0AR = (uint32_t) &tempRx;
67
    DMA_StreamRx->CR &= ~CR_MINC; /* nicht inkrementieren */
68
  }
69
  else
70
  {
71
    /* Neuen Block fuers lesen vorbereiten */
72
    DMA_StreamRx->M0AR = (uint32_t) receive;
73
    DMA_StreamRx->CR |= CR_MINC; /* inkrementieren */
74
  }
75
76
  if ( send == NULL )
77
  {
78
    /* Keine Daten senden */
79
    DMA_StreamTx->M0AR = (uint32_t) &tempTx;
80
    DMA_StreamTx->CR &= ~CR_MINC; /* nicht inkrementieren */
81
  }
82
  else
83
  {
84
    /* Neuen Block fuers senden vorbereiten */
85
    DMA_StreamTx->M0AR = (uint32_t) send;
86
    DMA_StreamTx->CR |= CR_MINC; /* inkrementieren */
87
  }
88
89
90
  DMA_SetCurrDataCounter(DMA_StreamTx, len);
91
  DMA_SetCurrDataCounter(DMA_StreamRx, len);
92
93
94
  /* Enable DMA SPI TX Stream */
95
  DMA_Cmd(DMA_StreamTx, ENABLE);
96
97
  /* Enable DMA SPI RX Stream */
98
  DMA_Cmd(DMA_StreamRx, ENABLE);
99
100
  /* Enable SPI DMA TX Requsts */
101
  SPI_I2S_DMACmd(SPIx, SPI_I2S_DMAReq_Tx, ENABLE);
102
103
  /* Enable SPI DMA RX Requsts */
104
  SPI_I2S_DMACmd(SPIx, SPI_I2S_DMAReq_Rx, ENABLE);
105
106
  /* Enable the SPI peripheral */
107
  SPI_Cmd(SPIx, ENABLE);
108
109
  /* Waiting the end of Data transfer */
110
  while( DMA_GetFlagStatus(DMA_StreamTx, DMA_FlagTcif_Tx)==RESET );
111
  while( DMA_GetFlagStatus(DMA_StreamRx, DMA_FlagTcif_Rx)==RESET );
112
113
  /* Clear DMA Transfer Complete Flags */
114
  DMA_ClearFlag(DMA_StreamTx, DMA_FlagTcif_Tx);
115
  DMA_ClearFlag(DMA_StreamRx, DMA_FlagTcif_Rx);
116
117
  /* Disable DMA SPI TX Stream */
118
  DMA_Cmd(DMA_StreamTx, DISABLE);
119
120
  /* Disable DMA SPI RX Stream */
121
  DMA_Cmd(DMA_StreamRx, DISABLE);
122
123
  /* Disable SPI DMA TX Requsts */
124
  SPI_I2S_DMACmd(SPIx, SPI_I2S_DMAReq_Tx, DISABLE);
125
126
  /* Disable SPI DMA RX Requsts */
127
  SPI_I2S_DMACmd(SPIx, SPI_I2S_DMAReq_Rx, DISABLE);
128
129
  /* Disable the SPI peripheral */
130
  /* Laesst Clock- und Daten-Pins floatend zurueck */
131
  SPI_Cmd(SPIx, DISABLE);
132
}

Die strict aliasing rule hier wohl "nur" deshalb keine Rolle, weil das 
Array per Hardware außerhalb des Betrachtungsbereichs des Compilers 
gefüllt wird.

Was ich mich frage:
 a) Ich habe in einigen Fällen den gleichen Speicherbereich als const 
uint8_t* und uint8_t * übergeben. Kann das Ärger machen?
    Die Unterscheidung ist deshalb drin, weil der Sendepuffer durchaus 
auch aus Literalen im Flash bestehen kann. Dann ist der Empfangsbuffer 
natürlich separat.
 b) Bei den Implementierungen (=Quelltexten) von memmove(), die ich im 
Netz gefunden habe, scheint strict aliasing keine besondere 
Berücksichtigung zu finden. Warum?


Edit: Die Forensoftware weigert sich, die unnoetigen Leerzeilen löschen 
zu lassen.

: Verschoben durch Admin
von Programmierer (Gast)


Lesenswert?

Walter T. schrieb:
> die "strict aliasing rule" besagt ja, dass zwei Zeiger, die einer
> Funktion übergeben werden, vom Compiler als voneinander unabhängig
> betrachtet werden können

Nur wenn die Pointer unterschiedliche Typen haben! Was du meinst ist nur 
der Fall wenn du das "restrict"-Keyword hinzunimmst. "char" (also auch 
"uint8_t") Pointer dürfen als Ausnahme alles Aliasen, weshalb auch 
memcpy, memmove & co funktionieren.

Walter T. schrieb:
> a) Ich habe in einigen Fällen den gleichen Speicherbereich als const
> uint8_t* und uint8_t * übergeben. Kann das Ärger machen?

Nö, das ist gleich doppelt erlaubt:
- Gleicher Typ => dürfen überlappen
- Vom Typ char => dürfen überlappen

Walter T. schrieb:
> b) Bei den Implementierungen (=Quelltexten) von memmove(), die ich im
> Netz gefunden habe, scheint strict aliasing keine besondere
> Berücksichtigung zu finden. Warum?

Dito.

von Programmierer (Gast)


Lesenswert?

Mit anderen Worten: strict-Aliasing wird erst dann zum Problem, wenn man 
"böse" Casts macht, z.B. "int*" nach "short*" o.ä., wobei eben als 
Ausnahme der Cast auf Zeiger auf einen char-Typ erlaubt ist. Solche 
"bösen" Casts sollte man sowieso nie machen (schlechter Stil), wodurch 
sich das Problem in Luft auflöst. Interessanter wird's wenn man das 
"restrict"-Keyword hinzu nimmt.

von Michael (Gast)


Lesenswert?

Programmierer schrieb:
> Mit anderen Worten: strict-Aliasing wird erst dann zum Problem, wenn man
> "böse" Casts macht, z.B. "int*" nach "short*" o.ä., wobei eben als
> Ausnahme der Cast auf Zeiger auf einen char-Typ erlaubt ist.

Auch wenn mir diese Regel bekannt ist, frage ich mich doch, was der 
technische Grund dafür ist. An welcher Stelle im Assembler-Code entsteht 
das Problem?

von Walter T. (nicolas)


Lesenswert?

Programmierer schrieb:
> Nur wenn die Pointer unterschiedliche Typen haben!

Danke für den Hinweis! "strict aliasing" wird nicht unbedingt dadurch 
einfacher, dass es in den Spielregeln (C-Standard) nicht behandelt wird.

Michael schrieb:
> An welcher Stelle im Assembler-Code entsteht
> das Problem?

Ist die Frage ernst oder ein Seitenhieb auf die Hochsprachen-Fraktion?

Wenn ersteres: John Regehr hatte mal etwas Schönes dazu in seinem Blog 
geschrieben: https://blog.regehr.org/archives/1307
Es erlaubt Optimierungen, die sonst nicht möglich sind.

von Programmierer (Gast)


Lesenswert?

Hier ein kleines Beispiel:

https://godbolt.org/z/zj11xvo3r

Ohne Optimierungen gibt der Code 0x0123abab aus, mit Optimierungen 
0x01234567. Bei anderen Compilern/Plattformen kann das Ergebnis 
unterschiedlich sein.

Der Grund ist: Bei eingeschaltetem Optimizer liest der Compiler den Wert 
"*foo" nur ein mal, vor dem ersten printf, aus dem Speicher (Wert 
0x01234567), und behält ihn in einem Prozessor-Register. Weil der 
Zugriff auf das "bar" auf einen anderen Typ stattfindet ("unsigned 
short"), darf der Compiler annehmen, dass dieser den Wert des ersten 
Zugriffs ("unsigned int") nicht beeinflusst, weshalb er den Wert nicht 
erneut ausliest, und beim 2. printf erneut den einmalig gelesenen Wert 
ausgibt. Bei abgeschaltetem Strict Aliasing oder eben bei allgemein 
abgeschalteter Compiler-Optimierung wird diese Optimierung deaktiviert, 
und nach jedem Pointer-Schreiben müssen alle anderen Pointer neu 
ausgelesen werden (ineffizient).

Walter T. schrieb:
> dass es in den Spielregeln (C-Standard) nicht behandelt wird.

Doch, es ist dort explizit und detailliert behandelt, mit allen 
Ausnahmen. Es heißt da nur nicht "strict aliasing". Wenn du 
Standard-Konformen Code schreibst, wird er funktionieren. Das Hadern mit 
Strict Aliasing bzw. das Abschalten mit -fno-strict-aliasing wird nur 
relevant, wenn man fehlerhaften Code schreiben möchte, wie z.B. im 
Linux-Kernel, der nur bei abgeschaltetem Strict Aliasing funktioniert.

von Programmierer (Gast)


Lesenswert?

Walter T. schrieb:
> dass es in den Spielregeln (C-Standard) nicht behandelt wird.

Im aktuellen Draft:
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf

unter Kapitel 6.5 "Expressions", Absatz 6-7, S. 77, sind die Regeln 
definiert, wie auf Pointer/Union-Elemente zuzugreifen ist. Wenn du dich 
daran hältst, funktioniert alles. Probleme entstehen erst, wenn man 
meint davon abweichen zu müssen.

von Walter T. (nicolas)


Lesenswert?

Programmierer schrieb:
> Im aktuellen Draft unter Kapitel 6.5 "Expressions", Absatz 6-7, S. 77,

Danke! Ich habe schon gesucht, aber war noch nicht über die "Operators" 
hinausgekommen. Vielleicht sollte ich diese 647 Seiten wirklich mal 
lesen, aber die Realität geht ja doch mehr Richtung Strg-F.

: Bearbeitet durch User
von Programmierer (Gast)


Lesenswert?

Walter T. schrieb:
> Vielleicht sollte ich diese 647 Seiten wirklich mal
> lesen,

Naja, das macht keiner so wirklich außer Compiler-Autoren. Gute Bücher 
sind wesentlich hilfreicher zum Lernen der Sprache. Wenn man eine 
definitive Antwort auf ein spezielles Problem braucht kann man ja immer 
noch Strg+F machen...

von Michael (Gast)


Lesenswert?

Walter T. schrieb:
> Wenn ersteres: John Regehr hatte mal etwas Schönes dazu in seinem Blog
> geschrieben: https://blog.regehr.org/archives/1307
> Es erlaubt Optimierungen, die sonst nicht möglich sind.

Natürlich ernst.

Das ist schon ein sehr konstruiertes akademisches Beispiel. Der 
bemängelt beispielsweise OpenSSL, dessen Funktion in unzähligen 
Implementierungen gegeben ist.

Programmierer schrieb:
> Das Hadern mit Strict Aliasing bzw. das Abschalten mit
> -fno-strict-aliasing wird nur relevant, wenn man fehlerhaften Code
> schreiben möchte, wie z.B. im Linux-Kernel, der nur bei abgeschaltetem
> Strict Aliasing funktioniert.

Wieso sollte der Code fehlerhaft sein? Wenn das Ergebnis stimmt, ist der 
Code nicht falsch. Er entspricht allenfalls nicht dem Standard.

Ihr versucht hier mit Gewalt Probleme zu konstruieren, die es in der 
Praxis nicht gibt. Der Code des Threaderstellers wird ebenfalls keine 
Probleme in dieser Hinsicht haben.

von Oliver S. (oliverso)


Lesenswert?

Michael schrieb:
> Wieso sollte der Code fehlerhaft sein? Wenn das Ergebnis stimmt, ist der
> Code nicht falsch. Er entspricht allenfalls nicht dem Standard.

Nun ja, da könnte man anderer Meinung sein.

Egal, hier gibt es was zum Nachlesen zum Thema:

https://gist.github.com/shafik/848ae25ee209f698763cffee272a58f8

Oliver

von Walter T. (nicolas)


Lesenswert?

Walter T. schrieb:
> Vielleicht sollte ich diese 647 Seiten...

Hoppla! Jetzt sind es 701 Seite. Also noch einmal 54 Seiten mehr als 
das, was Jörg vor ein paar Wochen als Draft verlinkt hat. Der Text wird 
schneller länger als ich lesen kann!

Michael schrieb:
> Das ist schon ein sehr konstruiertes akademisches Beispiel.

Das sind wohl alle sehr kurzen, prägnanten Beispiele, die in zwei Zeilen 
etwas auf den Punkt bringen sollen.

Michael schrieb:
> Ihr versucht hier mit Gewalt Probleme zu konstruieren, die es in der
> Praxis nicht gibt.

Ich mache ein "Code Review". In Anführungszeichen, weil das bei selbst 
geschriebenem Code eigentlich nicht geht. Da ist es Sinn und Zweck der 
Sache, überargwöhnisch zu sein.

von Programmierer (Gast)


Lesenswert?

Michael schrieb:
> Wieso sollte der Code fehlerhaft sein? Wenn das Ergebnis stimmt, ist der
> Code nicht falsch.

Nein! Das ist eine völlig falsche und gefährliche Sichtweise. 
Insbesondere in C und C++ gibt es sehr viel Code, dessen Ergebnis zwar 
korrekt aussieht, der aber dennoch fehlerhaft ist. Beim Portieren auf 
eine andere Plattform, einen anderen Compiler, Ändern der 
Compiler-Optionen usw. kann dieser Code plötzlich falsche Ergebnisse 
liefern.

>Er entspricht allenfalls nicht dem Standard.

Code, der nicht dem Standard entspricht, ist falsch, da es keinerlei 
Garantie gibt, dass er unter allen Bedingungen (Plattformen, 
Compiler-Versionen/Optionen) das richtige Ergebnis liefert. Das kann 
gerade im Embedded-Bereich schlimme Folgen haben (IIRC ist deswegen eine 
Ariane-Rakete abgestürzt). Die meisten Compiler liefern zusätzliche 
Garantien was funktioniert, aber dann hat man immer noch Probleme beim 
Portieren auf andere Compiler.

Michael schrieb:
> Ihr versucht hier mit Gewalt Probleme zu konstruieren, die es in der
> Praxis nicht gibt.

z.B. der Linux-Kernel ist voll mit solchen Problemen. Wäre er das nicht, 
könnte man ohne -fno-strict-aliasing kompilieren, was die Performance 
verbessern würde.

Michael schrieb:
> Das ist schon ein sehr konstruiertes akademisches Beispiel.

Solche "akademischen Beispiel" sind sehr verbreitet in existierendem 
Code, insbesondere wenn es um Serialisierung und Netzwerkprotokolle 
geht. Hier im Forum wird alle 3 Tage danach gefragt, und es kommen 
sofort 100 Antworten welche die Strict-Aliasing-Rules verletzen, und 
somit genau wie in meinem Beispiel nur bei abgeschalteter Optimierung 
funktionieren. Rein zufällig gibt es auch alle 5 Tage eine Frage "Warum 
funktioniert mein Code nur ohne Optimierungen?". Manche Firmen 
kompilieren nie mit Optimierungen und kaufen lieber leistungsfähigere 
Controller, weil sie es nicht schaffen korrekten Code zu schreiben.

von Walter T. (nicolas)


Lesenswert?

Walter T. schrieb:
> Hoppla! Jetzt sind es 701 Seite. Also noch einmal 54 Seiten mehr als
> das, was Jörg vor ein paar Wochen als Draft verlinkt hat. Der Text wird
> schneller länger als ich lesen kann!

Ich habe mich verguckt. Der Draft vom Dezember 2020 ist 54 Seiten kürzer 
als der oben verlinkte Draft vom April 2011. Es geht also in die 
richtige Richtung. :-)

[Edit]

Beitrag "Re: [gcc,clang] kein Error bei Funktionsaufruf mit falschen Parametern"

: Bearbeitet durch User
von Michael (Gast)


Lesenswert?

Programmierer schrieb:
> Manche Firmen kompilieren nie mit Optimierungen und kaufen lieber
> leistungsfähigere Controller, weil sie es nicht schaffen korrekten Code
> zu schreiben.

Dein ganzer Beitrag zeugt von einer gänzlich gegensätzlichen Sichtweise 
zwischen uns beiden.

Was nutzt ein Standard, der an der Praxis vorbeigeht? Was nutzt ein 
Compiler, der diesen Standard einhält, aber sich nicht so verhält, wie 
es die Anwender erwarten?

Gerade wenn es um Optimierungen usw. geht, sollte der vorhandene Code, 
wie Linux usw., als Grundlage herhalten und sicherstellen, dass dieser 
damit funktioniert.

Kann der Compiler nicht zweifelsfrei feststellen, dass die 
Speicherbereiche nicht doch identisch sind, darf er nicht so optimieren, 
dass dadurch Fehler bei der Ausführung entstehen können. Das sollte 
eigentlich selbstverständlich sein. Es vom Datentyp des Parameters 
abhängig zu machen ist verrückt. Klar, bei float und int mag es 
konstruiert erscheinen, aber ein int* und ein void* als Parameter 
derselben Funktion können durchaus identisch sein.

Ich kenne die Arbeit in Normgremien selbst, da kommt selten etwas 
brauchbares heraus, Kompromisse dienen fast immer nur dazu, damit keiner 
der Beteiligten einen Gesichtsverlust erleidet...

von Walter T. (nicolas)


Lesenswert?

Michael schrieb:
> Gerade wenn es um Optimierungen usw. geht, sollte der vorhandene Code,
> wie Linux usw., als Grundlage herhalten und sicherstellen, dass dieser
> damit funktioniert.

STOP!

Diese Diskussion ("Hat sich der GCC an den Linix-Kernel oder an den 
C-Standard anzupassen?") hat sehr viele heiße Diskussionen in den 
entsprechenden Feeds verursacht. Hier passt sie nicht hin. Hier geht es 
nur um "strict aliasing" im Kontext "ARM-GCC". Um den IST-Zustand, 
keinen SOLL-Zustand.

: Bearbeitet durch User
von Programmierer (Gast)


Lesenswert?

Michael schrieb:
> Was nutzt ein Standard, der an der Praxis vorbeigeht?

Wo/Wieso tut er das?

Michael schrieb:
> Was nutzt ein
> Compiler, der diesen Standard einhält, aber sich nicht so verhält, wie
> es die Anwender erwarten?

Wo ist klar definiert, was die Anwender erwarten? Im Standard.

Michael schrieb:
> Gerade wenn es um Optimierungen usw. geht, sollte der vorhandene Code,
> wie Linux usw., als Grundlage herhalten und sicherstellen, dass dieser
> damit funktioniert.

So klappt das aber nicht. Wer definiert, welcher Code genau korrekt ist 
und welcher nicht? Findest du, Strict Aliasing sollte aus dem Standard 
entfernt werden, sodass der Linux-Code korrekt wird, aber dafür der 
gesamte C-Code langsamer?

Michael schrieb:
> Kann der Compiler nicht zweifelsfrei feststellen, dass die
> Speicherbereiche nicht doch identisch sind

Das kann er praktisch nie.

Michael schrieb:
> Das sollte
> eigentlich selbstverständlich sein.

Bei FORTRAN ist es genau eben nicht so, da gibt es kein Aliasing, 
weshalb FORTRAN bis heute einen Geschwindigkeitsvorteil hat. Um das in C 
nachzubilden gibt es Strict Aliasing und das "restrict" Keyword.

Michael schrieb:
> aber ein int* und ein void* als Parameter
> derselben Funktion können durchaus identisch sein.

void* kann man eh nicht dereferenzieren, also kein Problem.

Michael schrieb:
> Es vom Datentyp des Parameters
> abhängig zu machen ist verrückt.

Es ist naheliegend, dass Pointer unterschiedlichen Typs nicht auf das 
gleiche zeigen können.

Hast du jetzt ein Problem mit Strict Aliasing im Speziellen oder dem 
C-Standard im Allgemeinen? Wenn du nur Strict Aliasing nicht magst und 
mit dem Geschwindigkeitsnachteil leben kannst (ich höre c-hater schon 
lachen), kannst du gerne mit -fno-strict-aliasing kompilieren.

Wenn du findest, dass der ganze Standard so umgebaut werden sollte dass 
er viel nachlässiger ist und auch kein Undefined Behaviour kennt (was ja 
bis jetzt ebenfalls der Performance zugute kommt), kannst du auch 
einfach Java verwenden, denn da ist es genau so: Code, der das korrekte 
Ergebnis liefert, tut das wahrscheinlich immer und ist korrekt. Wenn 
Multithreading ins Spiel kommt, stimmt das natürlich auch nicht mehr.

von Programmierer (Gast)


Lesenswert?

Walter T. schrieb:
> Hier passt sie nicht hin. Hier geht es
> nur um "strict aliasing" im Kontext "ARM-GCC". Um den IST-Zustand,
> keinen SOLL-Zustand.

Das Ganze ist sowieso weder ARM- noch GCC-spezifisch. Das o.g. Beispiel 
ist amd64, und das ist bei anderen Compilern ganz genauso.

von Michael (Gast)


Lesenswert?

Programmierer schrieb:
> void* kann man eh nicht dereferenzieren, also kein Problem.

Haha. Zum Dereferenzieren brauche ich einen Cast und dann fängt das 
Spiel doch an...

Programmierer schrieb:
> Es ist naheliegend, dass Pointer unterschiedlichen Typs nicht auf das
> gleiche zeigen können.

Sehe ich nicht so. Für den Compiler sind zwei Structs unterschiedlich, 
obwohl sie identisch aufgebaut sind. Und die genannte "Vererbung" (ein 
Struct beginnt mit einem anderen Struct) ist für mich durchaus legitime 
Verwendung.

Programmierer schrieb:
> Wo ist klar definiert, was die Anwender erwarten? Im Standard.

Das ist absurd. Ich habe diesen Standard nicht geschrieben, das ist 
nicht, was ich oder viele andere Anwender erwarten. Das ist das, was das 
Normgremium beschlossen hat.

Die Frage ist, was für den Hersteller des Compilers relevant ist. Es gab 
Firmen, die ihre Kunden zufrieden stellen wollten. Bei GCC hat das heute 
sowieso eine merkwürdige Richtung eingeschlagen...

von Walter T. (nicolas)


Lesenswert?

Meinetwegen. Mein Teil ist eh geklärt. Sollte eine ähnliche 
Fragestellung noch einmal auftauchen, mache ich einen neuen Thread auf. 
Viel Spaß noch!

von Programmierer (Gast)


Lesenswert?

Michael schrieb:
> Haha. Zum Dereferenzieren brauche ich einen Cast und dann fängt das
> Spiel doch an...

Und auf was möchtest du den void* casten, und wieso kannst du das nicht 
anders lösen?

Michael schrieb:
> Für den Compiler sind zwei Structs unterschiedlich,
> obwohl sie identisch aufgebaut sind.

Das stimmt nur halb, es gibt im Kontext von Unions die "common initial 
sequence".

Michael schrieb:
> Und die genannte "Vererbung" (ein
> Struct beginnt mit einem anderen Struct) ist für mich durchaus legitime
> Verwendung.

Das lässt sich aber auch lösen, indem man den "upcast" explizit macht, 
d.h. den Pointer auf das "Basis-Element" übergibt.

Michael schrieb:
> Das ist absurd. Ich habe diesen Standard nicht geschrieben, das ist
> nicht, was ich oder viele andere Anwender erwarten.

Na dann schreibe deinen eigenen Standard und hoffe dass ihn jemand 
implementiert. Oder benutze einfach eine Sprache in der das schon so 
ist.

Michael schrieb:
> Bei GCC hat das heute
> sowieso eine merkwürdige Richtung eingeschlagen...

Wieso, die sind doch sogar so nett und bieten -fno-strict-aliasing an, 
was erwartest du denn noch, dass das standardmäßig an ist?

von Michael (Gast)


Lesenswert?

Programmierer schrieb:
> was erwartest du denn noch, dass das standardmäßig an ist?

Ja, zum Beispiel. "Invasive Optimierungen" sollten nur dann greifen, 
wenn der Anwender weiß, was er tut.

Was ist in der Praxis häufiger vertreten? Genaue Kenntnis über diese 
"Strict Aliasing"-Geschichte? Oder Code, der diese verletzt?

Es bleibt festzustellen, dass es mit älteren Compilern kaum solche 
Fragestellungen gab. C89 funktionierte einwandfrei, heute kommen alle 
paar Jahre neue Versionen der Standards. Das heutige C++ zum Beispiel 
hat keinerlei Ähnlichkeit mehr zu dem, was ich damals kennenlernte in 
den Neunzigern...

von Programmierer (Gast)


Lesenswert?

Michael schrieb:
> Ja, zum Beispiel. "Invasive Optimierungen" sollten nur dann greifen,
> wenn der Anwender weiß, was er tut.

Praktisch alle Optimierungen sind invasiv auf die eine oder andere Art, 
und die sind auch nur an wenn man -O übergibt. Was jetzt standardmäßig 
an oder aus ist ist Haarspalterei. Ich finde es gefährlich, 
standardmäßig nicht-standard-konformen Code durchzuwinken.

Michael schrieb:
> Was ist in der Praxis häufiger vertreten? Genaue Kenntnis über diese
> "Strict Aliasing"-Geschichte? Oder Code, der diese verletzt?

C ist eine Systems Programming Sprache, um effizienten Low-Level-Code zu 
implementieren. Es geht nicht darum, diese für Anfänger einfach zu 
machen. C ist ein kompliziertes aber mächtiges Werkzeug, das man 
bedienen können muss. Für alles andere gibt es andere Sprachen. C auf 
Kosten der Performance zu vereinfachen wäre so wie ein Formel-1 Auto mit 
Kofferraum und Klimaanlage auszustatten.

Michael schrieb:
> Es bleibt festzustellen, dass es mit älteren Compilern kaum solche
> Fragestellungen gab.

Dafür waren die auch langsamer.

Michael schrieb:
> C89 funktionierte einwandfrei

C89 hatte Strict Aliasing auch schon, das war schon immer Teil der 
Sprache! Nur weil die uralten Compiler - bzw. die, die du verwendet hast 
- das nicht ausnutzen konnten...

von Oliver S. (oliverso)


Lesenswert?

Michael schrieb:
> Es bleibt festzustellen, dass es mit älteren Compilern kaum solche
> Fragestellungen gab. C89 funktionierte einwandfrei, heute kommen alle
> paar Jahre neue Versionen der Standards.

K&R, Ansi/C90, C99, C11, C18, ...

Stimmt, das geht so irrsinnig schnell voran, da kann man wirklich kaum 
Schritt halten. Da ist ja noch nicht mal die Tinte trocken, schon gibt 
einen neuen Standard ;)

Michael schrieb:
> Das heutige C++ zum Beispiel
> hat keinerlei Ähnlichkeit mehr zu dem, was ich damals kennenlernte in
> den Neunzigern...

Bisher war das alles immer abwärtskompatibel, und du kannst dem 
Compilern sagen, welchen Standard du gerne hättest. Der C++-Standard 
hindert dich nicht daran, C++-Code so zu schreiben, wie du es 
(kennen-)gelernt hast.

Oliver

: Bearbeitet durch User
von Michael (Gast)


Lesenswert?

Programmierer schrieb:
> Es geht nicht darum, diese für Anfänger einfach zu machen.

Ich bezweifle, dass der Linux-Kernel von Anfängern entwickelt wird.

Oliver S. schrieb:
> Der C++-Standard hindert dich nicht daran, C++-Code so zu schreiben, wie
> du es (kennen-)gelernt hast.

Das ist korrekt. Aber der Code, mit dem man heute konfrontiert wird, 
nutzt das teilweise intensiv. Glücklicherweise ist in meinem Unternehmen 
mittlerweile alles ausdrücklich verboten, was nach C++03 kam.

Das geht einfach in die falsche Richtung, aus meiner Sicht. Für Viele 
war C++ im Prinzip "C mit Klassen", also als solches wie es auch 
erfunden wurde. Manche wollen aber mit dem aktuellen C++ Java und C# 
usw. noch überholen...

von Programmierer (Gast)


Lesenswert?

Michael schrieb:
> Ich bezweifle, dass der Linux-Kernel von Anfängern entwickelt wird.

Eine Menge Treiber kommen von Firmen, die nicht immer die Kompetentesten 
sind... Aber die Strict-Aliasing-Entscheidung kommt von Linus Torvalds, 
und der hat sowieso spezielle Ansichten. Wahrscheinlich ist die Codebase 
einfach zu groß um es umzustellen, schließlich kompiliert der 
Linux-Kernel auch nur mit dem GCC mit ganz bestimmten Einstellungen.

Michael schrieb:
> Glücklicherweise ist in meinem Unternehmen
> mittlerweile alles ausdrücklich verboten, was nach C++03 kam.

Gibt es dafür einen technischen Grund, oder ist nur keine Zeit zum 
Lernen da? Schließlich kann man mit dem neuen C++ effizienteren, 
kompakteren, wartbareren Code schreiben.

Michael schrieb:
> Manche wollen aber mit dem aktuellen C++ Java und C#
> usw. noch überholen...

Klar, C++ hat schon immer Dinge ermöglicht die in anderen Sprachen nicht 
gehen. So kann man z.B. mit dem "neuen" Speichermodell (C++11) sehr 
effizient Multithreading nutzen, was in den anderen Sprachen zwar 
einfacher, aber auch weniger effizient ist. C++03 unterstützt offiziell 
überhaupt kein Multithreading, weshalb man auf nicht-portable 
Plattform-Spezifische Lösungen zurückgreifen muss - IMO nicht sehr 
erstrebenswert.

von PittyJ (Gast)


Lesenswert?

Programmierer schrieb:
>
> Klar, C++ hat schon immer Dinge ermöglicht die in anderen Sprachen nicht
> gehen. So kann man z.B. mit dem "neuen" Speichermodell (C++11) sehr
> effizient Multithreading nutzen, was in den anderen Sprachen zwar
> einfacher, aber auch weniger effizient ist. C++03 unterstützt offiziell
> überhaupt kein Multithreading, weshalb man auf nicht-portable
> Plattform-Spezifische Lösungen zurückgreifen muss - IMO nicht sehr
> erstrebenswert.

Ich benutze C++ auf einem Arm Prozessor Bare Metal. Bei 190K Flash und 
32K Ram ist dann nicht viel möglich für die C++ Features ab 2011.
Da ist nun mal kein Betriebsystem mit Threads darunter und auch new 
Operatoren kosten extrem. Nichts mit Maps und Vektoren, die permanent 
reallozieren. Da ist dann sofort der Speicher verhunzt.

Ich bleibe schön bei 'altem C++' weil ich damit auf Embedded Prozessoren 
arbeiten kann. Die neuen Features ignoriere ich, weil die nicht portabel 
sind.

von mh (Gast)


Lesenswert?

Programmierer schrieb:
> Wahrscheinlich ist die Codebase einfach zu groß um es umzustellen, schließlich 
kompiliert der
> Linux-Kernel auch nur mit dem GCC mit ganz bestimmten Einstellungen.
Das funktioniert als Ausrede für die Inkompatibilitäten mit dem 
C-Standard im existierenden Code. Aber mit jeder Zeile die neu 
hinzukommt oder geändert wird, könnte man sich an die Regeln halten. 
Dann würde der Unterschied mit der Zeit immer kleiner, statt immer 
größer.

von mh (Gast)


Lesenswert?

PittyJ schrieb:
> Ich benutze C++ auf einem Arm Prozessor Bare Metal. Bei 190K Flash und
> 32K Ram ist dann nicht viel möglich für die C++ Features ab 2011.
> Da ist nun mal kein Betriebsystem mit Threads darunter und auch new
> Operatoren kosten extrem. Nichts mit Maps und Vektoren, die permanent
> reallozieren. Da ist dann sofort der Speicher verhunzt.
>
> Ich bleibe schön bei 'altem C++' weil ich damit auf Embedded Prozessoren
> arbeiten kann. Die neuen Features ignoriere ich, weil die nicht portabel
> sind.

Und was ist mit all den Features, die keinen zusätlichen Ram oder Flash 
benötigen, sondern evtl. zu einer Ersparnis führen?

von Programmierer (Gast)


Lesenswert?

PittyJ schrieb:
> Ich benutze C++ auf einem Arm Prozessor Bare Metal. Bei 190K Flash und
> 32K Ram ist dann nicht viel möglich für die C++ Features ab 2011.

PittyJ schrieb:
> Die neuen Features ignoriere ich, weil die nicht portabel
> sind.

Variadische templates, constexpr, <cstdint> & co kosten nichts an 
Speicher und eigenen sich super für Mikrocontroller. Die Atomics, welche 
mit C++11 bzw. C11 eingeführt wurden, funktionieren auch für 
Mainloop<->ISR Synchronisation ohne Betriebssystem und passen genau auf 
die Atomic-Instruktionen von ARM's, die man sonst ohne Inline Assembly 
nicht nutzen könnte.

von Walter T. (nicolas)


Lesenswert?

Ich fürchte, ihr werdet die Diskussion zu keinem fruchtbaren Ergebnis 
bringen. Beide Seiten haben gute Gründe. Und die Diskussionen in den 
Mailinglisten und Feeds dazu sind ewig lang. Und immer noch nicht 
fertig.

Für die Besitzstandswahrer ist der Quelltext das Tafelsilber, das nicht 
durch neue Compilervarianten beeinträchtigt werden sollte.

Für die Verbesserer ist es wichtiger, neben den älteren neueren 
Anforderungen gerecht zu werden.

Meine persönliche Meinung ist es ja, dass Quelltext ein Investitionsgut 
ist, dass über 7 Jahre abgeschrieben gehört. Und genau wie man auch 
Maschinen und Gerätschaften weit über die Abschreibungsperiode hinaus 
betreiben kann, ist man auch hier dafür selbst verantwortlich, sie am 
Laufen zu halten und ggf. zu warten oder warten zu lassen.

Bei vielen Maschinen ist es sogar so, dass Umbauten auch immer 
automatisch die Pflicht beinhalten, auch aktuelle (Sicherheits-) 
Standards einzuhalten. Aber so weit würde ich bei Quelltext nicht gehen 
wollen.

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

Michael schrieb:
> Glücklicherweise ist in meinem Unternehmen
> mittlerweile alles ausdrücklich verboten, was nach C++03 kam.

Jeder ist seines Glückes Schmied...

Oliver

von aliasing (Gast)


Lesenswert?

Programmierer schrieb:
> PittyJ schrieb:
>> Ich benutze C++ auf einem Arm Prozessor Bare Metal. Bei 190K Flash und
>> 32K Ram ist dann nicht viel möglich für die C++ Features ab 2011.
>
> PittyJ schrieb:
>> Die neuen Features ignoriere ich, weil die nicht portabel
>> sind.
>
> Variadische templates, constexpr, <cstdint> & co kosten nichts an
> Speicher und eigenen sich super für Mikrocontroller. Die Atomics, welche
> mit C++11 bzw. C11 eingeführt wurden, funktionieren auch für
> Mainloop<->ISR Synchronisation ohne Betriebssystem und passen genau auf
> die Atomic-Instruktionen von ARM's, die man sonst ohne Inline Assembly
> nicht nutzen könnte.

oder auch std::span, ist ein super Ding!
Gibt es auf github als freie Implementierung, das funktioniert dann auch 
mit älteren Compilern bis der eigene Compiler C++20 kann.

C++ ist genauso schlank und effizient wie C - you only pay for what you 
use.
Gibt aber immer noobs die objektoriente Programmierung in C++ mit 
Interfaces und Vererbung mit einzelnen Funktionen in C vergleichen.

Auch in C programmiert man ojektorientiert und das kostet dann genauso 
viel.

von Rolf M. (rmagnus)


Lesenswert?

Programmierer schrieb:
> Variadische templates, constexpr, <cstdint> & co kosten nichts an
> Speicher und eigenen sich super für Mikrocontroller.

Oder schon so einfache Erleichterungen der Schreibarbeit wie range-based 
for oder auto. Und auch lambdas können hilfreich sein. Damit ist ein 
std::sort effizienter als ein qsort.

von Nur_ein_Typ (Gast)


Lesenswert?

Programmierer schrieb:
> [...] schließlich kompiliert der
> Linux-Kernel auch nur mit dem GCC mit ganz bestimmten Einstellungen.

Totaler Quatsch, das geht auch mit LLVM. Und schon vor > 10 Jahren hat 
das LinuxDNA-Projekt den Kernel mit Intels ICC kompiliert.

von Rolf M. (rmagnus)


Lesenswert?

Nur_ein_Typ schrieb:
> Und schon vor > 10 Jahren hat das LinuxDNA-Projekt den Kernel mit Intels
> ICC kompiliert.

Allerdings hat Intel einen nicht ganz unerheblichen Aufwand in den 
Compiler gesteckt, um das zu ermöglichen.

von Al Fine (Gast)


Lesenswert?

mh schrieb:
> Aber mit jeder Zeile die neu
> hinzukommt oder geändert wird, könnte man sich an die Regeln halten.
> Dann würde der Unterschied mit der Zeit immer kleiner, statt immer
> größer.

Aber warum sollte man sich an den schwachen Regelsatz des Standards 
halten, wenn die meiste Hardware sowieso stärkere Zusicherungen erlaubt?

von udok (Gast)


Lesenswert?

C ist ein recht "lockerer" Standard, der davon ausging,
dass Programmierer und Compilerbauer die selbe Person sind,
oder sich zumindest nicht das Haxl legen.
Das muss auch so sein, weil C alles vom 8 Bit Mikrocontroller mit 256 
Bytes
Ram bis zum Cray Supercomputer abdeckt.

Die neue Generation der Compiler Entwickler sieht ihren Baum, aber nicht
mehr den Wald, kommt frisch von der Uni,
ist blitzgescheit, ehrgeizig und hat Langeweile.

Der C Standard bietet Schlupflöcher für Optimierungen, die in 1 von 1000 
Fällen ein paar Prozent Geschwindigkeitsvorteil bringt.
Kein Informatiker mit 10 Jahren Industriepraxis würde auf die Idee
kommen, diese Schlupflöcher auszunutzen.

Dazu gehört die Strict Aliasing Geschichte.
Aber auch signed overflow.
Neuere gcc wissen nicht mehr, wie signed Arithmethik funktioniert.
(-fwrapv nicht vergessen).

In der Praxis spielen diese Optimierungen sogut wie nie eine Rolle,
hauen aber funktionierenden Code zusammen.
Das sieht man schön darin, dass Compiler wie der NT WinDDK 7.10
cl.exe konstant die kleinsten Files generieren, die auch oft noch
schneller sind, als etwa der 2019 cl oder der gcc.
Dazu sind die alte Compiler auch noch schneller beim Compilieren (*).

Dazu kommen Anwender, die konstant alles mit -O3 oder -O2 Übersetzen,
anstatt mit konservativerem -O1, wie es früher üblich war,
als man noch genau wissen wollte, wie C Statements übersetzt werden.

Leiden tut der Anwender und die Reputation der Sprache.

(*)
1
Source File mit ca. 170000 Zeilen nach Präprozessor, Optimierung -O1:
2
3
WinDDK cl         110 ms
4
VS 2019 cl        160 ms
5
clang-cl v11      250 ms
6
Mingw gcc v10     280 ms

von udok (Gast)


Lesenswert?

Walter T. schrieb:
> Meine persönliche Meinung ist es ja, dass Quelltext ein Investitionsgut
> ist, dass über 7 Jahre abgeschrieben gehört. Und genau wie man auch

Reichlich naiv...
aber wir leben ja in einer Wegwerfgesellschaft.

von Programmierer (Gast)


Lesenswert?

udok schrieb:
> Die neue Generation der Compiler Entwickler sieht ihren Baum, aber nicht
> mehr den Wald, kommt frisch von der Uni,
> ist blitzgescheit, ehrgeizig und hat Langeweile.
> Der C Standard bietet Schlupflöcher für Optimierungen, die in 1 von 1000
> Fällen ein paar Prozent Geschwindigkeitsvorteil bringt.
> Kein Informatiker mit 10 Jahren Industriepraxis würde auf die Idee
> kommen, diese Schlupflöcher auszunutzen.

Ihr versteht alle den Sinn von C nicht. C ist für hocheffiziente 
Systemprogrammierung und nicht umsonst als Super-Assembler bezeichnet. C 
sollte immer mit Assembler vergleichbare Ergebnisse erzielen. Wer diese 
Performance nicht braucht und nur "normale" Anwendungen schreibt 
("Industriepraxis"), benutzt mit C einfach die falsche Sprache. Leider 
hat sich die gesamte Embedded Industrie und auch Lehre auf C 
eingeschossen und missbraucht es für "normale" Anwendungen anstatt eine 
andere, freundlichere Sprache zu etablieren, wie es die Informatiker für 
andere Bereiche schon längst haben (C#, Java, PHP, JS, Python, ....). 
Anstatt also an Compiler-Entwicklern rumzunörgeln dass sie ihren Job 
tun, solltet ihr einfach mal aufhören C für etwas zu missbrauchen für 
das es nie gedacht war und euch für andere Sprachen öffnen.

udok schrieb:
> anstatt mit konservativerem -O1, wie es früher üblich war,

C ohne Optimierung zu übersetzen ist wie mit dem Ferrari nur in Gang 1 
zu fahren. Da hätte es auch ein VW getan, aber der ist wohl nicht cool 
genug.

von Programmierer (Gast)


Lesenswert?

Wenn C euch zu kompliziert ist, benutzt doch einfach mal Matlab, das 
wird doch sowieso ständig als Allheilmittel angepriesen. Das hat solche 
Probleme nicht.

von Rolf M. (rmagnus)


Lesenswert?

udok schrieb:
> Der C Standard bietet Schlupflöcher für Optimierungen, die in 1 von 1000
> Fällen ein paar Prozent Geschwindigkeitsvorteil bringt.

Ich würde es eher so sagen: Der C-Standard lässt dem Compiler viele 
Freiheiten zur Optimierung, da sonst an einigen Stellen für ein paar 
Spezialfälle zusätzlicher Code eingefügt werden müsste, der selten nötig 
ist, aber meistens das Programm langsamer macht.

> Kein Informatiker mit 10 Jahren Industriepraxis würde auf die Idee
> kommen, diese Schlupflöcher auszunutzen.

Was offiziell im Standard steht, sind keine "Schlupflöcher".

> Aber auch signed overflow.
> Neuere gcc wissen nicht mehr, wie signed Arithmethik funktioniert.
> (-fwrapv nicht vergessen).

Die wissen, dass ein signed overflow in C offiziell undefiniert ist und 
verhalten sich entsprechen.
Bewusst am Standard vorbei zu programmieren und das dann mit 
irgendwelchen Compiler-Optionen gerade zu biegen, ist viel eher, was ich 
als Ausnutzung von Schlupflöchern bezeichnen würde.

> In der Praxis spielen diese Optimierungen sogut wie nie eine Rolle,
> hauen aber funktionierenden Code zusammen.

Nein, sie hauen kaputten Code zusammen. Code, der auf Grund seiner 
Fehler nur auf niedrigen Optimierungsstufen das tut, was er soll, ist 
für mich kein "funktionierender Code", sondern Schrott.

von udok (Gast)


Lesenswert?

Rolf M. schrieb:
> Nein, sie hauen kaputten Code zusammen. Code, der auf Grund seiner
> Fehler nur auf niedrigen Optimierungsstufen das tut, was er soll, ist
> für mich kein "funktionierender Code", sondern Schrott.

Dann musst du halt die Codebasis der letzen 50 Jahre überarbeiten...
Da stelle ich mir eher die Frage:
Hat der Compilerbauer einfach Mist gemacht?

Gerade Signed Arithmethik (mit definiertem Überlaufverhalten)
ist seit dieser Zeit eigentlich bis auf Ausnahmen im DSP Bereich 
Standard.

Dazu kommt ja auch noch: Der C-Standard sagt ja nicht, dass das
Überlaufverhalten nicht erlaubt ist, er sagt nur, dass es dem 
Compilerbauer
überlassen ist, wie er damit umgeht.

Im stillen Einverständnis, dass der Compilerbauer für die
Zielarchitektur die beste Wahl trifft.
Und da krankt es am gcc ganz gewaltig.

von udok (Gast)


Lesenswert?

Hier mal ein Beispiel, abs(x) wird manchmal für Überlauferkennung 
verwendet:
1
// Bug report for gcc v. 4.5.2, Ubuntu
2
// cl is ok, but clang-cl has the same bug...
3
4
#include <stdio.h>
5
#include <stdlib.h>
6
7
int somethingrandom();
8
9
void test()
10
{
11
    int i, b, c;
12
13
    for (i = 0; i < 1000; i++) {
14
        b = somethingrandom();
15
        b = abs(b);
16
        if (b <= 0)
17
            continue;        // changed to if (b == 0) when compiled with -O2
18
19
        c = somethingrandom();
20
        c = abs(c);
21
        if (c < 0)
22
            continue;         // optimized away when compiled with -O2
23
24
        printf("0x%X 0x%X ", b, c);  // value 0x80000000 occurs
25
    }
26
}

In dem Beispiel geht gcc davon aus, dass i nie überläuft:
1
// Integer Overflow Handling of GCC and Clang:
2
//
3
// https://stackoverflow.com/questions/47232954/what-does-fwrapv-do
4
// Mathematically speaking, i+1 should always be greater than i for any integer i.
5
// However, for a 32-bit int, there is one value of i that makes that statement false,
6
// which is 2147483647 (i.e. 0x7FFFFFFF, i.e. INT_MAX).
7
// Adding one to that number will cause an overflow and the new value,
8
// according to the 2's compliment representation,
9
// will wrap-around and become -2147483648.
10
// Hence, i+1>i becomes -2147483648>2147483647 which is false.
11
//
12
// Compile with gcc -fwrapv to get a more meaningful result:
13
// x86_64-w64-mingw32-gcc.exe -fwrapv -O1 -Wall -c bug2.c
14
//
15
16
int f(int i)
17
{
18
    return i+1 > i;
19
}

Abfrage auf Überlauf:
1
void foo(int a, int b) {
2
    if (a <= 0 || b <= 0) return; // only deal with positive numbers
3
    if (a+b >= 0) {
4
        printf("positive sum\n");
5
    } else {
6
        printf("overflow\n");
7
    }
8
}

von Rolf M. (rmagnus)


Lesenswert?

udok schrieb:
> Rolf M. schrieb:
>> Nein, sie hauen kaputten Code zusammen. Code, der auf Grund seiner
>> Fehler nur auf niedrigen Optimierungsstufen das tut, was er soll, ist
>> für mich kein "funktionierender Code", sondern Schrott.
>
> Dann musst du halt die Codebasis der letzen 50 Jahre überarbeiten...

Dass es viel fehlerhaften Code gibt, bedeutet nicht, dass man die Fehler 
einfach weiter machen sollte.

> Da stelle ich mir eher die Frage:
> Hat der Compilerbauer einfach Mist gemacht?

Nein, hat er nicht. Den C-Standard gibt es schon lange. Der Compiler 
hält sich in diesem Fall daran, der Code nicht. Ganz offensichtlich 
liegt der Fehler beim Code, bei dessen Entwicklung falsche Annahmen 
getroffen wurden.

> Gerade Signed Arithmethik (mit definiertem Überlaufverhalten)
> ist seit dieser Zeit eigentlich bis auf Ausnahmen im DSP Bereich
> Standard.

Das Ziel der Compilerbauer ist nicht, bewusst dafür zu sorgen, dass 
"undefiniertes Verhalten" auch tatsächlich etwas undefiniertes macht. 
Das ergibt sich eben indirekt aus Optimierungen.

> Dazu kommt ja auch noch: Der C-Standard sagt ja nicht, dass das
> Überlaufverhalten nicht erlaubt ist, er sagt nur, dass es dem
> Compilerbauer überlassen ist, wie er damit umgeht.

Nicht nur. Er sagt, vor allem auch, dass der Compiler damit gar nicht 
umgehen muss. Er darf annehmen, dass das Programm so geschrieben ist, 
dass ein signed-Überlauf niemals stattfindet. Er braucht diesen Fall 
daher gar nicht erst zu berücksichtigen.

> Im stillen Einverständnis, dass der Compilerbauer für die
> Zielarchitektur die beste Wahl trifft.

Nein. Das wäre unspecified oder implementation-defined behavior. 
signed-overflow ist aber undefined behavior, das heißt, der 
Compilerbauer muss gar nichts wählen.

von Programmierer (Gast)


Lesenswert?

udok schrieb:
> Gerade Signed Arithmethik (mit definiertem Überlaufverhalten)
> ist seit dieser Zeit eigentlich bis auf Ausnahmen im DSP Bereich
> Standard.

Aber nicht C-Standard. Um das noch mal deutlich zu sagen: Korrekter 
(d.h. standard-konformer) C-Code von 1990 kompiliert auch heute noch 
absolut korrekt. Die Compiler-Bauer haben da nichts "verschlimmbessert", 
es funktioniert alles immer noch genau so wie immer. Lediglich 
fehlerhafter Code fällt jetzt wahrscheinlicher auf die Nase, dafür wird 
korrekter Code schneller. Und da Geschwindigkeit sowieso so ziemlich der 
einzige Grund ist, C und C++ zu verwenden, ist das sehr zu begrüßen. 
Wenn Geschwindigkeit nicht höchste Priorität hat, ist C und C++ schlicht 
die falsche Wahl.

von A. S. (Gast)


Lesenswert?

Rolf M. schrieb:
> Die wissen, dass ein signed overflow in C offiziell undefiniert ist und
> verhalten sich entsprechen.

Der (nicht definierte) Überlauf von signed ist m.E. der häufigste Fall 
von "toleriertem UB" und darum gefährlich. Im Embedded-Bereich würde es 
bei Timern und (neu-alt) auf 2er-Komplement-Maschinen die Casts oder ifs 
ersparen.

Man kann sich streiten, warum es eingeführt wurde. Wegen der vielen 
HW-Implementierungen zu der Zeit, um Sonderlocken zu vermeiden? Oder um 
Optimierungen zu ermöglichen weil es undefiniert ist? Beipiel:
1
int8_t debug;
2
int foo(int8_t i) 
3
{
4
    debug=100+i;
5
    return i<30;
6
}

Die Funktion darf Dank debug immer 1 zurückgeben.

Hier eine konforme Version für + und - von 
http://c-faq.com/misc/sd26.html

> Here is a complete set of three functions for ``careful'' addition,
> subtraction, and multiplication.
> (Note: these functions are still not perfect, and may fail if invoked
> on various edge cases, such as the smallest negative integer, INT_MIN.)
1
#include <stdio.h>
2
#include <limits.h>
3
4
int chkadd(int a, int b)
5
{
6
  if(b < 0)
7
    return chksub(a, -b);
8
  if(INT_MAX - b < a) {
9
    fputs("int overflow\n", stderr);
10
    return INT_MAX;
11
  }
12
  return a + b;
13
}
14
15
int chksub(int a, int b)
16
{
17
  if(b < 0)
18
    return chkadd(a, -b);
19
  if(INT_MIN + b > a) {
20
    fputs("int underflow\n", stderr);
21
    return INT_MIN;
22
  }
23
  return a - b;
24
}

von udok (Gast)


Lesenswert?

A. S. schrieb:
> Man kann sich streiten, warum es eingeführt wurde. Wegen der vielen
> HW-Implementierungen zu der Zeit, um Sonderlocken zu vermeiden? Oder um
> Optimierungen zu ermöglichen weil es undefiniert ist? Beipiel:

Damals war 2-er Kompliment noch nicht so verbreitet, da gab es noch
1-er Kompliment, Vorzeichenbits, und Saturated Arithmetik.

Diese Optimierungen sind völlig sinnlos, weil damit nie ein 
Geschwindigkeitsproblem gelöst wird,
der Code ist ja aus einem wichtigen Grund drinnen!
Aber funktionierender Code wird damit völlig unnötig zerstört.

von Oliver S. (oliverso)


Lesenswert?

udok schrieb:
> Aber funktionierender Code wird damit völlig unnötig zerstört.

Die Zeit, in der Codequalität nach "tut doch" bewertet wurde, ist aber 
auch schon lange vorbei.

Oliver

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Spannende Diskussion! :-)

Noch mal ein Hinweis aus der Praxis: Neben den Compilern gibt es auch 
immer mehr andere Tools, die den Code lesen. Statische Code Analyse 
sollte genau die selben Annahmen machen, wie der Compiler. Sanitizer 
prüfen zur Laufzeit ob der Code sich an den Standard hält.

Wenn man vom Standard abweicht, muss man damit leben, dass der Code 
nicht portierbar ist, Optimierungen den Code brechen und das obige Tools 
im Projekt nicht einsetzbar sind. Evtl. muss ich auch den Warning-Level 
meines Compilers zurück drehen.

von Al Fine (Gast)


Lesenswert?

udok schrieb:
> Damals war 2-er Kompliment noch nicht so verbreitet, da gab es noch
> 1-er Kompliment, Vorzeichenbits, und Saturated Arithmetik.
>
> Diese Optimierungen sind völlig sinnlos, weil damit nie ein
> Geschwindigkeitsproblem gelöst wird,
> der Code ist ja aus einem wichtigen Grund drinnen!
> Aber funktionierender Code wird damit völlig unnötig zerstört.

Das ist doch aber gerade der Punkt: Da wird nichts zerstört. Wenn du 
weißt, dass deine Hardware 2er Komplement konsequent unterstützt, nimmst 
du eben "-fwrapv". Ist dann natürlich nicht mehr auf 
1er-Komplement-Maschinen portierbar.

Wenn man die Anmerkungen zum Speichermodell des Linux-Kernels liest, 
sind da auch Zusicherungen, die der C-Standard nicht macht. Und da 
kommen auch noch neue dazu, wenn alte Hardware nicht mehr unterstützt 
wird. Und gerade diese Grundannahmen wären wirklich schwer im Code zu 
erkennen, weil sie sich direkt in einzelnen Codezeilen widerspiegeln.

von Yalu X. (yalu) (Moderator)


Lesenswert?

udok schrieb:
>   return i+1 > i;

Wer so etwas als Ersatz  für
1
  return i != INT_MAX;

schreibt, fällt zurecht auf die Nase.

Dasselbe gilt für
1
        c = abs(c);
2
        if (c < 0)
3
          continue;

Beides ist Code, der mit der alleinigen Absicht geschrieben wurde, dem
Leser ein "Häh?" zu entlocken.

von Al Fine (Gast)


Lesenswert?

Yalu X. schrieb:
> udok schrieb:
>>   return i+1 > i;
>
> Wer so etwas als Ersatz  für
>   return i != INT_MAX;
>
> schreibt, fällt zurecht auf die Nase.
>
> Dasselbe gilt für
>         c = abs(c);
>         if (c < 0)
>           continue;
>
> Beides ist Code, der mit der alleinigen Absicht geschrieben wurde, dem
> Leser ein "Häh?" zu entlocken.

Die meisten Compiler bieten eine builtin Funktion für 
Addition+Overflow-Check an. In C++ kann man das sogar noch richtig 
verwenden, weil man da trotzdem noch einfach "a+b" schreiben kann. Aber 
im Ernst: In C? Wer schreibt denn statt "a+b" echt 
"__builtin_add_overflow"?

von Walter T. (nicolas)


Lesenswert?

Al Fine schrieb:
> Wer schreibt denn statt "a+b" echt
> "__builtin_add_overflow"?

Der Kreis schließt sich wieder. Ich. Manchmal braucht man das Wissen, ob 
ein Überlauf stattgefunden hat. Und diese builtin-Funktion ist das beste 
Mittel, unter C an das Carry-Bit zu gelangen.

: Bearbeitet durch User
von Al Fine (Gast)


Lesenswert?

Walter T. schrieb:
> Al Fine schrieb:
>> Wer schreibt denn statt "a+b" echt
>> "__builtin_add_overflow"?
>
> Der Kreis schließt sich wieder. Ich. Manchmal braucht man das Wissen, ob
> ein Überlauf stattgefunden hat.

Die "gefährlichen" Fälle sind nur leider die, wo das unerwartet kommt.

von udok (Gast)


Lesenswert?

Al Fine schrieb:
> Das ist doch aber gerade der Punkt: Da wird nichts zerstört. Wenn du
> weißt, dass deine Hardware 2er Komplement konsequent unterstützt, nimmst
> du eben "-fwrapv". Ist dann natürlich nicht mehr auf
> 1er-Komplement-Maschinen portierbar.

Eben, die Logik gehört umgedreht!  -fwrap muss der default sein, es gibt
seit 20 Jahren praktisch nichts anderes mehr.
Wer glaubt dass er die Optimierung braucht, der muss -fno-wrap anmachen.
Dazu kommt dass man Warnungen nicht mal mit -Wall immer bekommt.

Ein guter Compiler muss gutmütig sein, und Überraschungen minimieren.
Es bleibt eh noch immer genug an Unklarheiten in der libc übrig.

>
> Wenn man die Anmerkungen zum Speichermodell des Linux-Kernels liest,
> sind da auch Zusicherungen, die der C-Standard nicht macht. Und da
> kommen auch noch neue dazu, wenn alte Hardware nicht mehr unterstützt
> wird. Und gerade diese Grundannahmen wären wirklich schwer im Code zu
> erkennen, weil sie sich direkt in einzelnen Codezeilen widerspiegeln.

Jeder Code mit mehr als 1000 Zeilen lebt nicht im luftleeren Raum.
Da stecken immer Annahmen dahinter, viele davon implizit.
Die grösste Stärke von C ist der Präprozessor.
Der ist ein wichtiger Grund für den Erfolg von C.

Praktisch jedes sinnvolle Program steckt voll mit #ifdef - #define - 
#else - #endif Anweisungen.
Das ist nichts anderes als das Abfangen von Compiler-, Bibliotheks- und
hardwarespezifischen Sonderfällen.
C++ löst das Problem mit Templates und Konstantenausdrücken, die 
wegoptimiert werden.  Lesbarer wird es damit aber auch nicht.

von udok (Gast)


Lesenswert?

Yalu X. schrieb:
> udok schrieb:
>>   return i+1 > i;
>
> Wer so etwas als Ersatz  für
>   return i != INT_MAX;

In dem konstruierten Fall hast du recht.  Man könnte auch einfach 
unsigned
verwenden (unsigned hat definiertes Overflow Verhalten).
Aber es gibt halt auch viele reale und sicherheitskritische
Überlauf Abfragen, die mit der sinnlosen Optimierung nicht mehr 
funktionieren.  Das ist einfach nur Verschwendung von Lebenszeit.

von Al Fine (Gast)


Lesenswert?

udok schrieb:
> C++ löst das Problem mit Templates und Konstantenausdrücken, die
> wegoptimiert werden.  Lesbarer wird es damit aber auch nicht.

Das bezweifle ich. Sobald man keine einzige Verwendung von nackten 
Datentypen mehr hat, sondern nur noch templates zB mit @build-time 
definierbarer Overflow-Policy ergibt sich eine ganz neue Qualität. Das 
mit Macros nachzubauen ist wohl eher eine Übung zum abgewöhnen.

von Al Fine (Gast)


Lesenswert?

Al Fine schrieb:
> Das bezweifle ich. Sobald man keine einzige Verwendung von nackten
> Datentypen mehr hat, sondern nur noch templates zB mit @build-time
> definierbarer Overflow-Policy ergibt sich eine ganz neue Qualität. Das
> mit Macros nachzubauen ist wohl eher eine Übung zum abgewöhnen.

Von der Warte aus betrachtet, ist es übrigens schön, dass es die Option 
gibt, Overflows undefined sein zu lassen. Das sollte es auch für 
unsigned geben!

von Rolf M. (rmagnus)


Lesenswert?

udok schrieb:
> Damals war 2-er Kompliment noch nicht so verbreitet, da gab es noch
> 1-er Kompliment, Vorzeichenbits, und Saturated Arithmetik.

Letztere gibt es auch heute.

> Diese Optimierungen sind völlig sinnlos, weil damit nie ein
> Geschwindigkeitsproblem gelöst wird,
> der Code ist ja aus einem wichtigen Grund drinnen!

Du meinst, um einen Fall abzuchecken, der eigentlich gar nicht auftreten 
dürfte.

> Aber funktionierender Code wird damit völlig unnötig zerstört.

Das wiederholst du hier immer wieder, aber es stimmt nicht. Der Code war 
schon immer fehlerhaft. Der Fehler ist nur bisher nicht aufgefallen.

udok schrieb:
> Eben, die Logik gehört umgedreht!  -fwrap muss der default sein, es gibt
> seit 20 Jahren praktisch nichts anderes mehr.

Offenbar schon, denn sonst würde das ja schon von sich aus auch ohne 
funktionieren.

> Ein guter Compiler muss gutmütig sein, und Überraschungen minimieren.

Er muss sich gemäß Standard verhalten. Und er muss keine Sonderfälle 
berücksichtigen, zu denen der Standard ausdrücklich sagt, dass er sie 
nicht berücksichtigen muss.

> Jeder Code mit mehr als 1000 Zeilen lebt nicht im luftleeren Raum.
> Da stecken immer Annahmen dahinter, viele davon implizit.

… und viele davon falsch. Gerade falsche Annahmen sind eine der größten 
Fehlerquellen überhaupt in der Programmierung.

> Die grösste Stärke von C ist der Präprozessor.
> Der ist ein wichtiger Grund für den Erfolg von C.

Und doch ist er gleichzeitig das, was in allen danach entwickelten 
Sprachen auf Teufel-komm-raus vermieden wird.

von Walter T. (nicolas)


Lesenswert?

Ich halte es für sinnvoll, Threads mit der Überschrift "ARM-GCC" von C++ 
freizuhalten.

Im Gegenzug halten wir auch Threads mit der Überschrift "ARM-G++" von C 
frei.

Deal?

von Al Fine (Gast)


Lesenswert?

Walter T. schrieb:
> Ich halte es für sinnvoll, Threads mit der Überschrift "ARM-GCC"
> von C++
> freizuhalten.
>
> Im Gegenzug halten wir auch Threads mit der Überschrift "ARM-G++" von C
> frei.
>
> Deal?

Aber gcc schluckt "-std=c++2a"...

von Walter T. (nicolas)


Lesenswert?

Al Fine schrieb:
> Aber gcc schluckt "-std=c++2a"...

Touché. Aber es nervt trotzdem. Ich halte mich ja auch in C++-Threads 
damit zurück, wie einfach doch alles in Matlab geht.

von Al Fine (Gast)


Lesenswert?

Walter T. schrieb:
> Touché. Aber es nervt trotzdem. Ich halte mich ja auch in C++-Threads
> damit zurück, wie einfach doch alles in Matlab geht.

Richtig. In C hat man für die Masse an Code die Optionen
- fwrapv verwenden
- vor jeder Rechnung brav prüfen, ob sie denn nicht überläuft
- katastrophale Fehler in Kauf nehmen

Die 2 ist auch aus Performance-technischer Sicht alles andere als 
anzuraten.

von Oliver S. (oliverso)


Lesenswert?

Al Fine schrieb:
> Aber gcc schluckt "-std=c++2a"...

Der schluckt auch "gcc -x f77"  ;)

Oliver

von A. S. (Gast)


Lesenswert?

Torsten R. schrieb:
> Wenn man vom Standard abweicht, muss man damit leben, dass ... das obige Tools
> im Projekt nicht einsetzbar sind.

Nein. Solche über Jahre üblichen UBs sind quasi immer dort 
konfigurierbar. Wäre auch weltfremd, wenn die Prüfer ein eigenes 
Universum an Interpretationen erzwingen. Gerade im embedded Bereich.

von Rolf M. (rmagnus)


Lesenswert?

Walter T. schrieb:
> Ich halte es für sinnvoll, Threads mit der Überschrift "ARM-GCC" von C++
> freizuhalten.

GCC ist die GNU Compiler Collection, die unter anderem einen 
C++-Compiler beinhaltet. Und gcc ist laut manpage der "GNU project C and 
C++ compiler".

Al Fine schrieb:
> In C hat man für die Masse an Code die Optionen
> - fwrapv verwenden
> - vor jeder Rechnung brav prüfen, ob sie denn nicht überläuft

Warum sollte ich z.B. bei einem
1
for (int i = 0; i < 100; ++i)
vor dem Inkrement jedes mal prüfen, ob i vielleicht überlaufen könnte?

> - katastrophale Fehler in Kauf nehmen

von Walter T. (nicolas)


Lesenswert?

Rolf M. schrieb:
> GCC ist die GNU Compiler Collection, die unter anderem einen
> C++-Compiler beinhaltet.

Und "strict alias" ist ein typisches C- und kein C++-Problem, wenn die 
Jungs artig sind und auf Referenzen anstatt Zeigern arbeiten.

von Al Fine (Gast)


Lesenswert?

Rolf M. schrieb:
> Warum sollte ich z.B. bei einem
> for (int i = 0; i < 100; ++i)
> vor dem Inkrement jedes mal prüfen, ob i vielleicht überlaufen könnte?


Hast du nicht in Gedanken den Beweis geführt, bevor du das Beispiel 
gepostet hast? Warum denn gerade 100?

von Klaus W. (mfgkw)


Lesenswert?

Walter T. schrieb:
> Und "strict alias" ist ein typisches C- und kein C++-Problem, wenn die
> Jungs artig sind und auf Referenzen anstatt Zeigern arbeiten.

oh, in C++ gibt es keine Zeiger mehr?

von Walter T. (nicolas)


Lesenswert?

Du willst also zu den Bad Boys gehören. Das kommst sicher prima bei den 
Mädels an.

von Klaus W. (mfgkw)


Lesenswert?

Da muß ich kein bad boy sein.

Oder ich bin nicht gut genug in C++, aber eine doppelt verkettete Liste 
mit Referenzen statt Zeigern kriege ich nicht hin.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Klaus W. schrieb:

> Oder ich bin nicht gut genug in C++, aber eine doppelt verkettete Liste
> mit Referenzen statt Zeigern kriege ich nicht hin.
1
template < typename T >
2
using klaus_im_seine_list = std::list< std::ref< T > >;

SCNR

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