Forum: Mikrocontroller und Digitale Elektronik Wie auf Union mit Bitfeldern direkt zugreifen?


von S. R. (svenska)


Lesenswert?

Hallo,

ich bastle gerade an einem Headerfile für einen Controller, wo ich die 
einzelnen Register wie folgt bitweise definiere:

1
    #define un const volatile uint32_t
2
    #define ro const volatile uint32_t
3
    #define rw volatile uint32_t
4
    #define wo volatile uint32_t
5
6
    /* Registerdefinition */
7
    union UART_DR_u {
8
        rw reg;
9
        struct {
10
            unsigned       DATA : 8;
11
            unsigned const FE   : 1;
12
            unsigned const PE   : 1;
13
            unsigned const BE   : 1;
14
            unsigned const OE   : 1;
15
        };
16
    };
17
    /* ... */
18
19
    /* Peripheriedefinition */
20
    struct {
21
        union UART_DR_u  DR;
22
        union UART_RSR_u RSR;
23
        un Res0[4];
24
        /* ... */
25
    };
26
27
    /* Instanzen */
28
    #define UART0  ((struct UART_s*)0x4000C000)
29
    #define UART1  ((struct UART_s*)0x4000D000)

Damit kann ich mittels UART0->DR.DATA auf die einzelnen Felder 
zugreifen, aber auch mittels UART0->DR.reg auf das ganze Register. Das 
finde ich beinahe schön.

Gibt es eine Möglichkeit, die Register so zu definieren, dass ich auf 
das ".reg" verzichten kann, also dass UART0->DR das gesamte Register und 
UART0->DR.DATA das einzelne Feld beschreibt?

Gibt es die Möglichkeit, "write-only"-Felder so zu markieren, dass ich 
bei Lesezugriffen eine Warnung bekomme? Für "read-only"-Felder gibt es 
ja const.

Kann ich in der Peripheriedefinition namenlose Feldelemente als 
Platzhalter für reservierte Bereiche definieren?

Vielen Dank,
Svenska

von H.Joachim S. (crazyhorse)


Lesenswert?

Wofür genau könnte write-only denn sinnvoll sein??
Der WOM ist einfach nicht totzukriegen :-)

von S. R. (svenska)


Lesenswert?

> Wofür genau könnte write-only denn sinnvoll sein??
Wenn im Datenblatt steht, dass ein Register nicht gelesen werden darf, 
dann hätte ich gerne eine Warnung, wenn ich es trotzdem versuche.

von vn nn (Gast)


Lesenswert?

H.Joachim S. schrieb:
> Wofür genau könnte write-only denn sinnvoll sein??

Datenregister von µC-Peripherie wär ein möglicherweise sinnvolles Ziel, 
z.B. UDR der AVR-UART: Lesezugriff ließt vom Receiver, Schreibzugriff 
schreibt auf den Transmitter.

von H.Joachim S. (crazyhorse)


Lesenswert?

Da ja, aber doch nicht als struct im RAM?

von B. S. (bestucki)


Lesenswert?

S. R. schrieb:
> #define un const volatile uint32_t
> #define ro const volatile uint32_t
> #define rw volatile uint32_t
> #define wo volatile uint32_t

Bitte benutze typedef.


S. R. schrieb:
> Damit kann ich mittels UART0->DR.DATA auf die einzelnen Felder
> zugreifen, aber auch mittels UART0->DR.reg auf das ganze Register. Das
> finde ich beinahe schön.

Sei dir bewusst, dass das nach Standard undefiniertes Verhalten erzeugt. 
Viele Compiler unterstützen jedoch dieses "Feature". Das Handbuch des 
Compilers hilft da weiter.
Die mit const deklarierten Variablen können über reg jedoch geschrieben 
werden. Durch Optimierungen des Compilers können dabei komische Effekte 
auftreten. Falls die Werte durch die Hardware geändert werden können, 
sollten diese ebenfalls nicht als const deklariert werden (gleiches 
Problem). In diesem Fall müsste so oder so die union als volatile 
deklariert werden.


S. R. schrieb:
> #define UART0  ((struct UART_s*)0x4000C000)
> #define UART1  ((struct UART_s*)0x4000D000)

volatile nötig oder nicht?


S. R. schrieb:
> Gibt es eine Möglichkeit, die Register so zu definieren, dass ich auf
> das ".reg" verzichten kann, also dass UART0->DR das gesamte Register und
> UART0->DR.DATA das einzelne Feld beschreibt?

Nicht in C.


S. R. schrieb:
> Gibt es die Möglichkeit, "write-only"-Felder so zu markieren, dass ich
> bei Lesezugriffen eine Warnung bekomme?

Nein. Du könntest für deine union Funktionen bereitstellen und nicht 
mehr direkt auf die Member zugreifen.


S. R. schrieb:
> Kann ich in der Peripheriedefinition namenlose Feldelemente als
> Platzhalter für reservierte Bereiche definieren?

Ja, durch einen unnamed member:
1
struct foo{
2
  unsigned int A : 5;
3
  unsigned int : 4;
4
  unsigned int C : 3;
5
};

Beachte dabei Alignment und Padding, das Compilerhandbuch hilft weiter.

von S. R. (svenska)


Lesenswert?

> Da ja, aber doch nicht als struct im RAM?
Wo siehst du in meinem Beispiel eine "struct im RAM"?

> #define UART0 ((struct UART_s*)0x4000C000)
Das ist ein Zeiger auf eine Struktur, die an einer fixen Adresse (in 
diesem Fall dem UART0-Peripherieblock) liegt. Nicht im RAM.

von S. R. (svenska)


Lesenswert?

Be S. schrieb:
> Bitte benutze typedef.

Einverstanden. ;-)

>> Damit kann ich mittels UART0->DR.DATA auf die einzelnen Felder
>> zugreifen, aber auch mittels UART0->DR.reg auf das ganze Register.
> Sei dir bewusst, dass das nach Standard undefiniertes Verhalten erzeugt.

Das ist mir klar, aber auf der Ebene kann ich damit leben. Der Code ist 
ohnehin nicht portabel, da hardware-spezifisch.

> Die mit const deklarierten Variablen können über reg jedoch geschrieben
> werden.

Logisch, wobei "reg" auch const ist, wenn das gesamte Register read-only 
ist.

>> #define UART0  ((struct UART_s*)0x4000C000)
>> #define UART1  ((struct UART_s*)0x4000D000)
> volatile nötig oder nicht?

Hmm, das ist eine gute Frage. Im Augenblick ist jeder einzelne Member 
volatile (wegen den #defines/typedefs oben), daher habe ich mir das 
gespart.

Wenn ich die Struktur selbst volatile mache, kann ich mir das für die 
einzelnen Member sparen, richtig? Wie interagiert das mit "const", ist 
das dann auch "const volatile" und ist für mich nur lesbar, für die 
Hardware aber schreibbar?

>> Gibt es eine Möglichkeit, die Register so zu definieren, dass ich auf
>> das ".reg" verzichten kann, also dass UART0->DR das gesamte Register und
>> UART0->DR.DATA das einzelne Feld beschreibt?
> Nicht in C.

Gut, das war eigentliche Frage.

>> Gibt es die Möglichkeit, "write-only"-Felder so zu markieren, dass ich
>> bei Lesezugriffen eine Warnung bekomme?
> Nein. Du könntest für deine union Funktionen bereitstellen und nicht
> mehr direkt auf die Member zugreifen.

Schade, aber dann ist das eben so.

>> Kann ich in der Peripheriedefinition namenlose Feldelemente als
>> Platzhalter für reservierte Bereiche definieren?
>
> Ja, durch einen unnamed member:
> struct foo{
>   unsigned int A : 5;
>   unsigned int : 4;
>   unsigned int C : 3;
> };
> Beachte dabei Alignment und Padding, das Compilerhandbuch hilft weiter.

Das gilt für Bitfelder, aber kann ich sowas auch für normale Member 
einer Struct machen? Zum Beispiel hat die UART am Offset 0x10 kein 
Register, daher lege ich da ein Dummy-Array rein.

Vielen Dank erstmal,
Svenska

von B. S. (bestucki)


Lesenswert?

S. R. schrieb:
> Wenn ich die Struktur selbst volatile mache, kann ich mir das für die
> einzelnen Member sparen, richtig?

Richtig.


S. R. schrieb:
> Wie interagiert das mit "const", ist
> das dann auch "const volatile" und ist für mich nur lesbar, für die
> Hardware aber schreibbar?

const und volatile schliessen sich gegenseitig aus. Falls der Wert aber 
von der Hardware verändert werden kann, musst du die Variable zwingend 
mit volatile deklarieren. Ansonsten wird der Compiler optimieren und den 
Wert evt. einmalig bei Programmstart aus dem Register lesen.


S. R. schrieb:
> Das gilt für Bitfelder, aber kann ich sowas auch für normale Member
> einer Struct machen?

unnamed member gibts nur bei Bitfeldern. Bei normalen Strukturen müsste 
man das über Dummies erledigen.


EDIT:
Wenn man Member von Strukturen als const oder volatile deklariert, holt 
man sich meistens mehr Probleme in Boot, als man sonst schon hat. 
Entweder die gesamte Struktur mit const oder volatile deklarieren oder 
gar nichts.

: Bearbeitet durch User
von Andreas S. (Firma: Schweigstill IT) (schweigstill) Benutzerseite


Lesenswert?

Be S. schrieb:
> Falls die Werte durch die Hardware geändert werden können,
> sollten diese ebenfalls nicht als const deklariert werden (gleiches
> Problem). In diesem Fall müsste so oder so die union als volatile
> deklariert werden.

Const und volatile schließen sich nicht gegenseitig aus. Du hast genau 
den Fall beschrieben, für den tatsächlich eine Typqualifizierung mittels 
"const volatile" gedacht ist.

> Wenn man Member von Strukturen als const oder volatile deklariert, holt
> man sich meistens mehr Probleme in Boot, als man sonst schon hat.
> Entweder die gesamte Struktur mit const oder volatile deklarieren oder
> gar nichts.

Volle Zustimmung!

: Bearbeitet durch User
von B. S. (bestucki)


Lesenswert?

Andreas S. schrieb:
> Const und volatile schließen sich nicht gegenseitig aus. Du hast genau
> den Fall beschrieben, für den tatsächlich eine Typqualifizierung mittels
> "const volatile" gedacht ist.

Danke für die Berichtigung, da war ich falsch informiert. Ist auch so im 
Standard beschrieben (Kapitel 6.7.3):
> EXAMPLE 1 An object declared
> extern const volatile int real_time_clock;
> may be modifiable by hardware, but cannot be assigned to, incremented,
> or decremented.

von Nop (Gast)


Lesenswert?

S. R. schrieb:
> amit kann ich mittels UART0->DR.DATA auf die einzelnen Felder
> zugreifen, aber auch mittels UART0->DR.reg auf das ganze Register.

Du weißt aber schon, daß die Anordnung der Bitfields im 
zugrundeliegenden Datentyp nicht im C-Standard definiert ist? Der 
Compiler darf die legen, wie er lustig ist. Also vom MSB her damit 
anfangen, oder auch vom LSB.

Bitfields sind für diese Anwendung verführerisch, aber grundfalsch.

Die einzige Ausnahme, wo man eine Union mit einem Bitfield machen kann, 
ist zum Zwecke des Kopierens des ganzen Bitfields, und zum Vergleichen 
auf Identität. Und selbst dabei sollte man sich überzeugen, daß die 
Datentypen wirklich gleich breit sind.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Nop schrieb:
> Du weißt aber schon, daß die Anordnung der Bitfields im
> zugrundeliegenden Datentyp nicht im C-Standard definiert ist? Der
> Compiler darf die legen, wie er lustig ist. Also vom MSB her damit
> anfangen, oder auch vom LSB.

Sowas ist im (E)ABI definiert.  Der Compiler wählt ja auch nicht nach 
Gutdünken, ob ein int nun 2, 4 oder 8 Bytes groß ist, abhängig von 
Wochentag und Mondphase :-)


> Bitfields sind für diese Anwendung verführerisch, aber grundfalsch.

ACK.

von Nop (Gast)


Lesenswert?

Johann L. schrieb:
> Sowas ist im (E)ABI definiert.

Nein, das kann jeder Compiler nach Gutdünken tun. Hat ja mit ABI auch 
nichts zu tun. Das gibt witzige Effekte, wenn z.B. der Hersteller eines 
Chips sowas aus Inkomptetenz im Referenzcode per Bitfields macht, man 
aber dann einen anderen Compiler benutzt.

Oder wenn der Compiler seine Meinung spontan beim nächsten Release 
ändert, was er ja darf. Speziell die GCC-Leutchen sind bekannt dafür, 
daß es sie nicht interessiert, wieviel ohnehin zweifelhaften 
Produktivcode sie zerbrechen, wenn der Standard es erlaubt. Siehe 
Torvalds' Anfälle zum Thema GCC.

> ACK.

Eben drum. (:

von Imonbln (Gast)


Lesenswert?

S. R. schrieb:
> Das ist mir klar, aber auf der Ebene kann ich damit leben. Der Code ist
> ohnehin nicht portabel, da hardware-spezifisch.

bis zum nächsten Complier-Release, der es anders macht. Weil er was 
"Optimieren" kann :)

von S. R. (svenska)


Lesenswert?

Hallo,

>> Das gilt für Bitfelder, aber kann ich sowas auch für normale Member
>> einer Struct machen?
> unnamed member gibts nur bei Bitfeldern. Bei normalen Strukturen müsste
> man das über Dummies erledigen.

Schade, aber dann wohl nicht zu ändern.

> EDIT:
> Wenn man Member von Strukturen als const oder volatile deklariert, holt
> man sich meistens mehr Probleme in Boot, als man sonst schon hat.
> Entweder die gesamte Struktur mit const oder volatile deklarieren oder
> gar nichts.

Wenn ich die Struktur als volatile deklariere und den einzelnen Member 
als const, dann habe ich den gleichen Effekt, wie wenn ich den Member 
als const volatile deklarieren würde, richtig?

Dann baue ich das mal um.

Nop schrieb:
> Du weißt aber schon, daß die Anordnung der Bitfields im
> zugrundeliegenden Datentyp nicht im C-Standard definiert ist? Der
> Compiler darf die legen, wie er lustig ist. Also vom MSB her damit
> anfangen, oder auch vom LSB.

Mein arm-none-eabi-gcc fängt beim LSB an und ich gehe mal stark davon 
aus, dass das auch so bleiben wird. ;-)

> Bitfields sind für diese Anwendung verführerisch, aber grundfalsch.

Du würdest also eine #define-Orgie nehmen, die die einzelnen Felder für 
jedes Register ausmaskiert? Habe ich auch mal gemacht, finde ich aber 
eher furchtbar. An den Stellen, wo man es machen muss, kann man ja 
immernoch auf das ganze Register zugreifen.

Nop schrieb:
> Nein, das kann jeder Compiler nach Gutdünken tun. Hat ja mit ABI auch
> nichts zu tun. Das gibt witzige Effekte, wenn z.B. der Hersteller eines
> Chips sowas aus Inkomptetenz im Referenzcode per Bitfields macht, man
> aber dann einen anderen Compiler benutzt.

Hast du Beispiele?

> Oder wenn der Compiler seine Meinung spontan beim nächsten Release
> ändert, was er ja darf.

Hast du Beispiele zu Bitfeldern?

Wenn das nämlich wirklich so furchtbar ist, dann könnte ich auch die 
tausend #defines schreiben, aber das wollte ich eher vermeiden. Lieber 
stelle ich den Header komplett auf C++ mit einer handgeklopften 
Bitfeld-Klasse um, damit ich in der Beschreibung möglichst nah am 
Datenblatt bin.

Du hast natürlich vollkommen Recht, dass ich mich mit meiner Struktur 
sehr tief in schlechtdefinierte Gegenden wage, aber wenn das Risiko nur 
theoretisch ist, dann akzeptiere ich das freiwillig.

Gruß,
Svenska

von Nop (Gast)


Lesenswert?

S. R. schrieb:

> Du würdest also eine #define-Orgie nehmen, die die einzelnen Felder für
> jedes Register ausmaskiert?

So macht man das embedded halt. Außer wenn Dein Controller Bitbanding 
kann (nicht Bitbanging). Es wird übrigens noch viel "besser", wenn Du 
bedenkst, daß Lesezugriffe auf Register selbige mitunter auch verändern 
können. Ein Schreibzugriff per Bitfield macht Dir im Hintergrund eine 
Lese-Operation, die Du nicht "siehst".

Deswegen liest man das Register in einen passenden Integer (auf ARM 
sinnigerweise uint32_t) und arbeitet dann auf dieser Variablen damit.

> Hast du Beispiele?

http://embeddedgurus.com/stack-overflow/2009/12/a-c-test-the-0x10-best-questions-for-would-be-embedded-programmers/

"I recently had the misfortune to look at a driver written by Infineon 
for one of their more complex communications chip. It used bit fields, 
and was completely useless because my compiler implemented the bit 
fields the other way around. The moral – never let a non-embedded person 
anywhere near a real piece of hardware!"

> Hast du Beispiele zu Bitfeldern?

Du meinst, wozu man sie einsetzen kann? Man sollte das so gut wie 
überhaupt nicht tun:

http://embeddedgurus.com/stack-overflow/2009/10/effective-c-tip-6-creating-a-flags-variable/#comment-2390

Einzige Ausnahme: Man hat eine Menge Bool'scher Variablen und möchte 
gerne Speicher sparen. Dafür sind sie geeignet und auch gedacht, dann 
machen sie den Quelltext lesbarer.

> Wenn das nämlich wirklich so furchtbar ist, dann könnte ich auch die
> tausend #defines schreiben, aber das wollte ich eher vermeiden.

Es hat seinen Grund, daß man mit einer Menge Defines arbeitet, und mit 
Bitmasken.

> Du hast natürlich vollkommen Recht, dass ich mich mit meiner Struktur
> sehr tief in schlechtdefinierte Gegenden wage, aber wenn das Risiko nur
> theoretisch ist, dann akzeptiere ich das freiwillig.

Es kann gute Gründe geben, wieso man jenseits des C-Standards 
programmiert. Aber "gefällt mir halt so" ist keiner. Die Gegenden sind 
auch nicht schlecht definiert, sie sind undefiniert.

Und ganz besonders, wenn Du GCC einsetzt! Bei kommerziellen Compilern 
können es sich die Hersteller nicht leisten, den funktionierenden, wenn 
auch schlechten Code ihrer Kunden mit einem Update zu zerbrechen. GCC 
ist aber nicht kommerziell, und es ist ihnen vollkommen egal, was mit 
C-Code ist, der nicht nach Standard arbeitet.

Ich mag es außerdem, wenn ich einen neuen GCC nach dem ersten 
Bugfix-Release einfach einsetzen kann, ohne erstmal rumzurätseln, welche 
Option ich jetzt schon wieder setzen muß, damit mein Code immer noch 
läuft.

von S. R. (svenska)


Lesenswert?

Hallo,

> Ein Schreibzugriff per Bitfield macht Dir im Hintergrund eine
> Lese-Operation, die Du nicht "siehst".

Das stimmt.

> "I recently had the misfortune to look at a driver written by Infineon
> for one of their more complex communications chip. It used bit fields,
> and was completely useless because my compiler implemented the bit
> fields the other way around.

oO...

>> Du hast natürlich vollkommen Recht, dass ich mich mit meiner Struktur
>> sehr tief in schlechtdefinierte Gegenden wage, aber wenn das Risiko nur
>> theoretisch ist, dann akzeptiere ich das freiwillig.

Du hast mich überzeugt (insbesondere das zwingende RMW).
Dann also doch wieder zurück zu den #defines. Seufz.

Danke für den Input,
Gruß

von Klaus (Gast)


Lesenswert?

Nop schrieb:
> Und ganz besonders, wenn Du GCC einsetzt! Bei kommerziellen Compilern
> können es sich die Hersteller nicht leisten, den funktionierenden, wenn
> auch schlechten Code ihrer Kunden mit einem Update zu zerbrechen. GCC
> ist aber nicht kommerziell, und es ist ihnen vollkommen egal, was mit
> C-Code ist, der nicht nach Standard arbeitet.

Das mit den Bitfeldern, die über Ports und SFRs gelegt werden, machen 
die Compiler für die PICs schon lange so. Dabei ist der XC16 (und auch 
der XC32) der GCC. Die Fa. Microchip, die den Compiler offiziell ihren 
gewerblichen Kunden anbietet, ist also im Gegensatzt zu dir der Meinung, 
der GCC wird sich nicht soweit ändern, daß der Code ihrer Großkunden 
sowie geschätzt hunderttausende Zeilen ihrer Prozessorspezifischen 
Headerfiles obsolet werden.

MfG Klaus

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Nop schrieb:
> Oder wenn der Compiler seine Meinung spontan beim nächsten Release
> ändert, was er ja darf. Speziell die GCC-Leutchen sind bekannt dafür,

Kannst du dafür ein konkretes Beispiel nennen?

In welcher GCC-Version für welche Architektur hat sich das 
Bitfield-Layout "einfach so" geändert?

von Nop (Gast)


Lesenswert?

Klaus schrieb:

> Die Fa. Microchip, die den Compiler offiziell ihren
> gewerblichen Kunden anbietet, ist also im Gegensatzt zu dir der Meinung,

.. daß man den C-Standard ignorieren darf. Compliert doch, läuft doch, 
alles gut. Das sind genau diejenigen, die sich irgendwann wundern, wieso 
es nicht mehr läuft. Im Übrigen zeigt das auch, daß die Firma Mikrochip 
keine ernsthaften Kunden mit der Softwareseite adressieren will, sondern 
nur Hobbybastler.

Denn z.B. MISRA-Konformität ist damit nicht zu erreichen. Ich würde 
sowas in einem Codereview schlichtweg durchfallen lassen. Der Rest der 
Codebasis wird nämlich mit derselben Einstellung geschrieben worden 
sein. Will man also bei entsprechenden Produkten was machen, dann kann 
man den Microchip-Code direkt wegwerfen und neu coden.

Infineon, siehe zitierter Teil, war auch der Meinung, daß Bitfields da 
ne tolle Idee sind. Hardwarefirmen machen oft saumäßigen Code, das ist 
nichts Neues. Möglicherweise halt an den billigstmöglichen Anbieter 
irgendwo in Versklavistan outgesourced.


@ Johann:

> Kannst du dafür ein konkretes Beispiel nennen?

Das bezog sich darauf, daß GCC erwiesenermaßen bereits Bestandscode 
zerbrochen hat. Deren Reaktion war dann "aber der Standard erlaubt das 
doch". Siehe die Rants von Torvalds zum Thema GCC 4.9. Davor compilierte 
auch alles, und es lief. Selbe Einstellung wie bei Microchip und 
Infineon.

Ob die auch auf den Gedanken kommen, mit den Bitfields was zu machen, 
weiß ich nicht. Tatsache ist aber, wenn man deren Einstellung zu 
nicht-standardkonformem Code kennt, dann ist es eine wirklich schlechte 
Idee, GCC für solche Zwecke einzusetzen.

Denkbar wäre es z.B., daß man auf ARM bei Optimierung auf 
Geschwindigkeit immer 32bit-Ints zugrundelegt, weil das schneller geht 
als etwa 8 bit. Bei Optimierung auf Größe hingegen könnte man das 
durchaus mit 8 bit machen, wenn es paßt. C-Compiler müssen nicht das 
tun, was der Programmierer sagt, solange das Ergebnis funktional im 
Rahmen des Standards äquivalent ist.

Abgesehen davon ist es auch mit nachsichtigeren Compilerschreibern eine 
grundsätzlich schlechte Idee, sich nicht an den C-Standard zu halten. 
Mit der Ausnahme, daß man Dinge tatsächlich braucht, die in C so nicht 
möglich sind - beispielsweise Erweiterungen. attribute naked, used, 
interrupt, section und dergleichen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Nop schrieb:
> @ Johann:
>
>> Kannst du dafür ein konkretes Beispiel nennen?
>
> Das bezog sich darauf, daß GCC erwiesenermaßen bereits Bestandscode
> zerbrochen hat.

Weil es einen Bug gab, der dann behoben wurde? (möglich, weil Software 
nun mal Bugs haben kann, die aber auch behoben werden können)

Oder weil der "Bestandscode" nicht Standard-konform war und auf einmal 
anderer Code erzeugt wurde als in der Phantasie des Autors? (nach meiner 
Erfahrung mit Abstand die wahrscheinlichste Alternative, der Autor wird 
dann gerne zum Rumpelstielzchen)

Oder weil die GCC-Entwickler einfach mal so das Bitfeld-Layout geändert 
haben weil sie Langeweile und nix besseres zu tun hatten? (absolut 
unwahrscheinlich)

> Deren Reaktion war dann "aber der Standard erlaubt das doch".
> Siehe die Rants von Torvalds zum Thema GCC 4.9. Davor compilierte
> auch alles, und es lief.

Du meinst vermutlich "Memory corruption due to word sharing"

https://gcc.gnu.org/ml/gcc/2012-02/msg00005.html

> Ob die auch auf den Gedanken kommen, mit den Bitfields was zu machen,
> weiß ich nicht. Tatsache ist aber, wenn man deren Einstellung zu
> nicht-standardkonformem Code kennt, dann ist es eine wirklich
> schlechte Idee, GCC für solche Zwecke einzusetzen.

Was bitte soll denn das Kriterium sein wenn nicht ein Standard oder 
explizite Zusicherungen des Compilers, bei GCC z.B:

- dass Type Punning per Unions funktioniert

- dass die Zugriffsbreite auf volatile Bitfelder per
  -fstrict-volatile-bitfields durch den Basistyp des Bitfelds
  ausgedrückt wird.

Letzteres ist schon deshalb problematisch weil garnicht sichergestellt 
ist, dass eine Architektur entsprechende Befehle überhaupt HAT oder 
ausführen kann ohne eine Trap zu generieren.

Oder einfach Torvalds "compiler should not do something stupid"?

> Denkbar wäre es z.B., daß man auf ARM bei Optimierung auf
> Geschwindigkeit immer 32bit-Ints zugrundelegt, weil das schneller geht
> als etwa 8 bit.

Siehe -fstrict-volatile-bitfields.  Wie gesagt geht es dabei nicht ums 
Layout (das ist Sache des (E)ABI), sondern darum, welche Zugriffe 
(nicht) erzeugt werden.

http://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html#index-fstrict-volatile-bitfields-1217

von Nop (Gast)


Lesenswert?

Johann L. schrieb:
> Weil es einen Bug gab, der dann behoben wurde?

Nein. Erstens ist das beim GCC schon sehr selten. Wenn etwas nicht geht, 
ist es in aller Regel der C-Programmierer, der etwas verkehrt macht. 
Zweitens beheben die GCC-Leute tatsächliche Bugs ja auch. Drittens bauen 
sie Bugs nicht bewußt ein - Zerbrechen von nicht standardkonformem 
Produktivcode aber schon. Also sollte man sich entweder strikt an den 
Standard halten oder nicht GCC nutzen.

> Oder weil der "Bestandscode" nicht Standard-konform war und auf einmal
> anderer Code erzeugt wurde als in der Phantasie des Autors?

Genau das. Registermapping mit Bitfields ist übrigens auch nicht 
standardkonform. "Aber das funktioniert doch" ist schlichtweg kein 
Argument, weil das in anderen Dingen auch schon eine Fehlannahme war.

> Was bitte soll denn das Kriterium sein wenn nicht ein Standard

Sag ich ja.

> - dass Type Punning per Unions funktioniert

Logisch, C99 halt. Wobei der Teil im Standard etwas lax notiert ist und 
es von daher schon gut ist, wenn der Compiler das explizit klarstellt.

> Oder einfach Torvalds "compiler should not do something stupid"?

Ja, ein echter Brüller, nicht wahr? Zumal es eigentlich gut ist, daß GCC 
da endlich mal mit pointer aliasing aus dem Knick kam. Genau das ist 
nämlich ein wichtiger Grund, wieso Fortran bei numerischen Berechnungen 
immer noch schneller ist als C. Weil Aliasing bei Fortran schlichtweg 
nicht drin ist. Mit den heutigen Caches, wo die Speicheranbindung ein 
Flaschenhals ist, merkt man das bei entsprechender Software auch.

von S. R. (svenska)


Lesenswert?

Nop schrieb:
> Also sollte man sich entweder strikt an den
> Standard halten oder nicht GCC nutzen.

Damit verbietest du den GCC für jede Form von Kernelprogrammierung (und 
im Übrigen auch jeden anderen C-Compiler). Gratuliere.

>> Oder einfach Torvalds "compiler should not do something stupid"?
> Ja, ein echter Brüller, nicht wahr?

Ich darf davon ausgehen, dass der Autopilot im Flugzeug keinen groben 
Unfug treibt, ja. Wenn ich diese Annahme nicht mehr treffen darf, dann 
darf ich keinen Autopiloten mehr benutzen.

> Zumal es eigentlich gut ist, daß GCC da endlich mal
> mit pointer aliasing aus dem Knick kam.

Es wäre vielleicht mal gut, einen C-Compiler zu schreiben, der jede Form 
von undefiniertem (oder implementation-defined) Code zu einem NOP 
optimiert, und zwar rekursiv.

Damit kann man zwar keinen real existierenden Code mehr bauen, aber man 
wäre standardkonform.

von Daniel A. (daniel-a)


Lesenswert?

S. R. schrieb:
> Nop schrieb:
>> Also sollte man sich entweder strikt an den
>> Standard halten oder nicht GCC nutzen.
>
> Damit verbietest du den GCC für jede Form von Kernelprogrammierung (und
> im Übrigen auch jeden anderen C-Compiler). Gratuliere.

Das ist doch Blödsinn, es ist absolut kein Problem einen Kernel zu 
schreiben, ohne die C-Regeln zu verletzen. Die Initialisierung von 
Prozessorspezifischem wie z.B. wechseln in den Protected Mode der CPU 
oder speichern der Stackframe beim Taskswitch muss sowieso in ASM 
geschrieben werden, das linkt man dann einfach zum C-Code dazu. Der 
grössere Rest, der noch übrig bleibt, kann absolut Problemlos in C 
gelöst werden, ohne den Standard verletzen zu müssen. Oder kannst du dir 
irgendein Beispiel überlegen, bei dem man den Standard verletzen muss?

S. R. schrieb:
>> Zumal es eigentlich gut ist, daß GCC da endlich mal
>> mit pointer aliasing aus dem Knick kam.
>
> Es wäre vielleicht mal gut, einen C-Compiler zu schreiben, der jede Form
> von undefiniertem (oder implementation-defined) Code zu einem NOP
> optimiert, und zwar rekursiv.

Ich finde was der GCC mit dem pointer aliasing ebenfalls gut. Es 
steigert die Portabilität des Codes. Häufig warnt der GCC ja sogar, wenn 
man in die Alias falle tappt, das genügt mir föllig. Ich weiss noch 
damals, als ich gemerkt habe wie wenig die Implementierung von Bitfelder 
vom Standard vorgeschrieben wird. Wenn der GCC nicht etwas anderes 
gemacht hätte, als ich erwartete, wäre es mir nie aufgefallen. Und was 
habe ich dann gemacht? Ich habe mich nicht beschwert, sondern meinen 
Code korrigiert, alle Bitfelder raus. Ich finde es immer wieder gut, 
wenn beim GCC neue solche Dinge dazu kommen und der Code nichtmehr 
Kompiliert, weil man ja so Clever war, -Werror zu setzen. Dann meckere 
ich aber nicht über GCC, sondern behebe den Fehler im Code.

von Nop (Gast)


Lesenswert?

S. R. schrieb:
> Damit verbietest du den GCC für jede Form von Kernelprogrammierung (und
> im Übrigen auch jeden anderen C-Compiler). Gratuliere.

Nee. Embedded gibt's haufenweise RTOS, die nicht über undefiniertes 
Verhalten laufen. C ist doch für dieses Level von Programmierung 
überhaupt erst geschaffen worden.

Für ein paar Dinge nimmt man dann noch Assembler, weil die 
"C-Modellmaschine" mitunter zuviel abstrahiert. Auch beim STM32 geht der 
eine oder andere exception-handler nicht in C zu programmieren.

> Es wäre vielleicht mal gut, einen C-Compiler zu schreiben, der jede Form
> von undefiniertem (oder implementation-defined) Code zu einem NOP
> optimiert, und zwar rekursiv.

Lies Dir mal die dreiteilige Serie von Chris Lattner zum Thema 
"undefined behaviour in C" durch, ist schon sehr interessant. Zwar ist 
das aus der LLVM-Ecke, aber GCC hat ja mit denselben technischen 
Aspekten zu tun.

http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html

Das ist nicht so einfach, wie es aussieht, jedes undefinierte Verhalten 
als Fehler zu erkennen. In C ist aus sehr gutem Grund viel undefiniert, 
nämlich weil es schnell sein soll, und zwar auf unterschiedlicher 
Hardware.

> Damit kann man zwar keinen real existierenden Code mehr bauen, aber man
> wäre standardkonform.

GCC hat die neuen, sehr genialen sanitiser-Features für undefiniertes 
Verhalten. Der Nachteil ist für embedded bare metal leider, daß es nicht 
wirklich einfach zu benutzen ist, wenn man nicht von vornherein zwischen 
Applikation und Hardware-Layer trennt. Tut man das aber, dann kann man 
zumindest die Applikation z.B. unter Linux auf undefiniertes Verhalten 
testen.

von W.S. (Gast)


Lesenswert?

S. R. schrieb:
> /* Peripheriedefinition */
>     struct {
>         union UART_DR_u  DR;
>         union UART_RSR_u RSR;
>         un Res0[4];
>         /* ... */
>     };

Warum um himmelswillen bloß so etwas? manchmal schreibst du ganz 
vernünftig - und dann kommt plötzlich sowas daher. Also, gibt's einen 
echten Grund für derartige Akrobatik? Ich sehe keinen. Schreib dir nen 
richtigen seriellen Handler mit einem hardwareunabhängigen Headerfile - 
und zwar für jeden seriellen Port einen separaten, damit du nicht in 
switch (uart) { case uart1... case uart2... usw. dich verzetteln mußt. 
Wenn du es so anpackst, brauchst du obige Eiertänze überhaupt nicht, 
dein Code wird stabil und übersichtlich und wartbar und du hast final 
mehr Freude am Leben.

W.S.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Nop schrieb:
> Zerbrechen von nicht standardkonformem Produktivcode aber schon.

ROFL.  Was will man denn da groß "zerbrechen".

Wenn Murx wie x = x++ nicht mehr "funktioniert", dann wird ein anderer 
Compilerhersteller gesucht?  Anstatt den Murx zu beheben?

von S. R. (svenska)


Lesenswert?

Daniel A. schrieb:
> Das ist doch Blödsinn, es ist absolut kein Problem einen Kernel zu
> schreiben, ohne die C-Regeln zu verletzen.

Natürlich kann man alle C-Regeln einhalten, wenn man die Teile des 
Kernels, wo man sie nicht einhalten könnte, in Assembler programmiert. 
Aber das geht gerade am Sinn vorbei.

Im Übrigen hat ARM die Cortex-M extra so entworfe, dass man den 
Startup-Code vollständig in C schreiben kann.

> Oder kannst du dir irgendein Beispiel überlegen, bei dem man den
> Standard verletzen muss?

Linus hatte im verlinkten Thread dazu was geschrieben:
LT> The paper C standard can never promise what a kernel expects.
LT> There are tons of unspecified things that a compiler could do,
LT> including moving memory around behind our back as long as it moves
LT> it back. Because it's all "invisible" in the virtual C machine
LT> in the absence of volatiles.
LT> The fact that the kernel has things like SMP coherency requirements
LT> is simply not covered by the standard. There are tons of other
LT> things not covered by the standard too that are just
LT> "that's what we need".

Die ganze Diskussion zu den C11/C++11 Memory Models läuft beispielsweise 
darauf hinaus, dass der Compiler niemals mehr als eine Compilation Unit 
am Stück sieht und damit niemals das wahre Ausmaß seines generierten 
Codes vollständig einschätzen kann.

Nop schrieb:
> Lies Dir mal die dreiteilige Serie von Chris Lattner zum Thema
> "undefined behaviour in C" durch, ist schon sehr interessant. Zwar ist
> das aus der LLVM-Ecke, aber GCC hat ja mit denselben technischen
> Aspekten zu tun.

Habe ich. Von ihm kam ja die Idee eines Compilers, der jedes 
undefinierte Verhalten rigoros erkennt und darauf optimiert. Das muss 
weder schnell geschehen noch muss der generierte Code besonders gut 
sein.

Da ein C-Programm, welches an irgendeiner Stelle undefiniertes Verhalten 
benutzt, schon vor seiner eigenen Ausführung(!) undefiniert ist, könnte 
man an so einem Compiler schön sehen, wie sinnvoll eine zu enge 
Auslegung des Standards ist.

So etwas ähnliches könnte man auch auf Betriebssystemsebene machen, 
indem man z.B. jeden ungültigen Bibliotheksaufruf (memcpy() mit 
überlappenden Bereichen, ...) sofort mit abort() quittiert.

Ich halte es da ein bisschen wie Linus, denn realer Code ist am Ende nie 
standard-compliant. Es gibt da auch ein schönes Beispiel, wo gcc ein 
leeres Programm kompiliert und dabei selbst undefined behaviour 
ausnutzt.

W.S. schrieb:
> Schreib dir nen richtigen seriellen Handler mit einem
> hardwareunabhängigen Headerfile - und zwar für jeden
> seriellen Port einen separaten, ...

Der serielle Handler (eher UART-Treiber) muss auf irgendeiner Ebene mit 
Registern arbeiten. Genau da bin ich und hätte gerne sprechende Namen, 
nämlich genau die aus dem Datenblatt. Die stehen in meinem Headerfile.

Um den Code, der die Register dann anspricht, ging es mir an dieser 
Stelle noch garnicht. Der besteht im Augenblick aus

__attribute__((constructor)) void uart0_init();
uint8_t uart0_rx(void);
void uart0_tx(uint8_t);

und wird in einem separaten (hardware-unabhängigen) Headerfile 
beschrieben. Da das Gesamtsystem sowieso nur Spielerei ist (als 
Vorbereitung für andere Architekturen), reichen mir blockierende 
PIO-Routinen aus.

von Nop (Gast)


Lesenswert?

Johann L. schrieb:
> ROFL.  Was will man denn da groß "zerbrechen".

Wieso, Du fandest gerade eben undefinierten Code noch ganz toll mit dem 
"Argument", läuft doch (bisher). ICH habe von vornherein gesagt "lieber 
nicht". Aber schön, wenn wir da inzwischen übereinstimmen, hat ja ne 
Weile gedauert.

von Nop (Gast)


Lesenswert?

S. R. schrieb:
> Natürlich kann man alle C-Regeln einhalten, wenn man die Teile des
> Kernels, wo man sie nicht einhalten könnte, in Assembler programmiert.

Assembler braucht man nicht, um undefinierten C-Code zu vermeiden, 
sondern weil C z.B. direkte Manipulationen von Registern nicht erlaubt. 
Sowas wie einen exception handler für stack overflow kann man nicht in C 
schreiben.

Gib doch mal ein Beispiel, wo man undefinierten Code schreiben "muß", um 
Assembler zu vermeiden.

> Das muss weder schnell geschehen noch muss der generierte Code besonders
> gut sein.

Man könnte ja auch einfach eine Sprache nehmen, die dafür entworfen 
wurde. C ist das nicht.

> Im Übrigen hat ARM die Cortex-M extra so entworfe, dass man den
> Startup-Code vollständig in C schreiben kann.

Undefinierten Code braucht man dazu aber nicht. Wäre auch ungeschickt, 
weil Keil, IAR und GCC dazu garantiert verschiedene Meinungen hätten. Im 
Übrigen geht der Speichertest auch nicht in C - zumindest den Teil, wo 
der Stack reinsoll, muß man in Assembler machen.

> denn realer Code ist am Ende nie standard-compliant.

Fehler macht jeder, keine Frage. Das heißt aber nicht, daß man sie 
gezielt einbauen sollte, denn nichts anderes ist undefinierter Code 
nunmal.

von S. R. (svenska)


Lesenswert?

Nop schrieb:
> Gib doch mal ein Beispiel, wo man undefinierten Code schreiben "muß", um
> Assembler zu vermeiden.

Du hast doch schon zwei genannt:
- Speichertest
- Stack-Overflow Exception Handler

>> [ Compiler, der undefiniertes Verhalten gezielt ausnutzt ]
>> Das muss weder schnell geschehen noch muss der generierte Code besonders
>> gut sein.
> Man könnte ja auch einfach eine Sprache nehmen, die dafür entworfen
> wurde. C ist das nicht.

Entweder, du nimmst den Standard als das Maß aller Dinge, oder du nimmst 
den Standard als Richtlinie, von der auch abgewichen werden kann.

Im ersten Fall solltest du über einen Compiler mit dazu passender 
Standardbibliothek, die gezielt undefiniertes Verhalten finden und 
möglichst idiotisch ausnutzen, um möglichst viele Bugs zu triggern, doch 
froh sein?

> Fehler macht jeder, keine Frage. Das heißt aber nicht, daß man sie
> gezielt einbauen sollte, denn nichts anderes ist undefinierter Code
> nunmal.

Der Unterschied zwischen "implementation-defined" und "undefined" ist, 
dass man sich auf ersteres ebenfalls verlassen kann, wenn man denn will. 
Das ist nicht zwingend ein Fehler.

von Nop (Gast)


Lesenswert?

S. R. schrieb:
> Du hast doch schon zwei genannt:
> - Speichertest
> - Stack-Overflow Exception Handler

Die kann man überhaupt nicht in C schreiben, weder mit definiertem noch 
mit undefiniertem Code. Also bleibt meine Frage bestehen - Beispiele für 
die Notwendigkeit von undefiniertem Verhalten?

> Entweder, du nimmst den Standard als das Maß aller Dinge, oder du nimmst
> den Standard als Richtlinie, von der auch abgewichen werden kann.

Ersteres. Letzteres zerbricht Dir nämlich früher oder später ohnehin.

Die einzigen Abweichungen, die ich akzeptabel finde, sind 
compilerspezifische Erweiterungen, die dann aber auch wohldefiniert 
sind. Zu denken wäre hier an die __attribute-Erweiterungen. interrupt, 
naked, used, section, was die sind, die ich so brauche. Das geht 
embedded oftmals nicht anders, ist aber auch nicht undefiniert.

Auch #pragma kann vereinzelt sinnvoll sein, etwa um enge Code-Abschnitte 
mit O3 zu optimieren, wenn Messungen sie als Hotspot ausweisen. Dort 
kann man auch mit builtin-expect arbeiten.

Natürlich alles über defines gekapselt. Bei anderen Compilern kann man 
diese Dinge dann erstmal zu "nicht da" definen, so daß der Code halt 
etwas langsamer läuft, bis man die entsprechende Formulierung für den 
anderen Compiler dort einbaut.

> Im ersten Fall solltest du über einen Compiler mit dazu passender
> Standardbibliothek, die gezielt undefiniertes Verhalten finden und
> möglichst idiotisch ausnutzen, um möglichst viele Bugs zu triggern, doch
> froh sein?

So einfach ist es nicht. Es geht vielmehr so herum, daß der Compiler 
ANNIMMT, der Code sei definiert, und nur für diesen Fall seine Ausgabe 
korrekt macht. Die undefinierten Fälle kann er getrost ignorieren. 
Deswegen ist das Ergebnis schneller UND korrekt, wenn der Code definiert 
war. Das ist nunmal das Wesen von C.

Praktisches Beispiel: Shift Overflow. Shift um mehr die die Wortbreite 
ist undefiniert. Wenn man (32bit) eine 1 um 31 bit nach links shiftet, 
und dann separat nochmal um 1 nach links shiftet, das gibt 0. Intuitiv 
würde man erwarten, daß auch 0 kriegt, wenn man gleich um 32 shiftet. 
Nun hat aber z.B. der Opcode auf x86-32 für den Shiftbetrag nur 5 Bits, 
womit sich nur 0-31 darstellen lassen. 32 ergibt damit 0 in den 
untersten 5 bit, also ein Shift um 0, womit die Ausgangs-1 gar nicht 
geshiftet wird - und 1 rauskommt.

Wollte man das definiert auf allen CPUs haben, müßte man Laufzeit-Checks 
verbauen, was langsamer wäre. Deswegen undefined.

Daß die Konsequenz dann haufenweise "dieser buffer overflow wurde Ihnen 
präsentiert mit freundlicher Unterstützung der Programmiersprache C" 
ist, steht auf einem anderen Blatt. Wer bereit ist, Performance zu 
opfern und dabei mehr Zuverlässigkeit zu gewinnen, für den sehe ich C 
einfach als eine schlechte Wahl an. Dann sollte man sich doch gleich 
eine dafü gedachte Sprache nehmen. ADA wäre eine Möglichkeit, und für 
hochkritische Systeme wie z.B. Triebwerksteuerung eines Flugzeugs 
sicherlich eine bessere Grundentscheidung als ausgerechnet C.

Zum Finden von Bugs durch undefiniertes Verhalten habe ich ja schon auf 
GCCs Sanitiser verwiesen, der zur Laufzeit in Debug-Builds Prüfungen 
macht.

Dann kann und sollte man auch statische Codeanalyse verwenden. GCC mit 
maximalem Warnungslevel muß ohne Warnungen compilieren, sonst sieht man 
bei hunderten Warnungen die kritischen nicht mehr. Wenn ich Code kriege, 
der mit Warnungen durchcompiliert, sehe ich das als einen Hinweis auf 
schlampige Arbeit.

GCC ist mitunter gnadenlos, was undefinierten Code angeht, aber er läßt 
einen durchaus nicht im Regen stehen.

CppCheck sollte man auch regelmäßig über seine Sourcen laufen lassen. 
Für kommerziellen Einsatz gibt's auch teure Tools, die noch mehr finden 
und die sich durchaus rechnen können.

> Der Unterschied zwischen "implementation-defined" und "undefined" ist,
> dass man sich auf ersteres ebenfalls verlassen kann, wenn man denn will.
> Das ist nicht zwingend ein Fehler.

Zustimmung. Wenn es explizit ausgewiesen ist, ja. Nur sollte man sich 
gut überlegen, ob man das wirklich machen muß, und es vermeiden, wenn 
sich gleichwertige Alternativen finden. Es fällt einem spätestens dann 
auf die Füße, wenn man den Code migrieren muß.

Das Management sagt dann nämlich "gut, wir nehmen jetzt statt 8051 einen 
Cortex-M, dann können wir die bestehende Applikation auch gut erweitern 
und haben auf Jahre Ruhe vor Hardware-Redesigns. Ist ja alles in C, also 
sollte das bis aufs Hardwarelayer ja ohne Änderungen laufen". Und dann 
stehst Du da mit nem völlig anderen Compiler...

Erschwerend kommt dann hinzu, daß das einige Jahre später sein dürfte, 
wenn die Entwickler des Ausgangscodes nicht mehr in der Firma sind. 
Bzw., wenn das Opensource-Projekt verwaist ist.

von Andreas S. (Firma: Schweigstill IT) (schweigstill) Benutzerseite


Lesenswert?

Nop schrieb:
> Praktisches Beispiel: Shift Overflow. Shift um mehr die die Wortbreite
> ist undefiniert.

Neben der für x86 dargestellten Verhaltensweise darf der Compiler sogar 
noch weiter gehen und z.B. bei Schiebeoperationen um mehr als die Breite 
des Datentyps den Code auch komplett weglassen. Undefiniertes Verhalten 
muss nicht einmal reproduzierbar sein, sondern darf z.B. bei 
Folgeoperationen auf zufälligen Register- oder Speicherinhalten 
basieren.

Einige Compiler erzeugen bei niedrigen Optimierungsstufen noch 
"richtigen" Code, in dem auch zu große Schiebeoperationen noch wie vom 
Entwickler gewünscht ausgeführt werden. Erst bei höheren 
Optimierungsstufen wird dann rigiros zusammengestrichen. Daher finde ich 
es reichlich absurd, wenn in manchen Projekten die gesamte Entwicklung 
mit "-O0" (oder dem entsprechende Äquivalent) erfolgt und erst nach 
erfolgten Tests das eigentliche Release mit -O2 oder -O3 kompiliert 
wird.

> Dann kann und sollte man auch statische Codeanalyse verwenden. GCC mit
> maximalem Warnungslevel muß ohne Warnungen compilieren, sonst sieht man
> bei hunderten Warnungen die kritischen nicht mehr. Wenn ich Code kriege,
> der mit Warnungen durchcompiliert, sehe ich das als einen Hinweis auf
> schlampige Arbeit.

Ein früherer Kollege produzierte immer wieder äußerst fehlerhaften Code 
und war dann tage- und wochenlang am Herumstochern. Gleichzeitig waren 
seine Code-Anteile auch diejenigen mit den meisten Warnungen, was auch 
alle anderen Entwickler in dem Projekt aus den o.a. Gründen nervte. 
Dieser Kollege berief sich aber darauf, dass keine Warnungen mehr 
aufträten, wenn man den Code zweimal kompiliere. Das war auch kein 
Wunder, denn die Make-Regeln erkannten, dass der Quellcode nicht erneut 
kompiliert werden musste...

von Nop (Gast)


Lesenswert?

Andreas S. schrieb:
> Das war auch kein
> Wunder, denn die Make-Regeln erkannten, dass der Quellcode nicht erneut
> kompiliert werden musste...

LOL Das ist der Oberhammer!

von S. R. (svenska)


Lesenswert?

Nop schrieb:
> Natürlich alles über defines gekapselt. Bei anderen Compilern kann man
> diese Dinge dann erstmal zu "nicht da" definen, so daß der Code halt
> etwas langsamer läuft, bis man die entsprechende Formulierung für den
> anderen Compiler dort einbaut.

Du nimmst an, dass ich Code schreiben wollte, der compiler- und 
möglicherweise architekturunabhängig ist. Wenn du oben schaust, dann 
ging es mir aber gerade um ein compiler- und architekturspezifisches 
Headerfile (und eventuell die dazu gehörigen Hardwaretreiber).

An der Stelle ist mir Portabilität nicht wirklich wichtig. Den Code muss 
ich weder für hypothetische Compiler noch für hypothetische 
Architekturen optimieren. Compiler-Erweiterungen usw. sind dann fair 
game.

Für den portablen Teil (Anwendungslogik) gelten andere Regeln!

>> Im ersten Fall solltest du über einen Compiler mit dazu passender
>> Standardbibliothek, die gezielt undefiniertes Verhalten finden und
>> möglichst idiotisch ausnutzen, um möglichst viele Bugs zu triggern, doch
>> froh sein?
>
> So einfach ist es nicht. Es geht vielmehr so herum, daß der Compiler
> ANNIMMT, der Code sei definiert, und nur für diesen Fall seine Ausgabe
> korrekt macht.

Genau das ist die Grundlage für viele große, braune Haufen. Weil eben 
den Programmierern bei weitem nicht klar ist, was genau alles 
undefiniert/implementation-defined ist - und weil es umgebungsspezifisch 
ist.

Du argumentierst als Compiler-Hersteller. Selbstverständlich darf man 
sich hinstellen und "das ist laut Standard erlaubt" schreiben, aber 
effektiv sollte der Compiler dem Programmierer dienen, nicht umgekehrt.

> Wer bereit ist, Performance zu opfern und dabei mehr Zuverlässigkeit zu
> gewinnen, für den sehe ich C einfach als eine schlechte Wahl an.

Performance ist nicht binär, das sind alles Kompromisse. Und jeder ist 
grundsätzlich bereit, Performance für Zuverlässigkeit und Bequemlichkeit 
zu opfern, denn sonst würde man gleich Assembler schreiben.

> Dann sollte man sich doch gleich eine dafü gedachte Sprache nehmen.
> ADA wäre eine Möglichkeit, und für hochkritische Systeme wie
> z.B. Triebwerksteuerung eines Flugzeugs sicherlich eine bessere
> Grundentscheidung als ausgerechnet C.

Das hilft mir nicht, wenn weder ich noch mein Compiler Ada können. 
Außerdem gibt es einen großen Bereich zwischen "Triebwerkssteuerung" und 
"Web-Browser", wo C durchaus eine gute Wahl ist, dir aber solches 
Verhalten den Tag versaut.

> Wenn ich Code kriege, der mit Warnungen durchcompiliert, sehe ich das
> als einen Hinweis auf schlampige Arbeit.

Alter Produktivcode wirft eigentlich immer Warnungen auf modernen 
Compilern, selbst wenn er zur Entwicklungszeit keine hatte. Das fällt 
immer dann negativ auf, wenn "-Wall -Wextra -Werror" überall im 
Sourcebaum verteilt ist und das Build dann wegen unbenutzten Variablen 
bricht.

Andreas S. schrieb:
> Undefiniertes Verhalten muss nicht einmal reproduzierbar sein, sondern
> darf z.B. bei Folgeoperationen auf zufälligen Register- oder
> Speicherinhalten basieren.

Laut Standard darf der Compiler das gesamte Programm zu einem NOP 
kompilieren, weil "Shift größer Wortbreite" dazu führt, dass dein 
Programm schon vor seiner Ausführung ungültig war. Damit sind alle 
Seiteneffekte, die das Programm möglicherweise hatte, auch nichtig.

> Ein früherer Kollege produzierte immer wieder äußerst fehlerhaften Code
> und war dann tage- und wochenlang am Herumstochern. Gleichzeitig waren
> seine Code-Anteile auch diejenigen mit den meisten Warnungen, was auch
> alle anderen Entwickler in dem Projekt aus den o.a. Gründen nervte.

Das ist dann ein Problem fürs Management. ;-)

von Nop (Gast)


Lesenswert?

S. R. schrieb:

> An der Stelle ist mir Portabilität nicht wirklich wichtig. Den Code muss
> ich weder für hypothetische Compiler noch für hypothetische
> Architekturen optimieren. Compiler-Erweiterungen usw. sind dann fair
> game.

Dann solltest Du jedenfalls in der GCC-Doku nachschlagen, ob sie die 
Implementation von Bitfields denn klar definieren, so daß Du Dich 
zumindest mit diesem Compiler und dieser Plattform darauf auch verlassen 
kannst.

Da z.B. sowas wie Padding bei Bitfields nicht definiert ist, sind je 
nach Optimierungsstufe interessante Effekte durchaus denkbar. Es sei 
denn, es ist dokumentiert, was GCC da tut.

> Für den portablen Teil (Anwendungslogik) gelten andere Regeln!

Ah, ok.

> Genau das ist die Grundlage für viele große, braune Haufen.

Ja. Das ist bei C so. Performance ist der Schwerpunkt, das ist der Sinn 
dieser Sprache. C ist ein portabler Makro-Assembler.

> Weil eben
> den Programmierern bei weitem nicht klar ist, was genau alles
> undefiniert/implementation-defined ist - und weil es umgebungsspezifisch
> ist.

Undefiniertes Verhalten ist nicht umgebungsspezifisch, sondern steht im 
C-Standard. Shift Overflow ist nicht nur auf x86 undefined, sondern 
überall.

> Du argumentierst als Compiler-Hersteller.

Nein, auch als C-Nutzer. Ich will maximale Performance, ohne mich 
deswegen flächendeckend auf Assembler einlassen zu müssen. Ich will 
Portabilität zwischen Plattformen, zumindest in der Applikation. Für 
Debug-Builds hat der GCC den Sanitiser - lahm, und das wäre ein 
"sicheres" C dann nämlich genauso, aber für Testläufe ist das ja kein 
Problem.

Wenn C-Programmierer sich erstens nicht mit C auskennen, zweitens die 
Compiler-Warnungen ignorieren und drittens den eigens angebotenen 
Sanitiser nicht nutzen, dann ist das IMO ein Layer-8-Problem.

> effektiv sollte der Compiler dem Programmierer dienen, nicht umgekehrt.

In C tut der Compiler, was man ihm sagt. Und nicht das, von dem er raten 
mag, daß das wohl gemeint sein könnte.

> Das hilft mir nicht, wenn weder ich noch mein Compiler Ada können.

Ersteres kann man lernen, wenn man die Sicherheit benötigt. Zweiteres 
kann GCC sehr wohl.

> Außerdem gibt es einen großen Bereich zwischen "Triebwerkssteuerung" und
> "Web-Browser", wo C durchaus eine gute Wahl ist, dir aber solches
> Verhalten den Tag versaut.

Dann nutz die Warnungen, nutz statische Codechecker, nutz den Sanitiser 
für Debugbuilds. Letzterer geht übrigens nur unter Linux, leider nicht 
unter Cygwin, aber mal eben ne Live-Distro booten ist auch als 
Windowsnutzer kein Akt.

> Alter Produktivcode wirft eigentlich immer Warnungen auf modernen
> Compilern, selbst wenn er zur Entwicklungszeit keine hatte.

Gutes Argument, weil die Compiler ja auch Fortschritte gemacht haben und 
mehr komische Dinge erkennen. Das heißt aber auch nur, daß man den Code 
dann mal geradeziehen muß. Wofür es vom Management nie Geld gibt, dafür 
aber gerne die dreifache Summe, wenn irgendwas dann im Feld schiefgeht. 
;-)

von S. R. (svenska)


Lesenswert?

Nop schrieb:
>> Genau das ist die Grundlage für viele große, braune Haufen.
>
> Ja. Das ist bei C so. Performance ist der Schwerpunkt, das ist der Sinn
> dieser Sprache. C ist ein portabler Makro-Assembler.

Mit dem Unterschied, dass beim Makro-Assembler sämtliches Verhalten 
definiert ist.

>> Weil eben den Programmierern bei weitem nicht klar ist, was genau
>> alles undefiniert/implementation-defined ist - und weil es
>> umgebungsspezifisch ist.
>
> Undefiniertes Verhalten ist nicht umgebungsspezifisch, sondern steht im
> C-Standard. Shift Overflow ist nicht nur auf x86 undefined, sondern
> überall.

Mag ja sein, dass der Tatbestand universell existiert. Ob er aber 
auftritt, hängt von der Umgebung ab. Ob (i << 16) definiert ist oder 
nicht, hängt von der Wortbreite und damit von der Umgebung ab.

Außerdem bleibe ich dabei, dass den meisten Programmierern nicht klar 
ist, was genau undefiniert ist. Ich bin mir auch sicher, dass du nicht 
alle Fälle kennst. Und das führt wieder zu braunen Haufen.

> In C tut der Compiler, was man ihm sagt. Und nicht das, von dem er raten
> mag, daß das wohl gemeint sein könnte.

Eben nicht. Der Compiler tut das, was ich ihm sage, /wenn er der Meinung 
ist, das sei so erlaubt/. Das ist etwas völlig anderes, was man zwar mit 
dem Standard rechtfertigen kann, aber vielen ziemlich grundlos ins Bein 
schießt.

von Nop (Gast)


Lesenswert?

S. R. schrieb:
> Mit dem Unterschied, dass beim Makro-Assembler sämtliches Verhalten
> definiert ist.

Nein. Ein Shift um mehr als die Wortbreite ist auch dort nicht 
definiert. Weil, wie erwähnt, x86-32 nur 5 bit dafür HAT.

> Mag ja sein, dass der Tatbestand universell existiert. Ob er aber
> auftritt, hängt von der Umgebung ab. Ob (i << 16) definiert ist oder
> nicht, hängt von der Wortbreite und damit von der Umgebung ab.

Wenn man die standard-Datentypen wie uint32_t nutzt, hat man das Problem 
nicht. Die wurden allerdings erst mit C99 eingeführt, und das ist erst 
17 Jahre her, von daher hat sich das natürlich noch nicht überall 
herumgesprochen.

> Ich bin mir auch sicher, dass du nicht
> alle Fälle kennst. Und das führt wieder zu braunen Haufen.

Nunja, ich weiß zumindest, was die häufigen Fehler dabei sind. Das weiß 
ich, weil ich auch entsprechende Blogs lese. Was daher kommt, daß ich 
mich dafür eben interessiere und mein Werkzeug auch gut handhaben 
möchte. Deswegen kenne und schätze ich auch das Sanitiser-Feature des 
GCC gegen undefiniertes Verhalten.

Also wirklich, da servieren einem die GCC-Leute schon die Möglichkeit, 
undefiniertes Verhalten sogar zur Laufzeit zu prüfen, auf dem 
Silbertablett - und es ist immer noch nicht recht? Soll man C-Compiler 
lahmen Code erzeugen lassen, weil Leute zu faul für einen Debug-Build 
mit allen checks sind? Gefällt mir nicht.

Übrigens, für umfangreiche Anwendungen halte ich C schon für fragwürdig. 
Dafür ist C nunmal nicht gedacht, sondern es ist eine 
Systemprogrammiersprache. Ich hab GUI-Programmierung in purem C gemacht, 
das war der Horror. Dafür gibt's geeignetere Werkzeuge.

> Das ist etwas völlig anderes, was man zwar mit
> dem Standard rechtfertigen kann, aber vielen ziemlich grundlos ins Bein
> schießt.

Erstens: nein, nicht grundlos. Es ist gerade der Spielraum des 
undefined, der so aggressive Optimierungen überhaupt erst ermöglicht, 
wie heutige C-Compiler sie beherrschen. Das sind zwei Seiten derselben 
Medaille.

Mal ein Beispiel, WIE aggressiv GCC das kann: Neulich hatte ich das 
Problem, in einer Schleife in einem Hotspot eine größere 
switch-case-Struktur zu haben. Die wollte ich optimieren, weswegen ich 
versuchshalber mal mit computed gotos (Sprungtabellen) rangegangen bin, 
um die Branches zu eliminieren. Das ging, weil der Wertebereich der 
cases nicht sehr groß war. Ich war sehr überrascht, daß GCC bei O2 
minimal, aber meßbar schneller war ohne die computed gotos. Weil der 
etwas Ähnliches nämlich auch von selber aus dem switch macht - nur 
besser, wenn ich ihm nicht halb reinpfusche. Bemerkenswert.

Zweitens sollen die sich halt eine andere Sprache suchen, mit der sie 
leichter zurechtkommen. Möglichst auch noch eine, wo sie sich nicht mit 
dem Werkzeug beschäftigen müssen. Das wird dann allerdings schwierig. 
Wenn man sein Werkzeug nicht kennt, kann man damit halt nicht arbeiten.

von S. R. (svenska)


Lesenswert?

Nop schrieb:
> S. R. schrieb:
>> Mit dem Unterschied, dass beim Makro-Assembler sämtliches Verhalten
>> definiert ist.
>
> Nein. Ein Shift um mehr als die Wortbreite ist auch dort nicht
> definiert. Weil, wie erwähnt, x86-32 nur 5 bit dafür HAT.

Wenn es nur 5 Bit dafür gibt, kannst du keine 6 Bit Shiftweiter 
ausdrücken (bzw. der Assembler sollte das eher als ungültigen Operanden 
verwerfen).

>> Mag ja sein, dass der Tatbestand universell existiert. Ob er aber
>> auftritt, hängt von der Umgebung ab. Ob (i << 16) definiert ist oder
>> nicht, hängt von der Wortbreite und damit von der Umgebung ab.
>
> Wenn man die standard-Datentypen wie uint32_t nutzt, hat man das Problem
> nicht.

Dennoch finden alle Berechnungen erstmal als "int" statt, nicht als 
uint32_t oder was auch immer.

> Nunja, ich weiß zumindest, was die häufigen Fehler dabei sind. Das weiß
> ich, weil ich auch entsprechende Blogs lese. Was daher kommt, daß ich
> mich dafür eben interessiere und mein Werkzeug auch gut handhaben
> möchte. Deswegen kenne und schätze ich auch das Sanitiser-Feature des
> GCC gegen undefiniertes Verhalten.

Das ist löblich. Als Grundvoraussetzung für C ist das aber Unfug. Die 
Mehrheit der Programmierer will C nicht ein Leben lang studieren, 
sondern benutzen - und dafür wurde es mal entworfen.

>> Das ist etwas völlig anderes, was man zwar mit
>> dem Standard rechtfertigen kann, aber vielen ziemlich grundlos ins Bein
>> schießt.
>
> Erstens: nein, nicht grundlos. Es ist gerade der Spielraum des
> undefined, der so aggressive Optimierungen überhaupt erst ermöglicht,
> wie heutige C-Compiler sie beherrschen. Das sind zwei Seiten derselben
> Medaille.

Nein. Ich müsste das Paper mal raussuchen, aber aggressiv mit 
undefiniertem Verhalten optimieren bringt selten mehr als 5% 
Leistungsvorteil. Ein nicht aufgefangener Cache Miss ist teurer.

Dafür riskierst du halt Sicherheitslücken und Bugs. Einen 
Integer-Overflow-Check baust du sicherlich nicht ein, damit der 
Optimizer den als "integer overflow ist undefined" rauswirft, sondern um 
Randfälle gezielt abzufangen.

> Zweitens sollen die sich halt eine andere Sprache suchen, mit der sie
> leichter zurechtkommen. Möglichst auch noch eine, wo sie sich nicht mit
> dem Werkzeug beschäftigen müssen. Das wird dann allerdings schwierig.
> Wenn man sein Werkzeug nicht kennt, kann man damit halt nicht arbeiten.

Mach das mal auf Systemen, wo du nur einen C-Compiler hast. Selbst, wenn 
du einen GCC hast, heißt das noch lange nicht, dass du damit auch C++ 
oder Ada spielen kannst.

Eine Systemsprache, ähnlich wie C, mit weniger undefiniertem Verhalten 
und ein paar historischen Unfällen weniger wäre schon schön. Es gibt ja 
fehlervermeidende APIs, die taugen. Aber das will ja niemand, sonst gäbs 
das ja...

von Daniel A. (daniel-a)


Lesenswert?

S. R. schrieb:
> Nop schrieb:
>> S. R. schrieb:
>>> Mit dem Unterschied, dass beim Makro-Assembler sämtliches Verhalten
>>> definiert ist.
>>
>> Nein. Ein Shift um mehr als die Wortbreite ist auch dort nicht
>> definiert. Weil, wie erwähnt, x86-32 nur 5 bit dafür HAT.
>
> Wenn es nur 5 Bit dafür gibt, kannst du keine 6 Bit Shiftweiter
> ausdrücken (bzw. der Assembler sollte das eher als ungültigen Operanden
> verwerfen).

Nein, ein Assembler sollte dann mit einem Fehler abbrechen.

>>> Mag ja sein, dass der Tatbestand universell existiert. Ob er aber
>>> auftritt, hängt von der Umgebung ab. Ob (i << 16) definiert ist oder
>>> nicht, hängt von der Wortbreite und damit von der Umgebung ab.
>>
>> Wenn man die standard-Datentypen wie uint32_t nutzt, hat man das Problem
>> nicht.
>
> Dennoch finden alle Berechnungen erstmal als "int" statt, nicht als
> uint32_t oder was auch immer.

Nein, nicht wenn z.B. int kleiner als uint32_t ist.

>> Nunja, ich weiß zumindest, was die häufigen Fehler dabei sind. Das weiß
>> ich, weil ich auch entsprechende Blogs lese. Was daher kommt, daß ich
>> mich dafür eben interessiere und mein Werkzeug auch gut handhaben
>> möchte. Deswegen kenne und schätze ich auch das Sanitiser-Feature des
>> GCC gegen undefiniertes Verhalten.
>
> Das ist löblich. Als Grundvoraussetzung für C ist das aber Unfug. Die
> Mehrheit der Programmierer will C nicht ein Leben lang studieren,
> sondern benutzen - und dafür wurde es mal entworfen.

C hat nur sehr wenige Konstrukte und Ausnahmen, die man sich merken 
muss, besonders im vergleich zu anderen Programmiersprachen. Wenn man 
sich einmal ernsthaft mit der Sprache auseinandersetzt, kann man alles 
in weniger als einem Jahr lernen. Sorry, aber ein Informatiker der seine 
Werkzeuge nicht kennt und es auch nicht versucht ist ein schlechter 
Informatiker.

>>> Das ist etwas völlig anderes, was man zwar mit
>>> dem Standard rechtfertigen kann, aber vielen ziemlich grundlos ins Bein
>>> schießt.
>>
>> Erstens: nein, nicht grundlos. Es ist gerade der Spielraum des
>> undefined, der so aggressive Optimierungen überhaupt erst ermöglicht,
>> wie heutige C-Compiler sie beherrschen. Das sind zwei Seiten derselben
>> Medaille.
>
> Nein. Ich müsste das Paper mal raussuchen, aber aggressiv mit
> undefiniertem Verhalten optimieren bringt selten mehr als 5%
> Leistungsvorteil. Ein nicht aufgefangener Cache Miss ist teurer.

Es kommt immer auf das wo an. z.B. bei der Manipulation von grossen 
Datenmengen kann das einen grossen unterschied machen, ob der Kompiler 
nun weiss, das der inhalt von buffer a nicht geändert wird, wenn in 
buffer b geschrieben wird. Und was wilst du mit dem "Cache Miss" sagen? 
Das kann keine Programmiersprache verhindern, aber viele Kompiler 
beherschen es besser als die Assembler typen.

> Dafür riskierst du halt Sicherheitslücken und Bugs.

Das würde man, wenn man undefiniertes verhalten ausnutzt, und auf 
sämtliche Kompilerunterstützung, die einen darauf hinweisen Ignoriert. 
Was in undefinierten fällen sinvoll wäre, ist ausserdem ansichtssache. 
Ich könnte dir sagen: "Teile den Kuchen für 0.0 Personen auf", und ich 
würde unendlich viele stücke erwarten. Für andere gibt es immernoch nur 
ein Stück, und beide sind falsch.

> Einen
> Integer-Overflow-Check baust du sicherlich nicht ein, damit der
> Optimizer den als "integer overflow ist undefined" rauswirft, sondern um
> Randfälle gezielt abzufangen.

1) Wiso verwendest du keine unsigned integer!?! Signed integer sind 
wirklich nur selten nützlich.
2) Wiso implementierst du die Prüfung falsch, du willst doch vorher 
wissen, ob es einen geben würde?
3) Spätestens bei den Unittests fällt das auf, besonders wenn man mit 
-ftrapv kompiliert.

PS: Ich mag es nicht, wenn C als Macroassembler bezeichnet wird. Für 
mich ist es eine hochsprache. Echtes C ist Platformunabhängig, dafür 
wurde es erfunden, deshalb wurde Unix in c neu implementiert.

von Nop (Gast)


Lesenswert?

S. R. schrieb:
> Wenn es nur 5 Bit dafür gibt, kannst du keine 6 Bit Shiftweiter
> ausdrücken (bzw. der Assembler sollte das eher als ungültigen Operanden
> verwerfen).

Im Trivialfall, daß Du um einen konstanten Betrag shiftest, warnt Dich 
der C-Compiler auch schon. Warnung ignoriert? Layer-8-Problem. Nein, 
spannend wird es dich erst, wenn das Shiften um einen variablen Betrag 
erfolgt, der erst zur Laufzeit ermittelt werden kann.

> Dennoch finden alle Berechnungen erstmal als "int" statt, nicht als
> uint32_t oder was auch immer.

Auch auf einem 16bit-System ist ein 16-bit-Shift auf einen uint32_t 
wohldefiniert. Das ist der Witz an den Standard-Datentypen. Nach 17 
Jahren C99 immer noch mit raw int arbeiten, wenn die Wortbreite 
entscheidend ist? Layer-8-Problem.

> Das ist löblich. Als Grundvoraussetzung für C ist das aber Unfug.

Kommt drauf an. Für Hobbyprogrammierer, die mal eben ein wenig mit einem 
Microcontroller spielen wollen, gebe ich Dir recht. Aber an dieser 
Klientel richtet man keine Tools wie C-Compiler aus. Bei Leuten, die 
ernsthaft in dem Bereich tätig sind, erwarte ich, daß sie jedenfalls die 
häufigsten undefined-Stellen kennen. Viele sind es nicht.

Genauso wie man sich auch im Klaren sein sollte, daß die 
Operator-Prioritäten in C gelegentlich verwirrend sein können. Selbst 
wenn man sie nicht alle kennt, dann klammert man halt. Mache ich bei 
meinen Code sowieso, weil den auch andere Leute lesen können sollen.

> Die
> Mehrheit der Programmierer will C nicht ein Leben lang studieren,
> sondern benutzen - und dafür wurde es mal entworfen.

Für "einfach mal eben anwenden" gibt es diverse Scriptsprachen, die 
einem auch gleich das Ressourcenmanagement abnehmen. Genaugenommen war 
es nicht C, was so gedacht war, sondern Basic.

> Ein nicht aufgefangener Cache Miss ist teurer.

Aha. Undefined behahviour im Hinblick auf Pointer ALiasing adressiert 
aber gerade die Cache Misses. Genau wie ich sagte, das dient der 
Performance.

> Dafür riskierst du halt Sicherheitslücken und Bugs.

Dagegen gibt's Tools für die Debugphase. Wieso willst Du die eigentlich 
nicht benutzen? Wirklich, der Sanitiser beißt nicht, und CppCheck hat 
mir auch nur mal aus Reflex ein wenig in den Finger gebissen, weil es 
sich angesichts unerwartet frühmorgendlicher Nutzung erschreckt hatte.

> Einen
> Integer-Overflow-Check baust du sicherlich nicht ein, damit der
> Optimizer den als "integer overflow ist undefined" rauswirft, sondern um
> Randfälle gezielt abzufangen.

Erstens ist unsigned overflow sehr wohl definiert. Zweitens heißen die 
beiden Zauberworte hier wiederum "debug-build" und "assert".

> Mach das mal auf Systemen, wo du nur einen C-Compiler hast.

Die sind auch nichts für Anfänger.

> Selbst, wenn
> du einen GCC hast, heißt das noch lange nicht, dass du damit auch C++
> oder Ada spielen kannst.

Warum nicht? Gibt Leute, die C++ auf Mikrocontrollern verwenden. Ich bin 
kein Fan von C++, aber wenn man sich auf C plus Klassen beschränkt (im 
Wesentlichen), dann soll das nicht mehr Speicher kosten als C. Dazu muß 
man, Überraschung, allerdings C++ beherrschen und insbesondere wissen, 
welche Features man lieber nicht nutzen sollte.

> Eine Systemsprache, ähnlich wie C, mit weniger undefiniertem Verhalten
> und ein paar historischen Unfällen weniger wäre schon schön.

Mit einer Systemprogrammiersprache wird man IMMER einigen Unsinn machen 
können. Allein schon wegen der Pointer und deren Casting.

> Es gibt ja fehlervermeidende APIs, die taugen.

Was haben APIs denn jetzt damit zu tun?

von Nop (Gast)


Lesenswert?

Daniel A. schrieb:
> PS: Ich mag es nicht, wenn C als Macroassembler bezeichnet wird. Für
> mich ist es eine hochsprache. Echtes C ist Platformunabhängig, dafür
> wurde es erfunden, deshalb wurde Unix in c neu implementiert.

Deswegen bezeichne ich es ja auch als "portablen Macroassembler". :-)

von S. R. (svenska)


Lesenswert?

Daniel A. schrieb:
>> Die Mehrheit der Programmierer will C nicht ein Leben lang studieren,
>> sondern benutzen - und dafür wurde es mal entworfen.
>
> C hat nur sehr wenige Konstrukte und Ausnahmen, die man sich merken
> muss, besonders im vergleich zu anderen Programmiersprachen.

Es gibt ein Paper von der TU Wien: "What every compiler compiler writer 
should know about programmers" 
(http://www.complang.tuwien.ac.at/kps2015/proceedings/KPS_2015_submission_29.pdf). 
Lies das mal.

Ich zitiere:

>>> There are 203 undefined behaviours listed in appendix J of the
>>> C11 standard (up from 191 in C99).

Das ist nicht gerade "sehr wenig".

> Wenn man sich einmal ernsthaft mit der Sprache auseinandersetzt, kann
> man alles in weniger als einem Jahr lernen.

Wenn es so einfach wäre, warum gibt es dann ständig Bugs und 
Sicherheitsprobleme deswegen?

> Sorry, aber ein Informatiker der seine Werkzeuge nicht kennt und es
> auch nicht versucht ist ein schlechter Informatiker.

Ach, da bin ich aber froh. Ich bin nämlich überhaupt kein Informatiker.

Davon abgesehen: Es gibt einen Unterschied zwischen "ich, der sich damit 
ein kleines bisschen auskennt, finde das große Scheiße und eine 
nachweislich nahezu nutzlose Grundlage für viele real existierende 
Probleme" und "ich vermeide das in meinem Code, wenn möglich".

> Es kommt immer auf das wo an. z.B. bei der Manipulation von grossen
> Datenmengen kann das einen grossen unterschied machen, ob der Kompiler
> nun weiss, das der inhalt von buffer a nicht geändert wird, wenn in
> buffer b geschrieben wird.

Strohmann. Wenn ich "const" ranschreibe, weiß der Compiler, dass da nix 
geändert wird. Da braucht der nicht über undefiniertes Verhalten 
inferieren.

> Und was wilst du mit dem "Cache Miss" sagen?

Dass ein Cache Miss mehr Performance kostet als eine nicht gemachte 
Optimierung, die undefiniertes Verhalten gezielt ausnutzt.

>> Einen Integer-Overflow-Check baust du sicherlich nicht ein, damit
>> der Optimizer den als "integer overflow ist undefined" rauswirft,
>> sondern um Randfälle gezielt abzufangen.
>
> 1) Wiso verwendest du keine unsigned integer!?! Signed integer sind
> wirklich nur selten nützlich.
> 2) Wiso implementierst du die Prüfung falsch, du willst doch vorher
> wissen, ob es einen geben würde?
> 3) Spätestens bei den Unittests fällt das auf, besonders wenn man mit
> -ftrapv kompiliert.

Ist das mein Code? Nein.
Ist das produktiver Code? Ja.
Wurde der Overflow-Check falsch implementiert? Weißt du nicht.

Kannst du in C einen "signed integer overflow check" effizient 
implementieren? Nein, denn "signed integer" kann per Standard nie 
auftreten. (Du könntest auf unsigned casten, aber da der Standard auch 
nicht sagt, wie signed integer auf Bitebene aussehen, ist das auch 
wieder undefiniert.)

Nop schrieb:
>> Das ist löblich. Als Grundvoraussetzung für C ist das aber Unfug.
>
> Bei Leuten, die ernsthaft in dem Bereich tätig sind, erwarte ich,
> daß sie jedenfalls die häufigsten undefined-Stellen kennen. Viele
> sind es nicht.

Es gibt erstaunlich wenig Ingeneure, die sinnvoll programmieren 
können...

>> Ein nicht aufgefangener Cache Miss ist teurer.
>
> Aha. Undefined behahviour im Hinblick auf Pointer ALiasing adressiert
> aber gerade die Cache Misses. Genau wie ich sagte, das dient der
> Performance.

Lies du bitte auch mal das oben angegebene Paper.

>> Dafür riskierst du halt Sicherheitslücken und Bugs.
> Dagegen gibt's Tools für die Debugphase.

Ich weiß, du wiederholst das ja ständig, als ob das die universelle 
Lösung für alles wäre.

> Wieso willst Du die eigentlich nicht benutzen?

Habe ich gesagt, dass ich das nicht tue?

Es gibt einen Unterschied zwischen "ich find sowas scheiße" und "ich 
versuche, so weit wie möglich damit klarzukommen".

> Erstens ist unsigned overflow sehr wohl definiert. Zweitens heißen die
> beiden Zauberworte hier wiederum "debug-build" und "assert".

Also dürfen Fehler durch ungültige Eingaben nur in der Debugphase 
erkannt werden. Gut zu wissen. :-)

>> Mach das mal auf Systemen, wo du nur einen C-Compiler hast.
>
> Die sind auch nichts für Anfänger.

Und jeder, der kein Anfänger ist, muss alle Seltsamkeiten von C 
beherrschen? Ziemlich arrogant, wenn du mich fragst.

>> Selbst, wenn du einen GCC hast, heißt das noch lange nicht, dass du
>> damit auch C++ oder Ada spielen kannst.
>
> Warum nicht? Gibt Leute, die C++ auf Mikrocontrollern verwenden.

Das setzt voraus, dass die Runtime (Startup-Code, Linkerscript etc.) das 
kann. Es gibt erstaunlich viel Vendor-Code, der das nicht ordentlich 
implementiert und dann funktioniert es, bis es laut knallt.

> Mit einer Systemprogrammiersprache wird man IMMER einigen Unsinn machen
> können. Allein schon wegen der Pointer und deren Casting.

Es gibt auch einen Unterschied zwischen "Unsinn machen können" und 
"versehentlich Unsinn machen, obwohl es nicht nach Unsinn aussieht".

Das ist ja das Hauptproblem mit z.B. Perl, dass viele Konstrukte nicht 
exakt das tun, was man erwarten würde, wenn man die Sprache nicht 
vollständig kann. Sowas kann man im Design vermeiden.

>> Es gibt ja fehlervermeidende APIs, die taugen.
>
> Was haben APIs denn jetzt damit zu tun?

Weil solche Standard-APIs wie gets() oder scanf() einfach stinken.
Weil globale Zustände wie "errno" einfach stinken.

Und jetzt komm mir nicht damit, dass gets() seit C11 nicht mehr im 
Standard steht, das weiß ich auch. Aber welcher Compiler unterstützt 
schon C11 komplett? Selbst C99 wird von MSVC nicht vollständig 
unterstützt.

von Oliver S. (oliverso)


Lesenswert?

S. R. schrieb:
> Weil solche Standard-APIs wie gets() oder scanf() einfach stinken.
> Weil globale Zustände wie "errno" einfach stinken.

Wenn ich gezwungen wäre, mit der Einstellung jeden Tag in C zu 
programmieren, wäre es bei mir Zeit für einen neuen Job. Auch, wenn du 
recht hast.

Oliver

von Daniel A. (daniel-a)


Lesenswert?

S. R. schrieb:
> Es gibt ein Paper von der TU Wien: "What every compiler compiler writer
> should know about programmers"
> 
(http://www.complang.tuwien.ac.at/kps2015/proceedings/KPS_2015_submission_29.pdf).
> Lies das mal.

Ich werde erstmal den Artikel kommentieren, den rest sehe ich mir später 
an.

Wow, der verlinkte Typ kann Wörter verdrehen, mal sehen:

> C*  A language (or family of languages) where language constructs
>     correspond directly to things that the hardware does.
>     E.g., * corresponds to what a hardware multiply instruction does.
>     In terms of the C standard, conforming programs are written in C*.

Ok, er führt eine neue Unterscheidung ein, aber er behautet auch, dass 
Hardwarekonstrukte direkt zu den Eigenschaften der Hardwareinstruktionen 
korrespondieren. Das steht so aber nirgends im Standard(1), im 
Gegenteil, es steht:

> The semantic descriptions in this Standard describe the behavior of an
> abstract machine in which issues of optimization are irrelevant.
...
> In the abstract machine, all expressions are evaluated as specified by
> the semantics. An actual implementation need not evaluate part of an
> expression if it can deduce that its value is not used and that no
> needed side effects are produced (including any caused by calling a
> function or accessing a volatile object).

In anderen Worten, die Operationen haben nichts mit der Endhardware zu 
tun, sondern deren Verhalten wird im Rahmen einer Hypothetischen 
Maschine beschrieben, und welche Operationen tatsächlich erfolgen, um 
das Verhalten zu erreichen.

Der Denkfehler des Author ist, das er im gesamten Dokument nicht 
zwischen Implementation defined und undefined behaviour, btw. 
argumentiert dass eine Implementation etwas, dass Implementation defined 
ist, auch als undefined spezifizieren kann. Mir sind beim GCC sind keine 
Implementation defined fälle bekannt, die von diesem nicht definiert 
werden, aber zu definieren sinnvoll wären, wobei ich meinen Code dennoch 
nicht auf derartiges stütze. Als Beispiel Signed integer overflow ist 
undefined, aus dem Standard:
> As with any other arithmetic overflow, if the result does not fit in the
> space provided, the behavior is undefined.
und
> The integral promotions are performed on each of the operands. The type
> of the result is that of the promoted left operand. If the value of the
> right operand is negative or is greater than or equal to the width in
> bits of the promoted left operand, the behavior is undefined.
Also definiert GCC das nicht. Aber viele Implementation defined 
Binäroperationen werden von GCC definiert, wie z.B. (2):
> Bitwise operators act on the representation of the value including both > the 
sign and value bits, where the sign bit is considered immediately
> above the highest-value value bit. Signed ‘>>’ acts on negative numbers
> by sign extension.

Der Autor des Artikels stellt sich geschickt an, anderen glauben zu 
machen, das dass nicht genau im sinne der Schreiber des Standard gewesen 
währe, wenn undefined behaviour nicht definiert wird, und schreibt:

> Original idea was that C compilers implement C*, and that undefined
> behaviour gives them wiggle room to choose an efficient hardware
> instruction; e.g., for << use the hardware’s shift instruction, and
> differences between different architectures for some parameters result
> in not defining the behaviour in these cases.

Er stützt sich hierbei auf seine anfangs eingeführte Definition von dem 
was er "C*" nennt. Er betrachtet soetwas vermutlich als "conforming 
programs". Des halb schreibt er auch:

> The compiler maintainers try to deflect from their responsibility for
> the situation by pointing at the C standard. But the C standard actually
> takes a very different position from what the compiler maintainers want
> to make us believe. In particular, the C99 rationale 22 [C03] states:
>> C code can be non-portable. Although it strove to give program-
>> mers the opportunity to write truly portable programs, the C
>> 89 Committee did not want to force programmers into writing portably,
>> to preclude the use of C as a ”high-level assembler”: the ability to
>> write machine-specific code is one of the strengths of C. It is this
>> principle which largely motivates drawing the distinction between
>> strictly conforming program
>> and conforming program (§4).

Ich habe ja nur einen draft von c99 (3), aber dort konnte ich dieses 
Statement nicht finden. Es steht zwar folgendes:
> A program that is correct in all other aspects, operating on correct
> data, containing unspecified behavior shall be a correct program and
> act in accordance with 5.1.2.3.

Aber es steht nicht, ob ein Programm mit undefined behavior oder 
implementation defined behavior auch ein conforming program sei, und 
unspecified ist im standard sowieso etwas anderes als undefined. Es 
steht eigentlich nur, dass ein strictly conforming program auch ein 
conforming program ist.

1) http://port70.net/~nsz/c/c89/c89-draft.html#2.1.2.3
2) 
https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation
3) http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf

: Bearbeitet durch User
von Daniel A. (daniel-a)


Lesenswert?

S. R. schrieb:
>> Wenn man sich einmal ernsthaft mit der Sprache auseinandersetzt, kann
>> man alles in weniger als einem Jahr lernen.
>
> Wenn es so einfach wäre, warum gibt es dann ständig Bugs und
> Sicherheitsprobleme deswegen?

S. R. schrieb:
>> Es kommt immer auf das wo an. z.B. bei der Manipulation von grossen
>> Datenmengen kann das einen grossen unterschied machen, ob der Kompiler
>> nun weiss, das der inhalt von buffer a nicht geändert wird, wenn in
>> buffer b geschrieben wird.
>
> Strohmann. Wenn ich "const" ranschreibe, weiß der Compiler, dass da nix
> geändert wird. Da braucht der nicht über undefiniertes Verhalten
> inferieren.

Nein, das weiss er, wenn du zusätzlich noch restrict hinschreibst, btw. 
dann darf er davon ausgehen.

Beispiel:
1
#include <stdio.h>
2
void bla( char* a, const char* b ){
3
  while( *a && (*a++ = *--b) );
4
}
5
int main(){
6
  { // same memory, const changes in bla
7
    char a[]="\0Hello World";
8
    bla(a+1,a+sizeof(a)-1);
9
    puts(a+1);
10
  }
11
  { // different memory, const doesn't changes in bla
12
    char a[]="\0Hello World";
13
    const char b[]="\0Hello World";
14
    bla(a+1,b+sizeof(b)-1);
15
    puts(a+1);
16
  }
17
}
Ausgabe:
1
dlroW World
2
dlroW olleH

Wenn ich im Beispiel oben nur die Aufrufvariante mit 2 unterschiedlichen 
buffern zulassen will, muss ich "void bla( char*restrict a, const 
char*restrict b )" schreiben. Const hilft mir da garnichts.

-----------------

OK, Ich geb ja zu C ist nicht perfekt, und dein Standpunkt hat durchaus 
Hand und Fuss, aber es entspricht einfach nicht meinem Verständnis, wie 
man die Sprache am besten Anwenden sollte. Ich werde nicht anfangen 
nicht portablen Code zu schreiben, nur weil man das könnte.

Es gibt sicher viele, die schonmal über eine eigene Programmiersprache 
nachgedacht haben, mit den verschiedensten Ansprüchen und Ideen. Wenn 
man alle Ideen und das Wissen um bisherige Fehler sammeln könnte, mit 
den Pros und Contras, etc. womöglich könnte man dann ein Sprachset 
definieren, dass verschiedene Sprachen zur Verfügung stellt, welche die 
jeweiligen Anforderungen exakt abdecken, und miteinander Harmonieren. 
Dann wäre das Sprachproblem endlich gelöst.

von Nop (Gast)


Lesenswert?

S. R. schrieb:

> Es gibt ein Paper von der TU Wien: "What every compiler compiler writer
> should know about programmers"

Ja, kenne ich schon. Und nein, ich stimme dem nicht zu. Wenn er eine 
"sichere Sprache"(tm) will, soll er welche erschaffen. Hat der gute Herr 
Wirth ja mehrfach versucht, besonders praxisrelevant sind sie nicht 
geworden.

Ich habe noch mit Pascal angefangen, was als Lehrsprache schon gut war. 
Aber für praktische Anwendung insbesondere bei hardwarenaher 
Programmierung völlig ungeeignet. Deswegen gab's dann ja auch z.B. bei 
Turbo Pascal immer mehr Hacks, die die Begrenzungen der Sprache umgehen 
sollten, zugleich aber auch immer mehr eine proprietäre Borland-Insel 
erschufen.

Übrigens, mir ist das seinerzeit schon beim abstract negativ 
aufgefallen: "In recent years C compiler writers have taken the attitude
that tested and working production (i.e., conforming according to the
C standard)"

Auf gut deutsch: "conforming" heißt für ihn, daß es compiliert und 
läuft. Er beherrscht aber nichtmal grundlegende Logik, ein echtes 
Trauerspiel. WENN der Code konform zum C-Standard und inhaltlich korrekt 
ist, DANN läuft es. Der Umkehrschluß, wenn es inhaltlich korrekt ist und 
läuft, muß es ja standardkonform sein, ist ein Logikfehler. Non 
sequitur.

Wenn Leute, die schon Probleme mit grundlegender Logik haben, dann etwas 
über Compiler schreiben, wird es absurd.

Nebenbei, wenn Entwickler so lernen, wie er das macht, mal ein 
Einführungsbuch durchblättern und ansonsten rumprobieren, dann schreiben 
sie schlechten Code. Die Lösung ist aber kein compiler-dumb-down 
äquivalent zu "diese Kaffeetasse kann heiße Flüssigkeit enthalten"!

Bei Anwendern sehe ich es schon so, für die MUSS man seine Programme 
freundlich machen, denn Nutzer sollen keine Experten sein müssen. Dafür 
bezahlen sie die Entwickler ja. Aber wenn Entwickler für ihre 
Entwicklung diese Einstellung haben, sind sie eine Fehlbesetzung.

http://www.rmbconsulting.us/a-c-test-the-0x10-best-questions-for-would-be-embedded-programmers

Sowas ist der IMO richtige Ansatz dafür.

>>>> There are 203 undefined behaviours listed in appendix J of the
>>>> C11 standard (up from 191 in C99).
>
> Das ist nicht gerade "sehr wenig".

Eine Frage der Zählweise, weil so ziemlich dieselben Konstrukte ja 
formal in verschiedenen Aspekten definiert werden müssen. Daß mehrfache 
Seiteneffekte in einem Ausdruck ein Problem werden können, nunja. Keine 
Überraschung.

> Wenn es so einfach wäre, warum gibt es dann ständig Bugs und
> Sicherheitsprobleme deswegen?

Nicht wegen undefined behaviour, sondern weil man wie bei jeder 
tatsächlichen Systemprogrammiersprache das gesamte Ressourcenmanagement 
zu Fuß machen muß. Das bedeutet, man muß auf jeden Fehler (kein 
Speicher, File nicht da, sonstwas) manuell reagieren. Man muß außerdem 
selber tracken, wann man welche Ressource überhaupt hat (user after 
free). Inputs muß man selber validieren, wie z.B. Längenangaben in 
Paketen.

Und man muß soviel Speicher belegen, wie man denn wirklich braucht. Da 
gibt's ja Spezis, die den sizeof-Operator nicht kennen und dann mit 
magic numbers arbeiten. Das zerbricht natürlich bei der kleinsten 
Änderung, und ganz besonders, wenn man von 32bit auf 64bit geht, wo die 
Pointer auf einmal 8 Byte haben und nicht mehr 4. Das sind dann auch die 
Leute, die mit "unsigned int" Pointerarithmetik machen, weil es ja unter 
32bit (meistens) funktioniert. Statt einfach uintptr_t zu nutzen, das 
genau dafür gedacht ist. Oder die Längenparameter als int übergeben 
anstatt als size_t.

Ja, wenn man eine Systemprogrammierprache nutzen will und dann nicht 
versteht, daß die Pointerlänge von der Plattform abhängt UND C dafür 
aber Werkzeuge bereitstellt, mithin Pointer nicht verstanden hat, dann 
ist der Ofen halt aus.

Das ist kein Problem von C, sondern C ist eben keine managed language. 
Das hätte dann nämlich auch wieder andere Nachteile wie z.B. nicht 
deterministische Ausführung, worauf man ja bei malloc schonmal einen 
negativen Vorgeschmack bekommt (deswegen meidet man das ja embedded auch 
tunlichst).

Eine Systemprogrammiersprache ist nunmal vor allem dafür da, das System 
bereitzustellen, was den Applikationen ihr Ressourcenmanagement 
ermöglicht.

Und es soll eben schnell sein. Deswegen hat C bei Array-Zugriffen keine 
Boundary-Checks. Deswegen sind Pointer und Arrays sehr eng verwandt 
(wenn auch nicht identisch). Deswegen kann man auf Datenbereiche mit 
einem anderen Pointertyp zugreifen, beispielsweise um ein schnelles 
memzero() zu schreiben. Allerdings muß man das dann auch richtig tun, 
sonst's gibt's aliasing. Und/oder alignment faults. Aber wer solche 
Routinen schreibt, weiß das auch.

Und deswegen reicht man in C Strukturen anders als in anderen Sprachen 
halt nicht auf dem Stack an Funktionen, sondern übergibt nur den Zeiger.

Wer C ernsthaft vorwirft, für komplexe Anwendungen nicht sonderlich 
geeignet zu sein, weil man alles selber machen muß, der setzt 
schlichtweg die falsche Sprache für sein Problem ein.

> Strohmann. Wenn ich "const" ranschreibe, weiß der Compiler, dass da nix
> geändert wird. Da braucht der nicht über undefiniertes Verhalten
> inferieren.

Dann hast Du das Problem noch nicht genug betrachtet. Mit "const" kommt 
man da jedenfalls nicht weiter. Obwohl man natürlich auch const überall 
verwenden sollte, wo es Sinn ergibt, keine Frage. Allein schon der 
Lesbarkeit wegen.

Es geht halt auch darum, daß bei Strukturen, die nicht const sind, der 
Compiler wissen muß, daß nicht jeder beliebige anderweitige 
Pointer-Speicherzugriff diese Struktur ändern kann, wenn das von den 
Datentypen her gar nicht paßt.

Das ist ganz besonders für numerische Anwendungen ein Thema, und genau 
für diese wird das dann seit C99 auch noch mit restrict weiter 
unterstützt.

> Dass ein Cache Miss mehr Performance kostet als eine nicht gemachte
> Optimierung, die undefiniertes Verhalten gezielt ausnutzt.

Eben, und Pointer Aliasing ist sogar noch schlimmer, weil der Compiler 
einen load/store einbauen muß, statt die Werte im Register zu nutzen 
(schneller als Register geht nicht). Das ist besonders lästig auf 
Architekturen, die viele Register haben, wie z.B. ARM.

> Kannst du in C einen "signed integer overflow check" effizient
> implementieren?

Ja, die Zauberworte sind mal wieder assert und die Debugbuilds. Und 
Testen natürlich. Über den gesamten Eingabebereich. Unittests und so.

> Es gibt erstaunlich wenig Ingeneure, die sinnvoll programmieren
> können...

Rate mal, wieso es MISRA-C gibt. Kurz gesagt programmiert man damit C 
auf sehr pascalhafte Weise.

> Lies du bitte auch mal das oben angegebene Paper.

Ja, kenne ich. Ein typisches Uni-Paper. Mit der Einstellung wurde auch 
Pascal gemacht, und es ist geflopt.

> Ich weiß, du wiederholst das ja ständig, als ob das die universelle
> Lösung für alles wäre.

Sie helfen ja auch sehr viel und entkräften Deine Argumente 
größtenteils.

> Also dürfen Fehler durch ungültige Eingaben nur in der Debugphase
> erkannt werden. Gut zu wissen. :-)

Während der Debugphase (genauer eigentlich: Testphase) testest Du, ob Du 
ungültige Eingaben korrekt abfängst. Aber Teststrategien sind nochmal 
ein anderes Thema.

> Und jeder, der kein Anfänger ist, muss alle Seltsamkeiten von C
> beherrschen? Ziemlich arrogant, wenn du mich fragst.

Wer mit einer Systemprogrammiersprache in einem entsprechenden Umfeld 
programmieren will und sich auch nicht mit dem sehr leistungsfähigen 
Werkzeug befassen mag, sondern eigentlich lieber eine Art Sandbox 
möchte, der ist bei C definitiv verkehrt, ja.

Und nein, ich möchte nicht, daß man mir mein scharfes Küchenmesser 
stumpf schleift, weil es Leute gibt, die eigentlich am liebsten einen 
Löffel hätten, aber aus irgendwelchen Gründen sich lieber über das 
Messer aufregen, statt einfach einen Löffel zu nehmen.

> Das setzt voraus, dass die Runtime (Startup-Code, Linkerscript etc.) das
> kann.

Kann sie. Wenn nicht, ändert man sie eben. Obwohl das natürlich dann 
wieder nichts für Leute ist, die sich nicht mit dem Werkzeug befassen 
wollen. ;-)

> Weil solche Standard-APIs wie gets() oder scanf() einfach stinken.

Ah, ja das stimmt. Aber wie Du weißt, ist das auch in C nicht erst seit 
gestern durch Besseres behoben worden.

> Und jetzt komm mir nicht damit, dass gets() seit C11 nicht mehr im
> Standard steht, das weiß ich auch.

Zuvor konnte man es nicht "mal eben" rauswerfen, weil sonst jede Menge 
standardkonformer, bestehender Produktivcode nicht mehr compiliert 
hätte. Das wiederum hätte verhindert, daß ein neuerer Standard sich 
überhaupt durchsetzen kann. Also gab's fgets() dazu.

> Selbst C99 wird von MSVC nicht vollständig unterstützt.

Daß Microsoft ein Saftladen ist, der lieber ein unübersichtliches 
Ribbon-Interface in seine Programme reinschmiert, anstatt mal seinen 
C-Compiler auf Vordermann zu bringen, kann man ja nun nicht C anlasten.

Zuguterletzt kann man jedenfalls beim GCC per Compiler-Option auch z.B. 
angeben, daß er signed overflow bitteschön brav machen soll (mit 
implementationsabhängigem Resultat logischerweise) und daß Pointer auch 
aliasen dürfen.

Was ich damit mache: Ich schreibe sauberen Code, teste den mit dem 
Sanitiser, korrigiere ggf. Stellen, die ich übersehen habe. Das sind 
meiner Erfahrung nach keine direkten C-Fehler, sondern eigentliche 
algorithmische Bugs, die in der Folge erst über undefined behaviour 
stolpern. Die typischen one-off-bugs beispielsweise. Und dann noch 
Reviews. Ich reviewe meinen Code mehrfach und finde dabei üblicherweise 
mehr Fehler als durch Testen.

Dann wird der Release gebenchmarked - mit allem undefined behaviour und 
mit den genannten Abschaltungen. Wenn ich feststelle, daß die 
Abschaltungen keinen Nachteil bringen, mithin Optimierung unter 
Ausnutzung von solchem undefined behaviour keinen Beitrag zur 
Geschwindigkeit leistet, dann schalte ich es fürs Release tatsächlich 
ab.

Aber für Debug und Test fordere ich GCC ganz gezielt heraus, meinen Code 
zu zerbrechen, wenn er irgend kann.

Wo man übrigens echt aufpassen muß beim GCC, ist das total kranke 
inlining. "inline" wird nur als Hinweis verstanden, GCC macht, was er 
will. Ohne inline inlined er auch, wenn er will. Deswegen gehe ich da 
über _atribute_ und inline, wo ich will, und verbiete es ihm, wo er 
Mist bauen kann. Letzteres vor allem wegen der Stackproblematik, die 
embedded immer ein Thema ist.

von Nop (Gast)


Lesenswert?

Daniel A. schrieb:
> Nein, das weiss er, wenn du zusätzlich noch restrict hinschreibst, btw.
> dann darf er davon ausgehen.

Restrict ist dafür da, um zu sagen, daß Pointer auf dieselben Datentypen 
auf nicht-übereinanderlappende Bereiche Zeigen.

Bei Aliasing geht's darum, daß z.B. ein float-pointer keinen 
Speicherplatz für einen integer verändern kann. Oh und btw, char-Pointer 
dürfen immer aliasen, genau wie void.

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.