Array 02. Nov 2021 18:41
Möchte ein beliebiges Array in C initialisieren mit 10 Feldern und
daraus mittels einer For-Schleife die Summe berechnen. Doch mein Code
liefert nur sehr hohe Zahlen und JEDES MAL ein Minus Zeichen davor...
vlt kann sich den jemand mal anschauen?
1
#include<stdio.h>
2
#include<stdlib.h>
3
4
intmain(){
5
6
inti;
7
intiSumme=0;
8
intiFeld[10];
9
10
11
for(i=0;i<10;i++)
12
{
13
14
iSumme+=iFeld[i];
15
16
}
17
18
printf("\nDie Summe aller Elemente im Array ist: %d",iSumme);
,,, schrieb:> Die Summe aller Elemente im Array ist: 0
Das wäre der Fall, wenn das Array global oder static wäre. Ist es aber
nicht, von daher liegt Undefined Behaviour vor (nicht initialisierte
Variable lesen). Der Compiler darf jedes Ergebnis ausgeben oder auch
gleich das ganze Programm wegoptimieren.
> Das wäre der Fall, wenn das Array global oder static wäre.
Wir haben uns angewöhnt - wenn die Zeile nicht eingerückt ist, handelt
es sich um eine globale Variable. Solltest du auch so machen, damit
jeder sofort sieht, wo das Problem liegt.
Nop schrieb:> Der Compiler darf jedes Ergebnis ausgeben oder auch> gleich das ganze Programm wegoptimieren.
Nun ja, 'uninitialisierte Variablen' ist als solches schon richtig.
Allerdings ist es nicht die Aufgabe eines Conmpilers, irgendwelche
Laufzeit-Ergebnisse auszugeben. Der Compiler erzeugt nur den zur
Berechnung erforderlichen Maschinencode.
Ganz generell halte ich die Masche mit dem Nullen des RAM-Bereiches beim
Kaltstart für eine gefährliche Sache, wenn man sich darauf verläßt.
Weitaus sicherer ist man, wenn man sich an die Regel hält, daß man
Variablen nur dann trauen kann, wenn man sie zuvor beschrieben hat.
W.S.
Noch ein Vorschlag schrieb:> Wir haben uns angewöhnt - wenn die Zeile nicht eingerückt ist, handelt> es sich um eine globale Variable.
Angewohnheiten sind etwas, das die Sprachdefinition nicht berührt. Und
hier braucht es nur eine kleine Umformatierung des Quelltextes, um den
Sinn oder Unsinn von Angewohnheiten besser zu sehen:
1
#include<stdio.h>
2
#include<stdlib.h>
3
4
intmain()
5
{inti;
6
intiSumme=0;
7
intiFeld[10];
8
9
for(i=0;i<10;i++)iSumme+=iFeld[i];
10
printf("\nDie Summe aller Elemente im Array ist: %d",iSumme);
11
}
Mit anderen Worten: alle Variablen liegen auf dem Stack und sind reinweg
lokal. Und ob man nun eine Zeile direkt mit dem allerersten Text-Zeichen
beginnt oder zuvor etwas an Leerzeichen hat, ist eigentlich egal. Soviel
zu Angewohnheiten.
W.S.
W.S. schrieb:> Ganz generell halte ich die Masche mit dem Nullen des RAM-Bereiches beim> Kaltstart für eine gefährliche Sache, wenn man sich darauf verläßt.
Auch die Diskussion hatten wir schon öfter.
Ich halte es für eine sehr sinnvolle Sache, sich auf die zugesicherten
Eigenschaften eines tools zu verlassen. Könnte man das nicht, müsste man
ja um alle mögliche und unmöglichen Eventualitäten herumprogrammieren,
was das ganze völlig sinnlos und unpraktikabel macht.
Sollte das tool, wie im hier besprochen Fall, ein ein C-Compiler sein,
dann ist dafür klar definiert, welche "Ram-Bereiche" genullt werden, und
welche nicht. Wenn der Compiler kein C-Compiler ist, dann gilt da
natürlich nicht.
Oliver
Oliver S. schrieb:> Ich halte es für eine sehr sinnvolle Sache, sich auf die zugesicherten> Eigenschaften eines tools zu verlassen. Könnte man das nicht, müsste man> ja um alle mögliche und unmöglichen Eventualitäten herumprogrammieren,...
Nö, man müßte sich nur angewöhnen, sich nicht darauf zu verlassen, daß
uninitialisierte Varablen immer auf Null gesetzt sind. Das ist keine
unbillige Forderung.
Was daraus wird, wenn jemand sich versehentlich auch einmal auf diese
Nullung bei lokalen Variablen auf dem Stack verläßt, sehen wir an diesem
Thread.
W.S.
W.S. schrieb:> Nö, man müßte sich nur angewöhnen, sich nicht darauf zu verlassen, daß> uninitialisierte Varablen immer auf Null gesetzt sind. Das ist keine> unbillige Forderung.
Es nicht nur eine unbillige, sondern eine sehr sinnvolle Forderung, daß
man die Programmiersprache kennt, in der man programmiert. Alles andere
ist Aluhutprogrammierung. Kann man machen, ist dann aber
Privatvergnügen.
Oliver
W.S. schrieb:> wenn jemand sich versehentlich auch einmal auf diese Nullung bei lokalen> Variablen auf dem Stack verläßt
Eine solche gibt es aber laut C-Standard nicht. Man sollte schon die
Eigenschaften derjenigen Sprache kennen, die man einsetzt. Und/oder man
setzt Tools zur Codeanalyse ein, die solche Fehler erkennen können.
W.S. schrieb:> Oliver S. schrieb:>> Ich halte es für eine sehr sinnvolle Sache, sich auf die zugesicherten>> Eigenschaften eines tools zu verlassen. Könnte man das nicht, müsste man>> ja um alle mögliche und unmöglichen Eventualitäten herumprogrammieren,...>> Nö, man müßte sich nur angewöhnen, sich nicht darauf zu verlassen, daß> uninitialisierte Varablen immer auf Null gesetzt sind.
Man sollte sich auch nicht darauf verlassen, dass Zuweisungen immer
ausgeführt werden. Deswegen sollte man statt
1
a=b+1;
schreiben:
1
a=b+1;
2
a=b+1;
Für sicherheitskritische Anwendungen reicht das aber noch nicht. Dort
schreibt man (bis SIL 2)
1
a=b+1;
2
a=b+1;
3
a=b+1;
und ab SIL 3
1
a=b+1;
2
a=b+1;
3
for(i=0,i=0;i<1000;i1=i+1,i1=i+1,i=i1,i=i1){
4
a=b+1;
5
a=b+1;
6
}
7
a=b+1;
8
a=b+1;
Nur so kann man halbwegs sicher sein, dass das Ergebnis auch wirklich in
der Variable a ankommt.
;-)
Mark B. schrieb:> Eine solche gibt es aber laut C-Standard nicht.
Das ist bekannt (auch mir!) und dennoch wurde es hier vergessen (jedoch
NICHT von mir).
Nun, irgend etwas mal zu vergessen, ist menschlich. Also sollte man die
Anzahl der zu erinnernden Regeln nicht unnötig hoch schrauben, sondern
möglichst durch einfachere und wirkungsvollere Regeln ersetzen, die
ihrerseits einen größeren Gültigkeitskreis besitzen. Eben dieses habe
ich hier als bessere weil allgemeingültigere Regel vorgeschlagen.
Und wenn man diese Regel kennt und berücksichtigt, dann werden sowas wie
generelle Nullung vor dem Programmstart schlichtweg überflüssig.
Das hat keineswegs mit Aluhut (=Scharlatanerie) zu tun, sondern nur mit
sauberer Programmierung.
W.S.
W.S. schrieb:> Der Compiler erzeugt nur den zur> Berechnung erforderlichen Maschinencode.
Das mag bei Pascal stimmen, bei C liegst Du damit mal wieder daneben. C
hat die as-if-Regel, unter der ein Compiler Code erzeugt, und die ist
mit dafür verantwortlich, daß C-Code so performant ist.
> Ganz generell halte ich die Masche mit dem Nullen des RAM-Bereiches beim> Kaltstart für eine gefährliche Sache, wenn man sich darauf verläßt.
Quatsch. Das ist nicht nur eine im C-Standard zugesicherte Eigenschaft,
sondern der Compiler wird eine Null-Initialisierung bei globalen
Variablen sowieso wegoptimieren - und sich nämlich darauf verlassen, daß
der Startupcode eine zum C-Standard konforme Umgebung herstellt. Womit
man wieder bei der Ausgangsbasis ist und sich auf den Startupcode
verläßt.
W.S. schrieb:> sondern> möglichst durch einfachere und wirkungsvollere Regeln ersetzen
Also wenn man ein wenig Assembler kann, und gelegentlich vielleicht auch
mal den Wunsch verspürt, so Leute wie den Gauß zu fragen, was er von
C-Programmierung hält, bzw. mit anfangen kann, dann kann man bei einigen
Code-Postings hier, und den darunter folgenden Kommentaren schon ein
wenig schmunzeln..
W.S. schrieb:> Yalu X. schrieb:>> Man sollte sich auch nicht darauf verlassen, dass Zuweisungen immer>> ausgeführt werden.>> Was hast du denn kurz zuvor geraucht?
Vermutlich das gleiche Zeug wie du :D
Nein, ich habe mich von dir davon überzeugen lassen, einem Compiler
(bzw. einer Tool-Chain) nie zu trauen und werden künftig konsequent
danach handeln :)
Nein, ich glaube es wäre nicht sehr zielführend, von dir oder vom
c-hater irgendwelche Ratschläge zum Thema C-Programmierung anzunehmen.
Aber ich nehme deinen Tip gerne an für die Firma.
Freue mich schon aufs nächste Review!
Wie macht man das eigentlich dann mit Vergleichen, wenn man die mehrfach
testet? Solange testen, bis drei aufeinanderfolgende gleiches Ergebnis
liefern? Auch, wenn darin Funktionen mit Nebeneffekten auftauchen?
Mark B. schrieb:> Ach, das ist einfach.
Für höhere Sicherheitsanforderungen sollte da aber noch ein Voting rein,
damit sogar W.S. sicher sein kann, daß seine Compiler funktionieren:
Mark B. schrieb:> Klaus W. schrieb:>> Wie macht man das eigentlich dann mit Vergleichen, wenn man die mehrfach>> testet?>> Ach, das ist einfach. Man schreibt:> 1if ( (a==b) && (b==a) )> 2{> 3 // Now a and b are truly equal> 4}>> So kann man ganz sicher sein, dass der Vergleich korrekt ist.>> ;-)
Da fehlt aber noch was
1
2
if((a==a)&&(a==b)&&(b==a)&&(b==b))
Erst jetzt kannst du wirklich sicher sein ;)
Oliver
Sehr interessanter Thread!
Erinnert mich an eine meiner Abschlussprüfungen in Deutsch, bei der man
aus einem 10 Zeilen Gedicht 20 Seiten Interpretation machen musste!
Warum konnte man in diesem Thread nicht sachlich bleiben und den TO
einfach nur darauf hinweisen, dass lokale Variablen, im Gegensatz zu
globalen Variablen, nicht selbstständig genullt werden?
Mark B. schrieb:> Klaus W. schrieb:>>> Wie macht man das eigentlich dann mit Vergleichen, wenn man die mehrfach>> testet?>> Ach, das ist einfach. Man schreibt:> 1if ( (a==b) && (b==a) )> 2{> 3 // Now a and b are truly equal> 4}>> So kann man ganz sicher sein, dass der Vergleich korrekt ist.> ;-)
Ist:
1
if((a==b)&&(b==a))
2
…
atomar?
Wenn nicht, dann kann sich a oder b beim zweiten Teil der Prüfung
verändert haben…
Yalu X. schrieb:> und ab SIL 3> a = b + 1;> a = b + 1;> for (i=0, i=0; i<1000; i1=i+1, i1=i+1, i=i1, i=i1) {> a = b + 1;> a = b + 1;> }> a = b + 1;> a = b + 1;>> Nur so kann man halbwegs sicher sein, dass das Ergebnis auch wirklich in> der Variable a ankommt.
Ab SIL4 wird dann auch beachtet, dass nicht nur fraglich ist, ob die
Zuweisung stattfindet, sondern dass sie fehlerhaft sein kann, wenn sie
stattfindet. Stell dir vor, die voletzte (angweiesene) Zuweisung hat nen
Bitfehler und die letzte (angewiesene) Zuweisung findet nicht statt...
W.S. schrieb:> Ganz generell halte ich die Masche mit dem Nullen des RAM-Bereiches beim> Kaltstart für eine gefährliche Sache, wenn man sich darauf verläßt.
Offenbar bist Du traumatisiert von irgendeinem uralten C-Compiler aus
dem letzten Jahrtausend (ich schätze mal 80er), der nur so mit Bugs
gespickt war.
Vermutlich wars irgendein DOS-Programm, programmiert von Leuten, welche
von C keine Ahnung hatten. Wie wir aus einem anderen Thread von Dir
gelernt haben, hielt sich dieser olle Compiler noch nichtmals an die
gebotene Reihenfolge bei AND- oder OR-verknüpften Statements, was damit
Short-Circuit Evaluation schlicht unmöglich macht.
Aber ich kann Dich beruhigen:
Schon 1970 haben die ersten C-Compiler bzw. C-Runtime-Libs für die PDP
11/70 unter UNIX dafür gesorgt, dass static- und globale Variablen mit 0
initialisiert waren. Auch damals gab es schon die
Short-Circuit-Evaluation, die Du immer noch nicht glauben magst.
Dein buggy DOS-C-Compiler aus dem letzten Jahrtausend existiert nicht
mehr. Folglich gibt es auch keinen mehr, der globale oder static
Variablen unitialisiert lässt.
Also höre einfach auf, immer wieder denselben Horror-Scheiß für bare
Münze zu verkaufen, nur weil Dich Dein blöder DOS-C-Compiler in der
Vergangenheit stark geschädigt hat. Das hilft hier keinem weiter. Du
verbreitest schlicht Unwahrheiten.
mh schrieb:> Yalu X. schrieb:>> und ab SIL 3>> a = b + 1;>> a = b + 1;>> for (i=0, i=0; i<1000; i1=i+1, i1=i+1, i=i1, i=i1) {>> a = b + 1;>> a = b + 1;>> }>> a = b + 1;>> a = b + 1;>>>> Nur so kann man halbwegs sicher sein, dass das Ergebnis auch wirklich in>> der Variable a ankommt.>> Ab SIL4 wird dann auch beachtet, dass nicht nur fraglich ist, ob die> Zuweisung stattfindet, sondern dass sie fehlerhaft sein kann, wenn sie> stattfindet. Stell dir vor, die voletzte (angweiesene) Zuweisung hat nen> Bitfehler und die letzte (angewiesene) Zuweisung findet nicht statt...
Ihr solltet euch mal Java2k anschauen.
http://p-nand-q.com/programming/languages/java2k/
@Frank M.
Als Moderator ist dein Ton völlig unangemessen.
Ansonsten nennt sich es sich defensive Programmierung, wenn man sich
nicht auf irgendwas verlässt. Schon der Compiler sollte eine Warnung
ausgeben wenn du eine Variable nutzt, und vorher nicht gesetzt hast.
Nicht so toll bei einer Saefty-Programmierung mit SIL Einstufung.
Spätestens eine statische Codeanalyse wird darüber nicht hinwegsehen.
Julius schrieb:> Schon der Compiler sollte eine Warnung> ausgeben wenn du eine Variable nutzt, und vorher nicht gesetzt hast.
Das sollte er bei globalen und static-Variablen keineswegs tun, denn die
sind immer initialisiert.
> Nicht so toll bei einer Saefty-Programmierung mit SIL Einstufung.
Doch, weil "Safety-Programmierung" nicht soviel bedeutet wie
"Programmieren ohne Kenntnisse der Programmiersprache".
> Spätestens eine statische Codeanalyse wird darüber nicht hinwegsehen.
Doch, wird sie. Also, bei globalen und statischen Variablen.
Julius schrieb:> @Frank M.> Als Moderator ist dein Ton völlig unangemessen.
Moderatoren sind halt auch Forennutzer (und möchten das sein). Es gibt
keinen Knopf, wo wir einstellen können "schreibe das als normaler Nutzer
ohne Moderatoren-Bit".
> Ansonsten nennt sich es sich defensive Programmierung, wenn man sich> nicht auf irgendwas verlässt.
Yalus Beitrag machte auf eine leicht spaßige, aber durchaus einen
ernsten Hintergrund darlegende Weise dar, dass du dich zwangsläufig auf
zugesicherte Eigenschaften verlassen können musst, ansonsten kannst du
die Tools gleich wegwerfen. Genauso, wie du dich eben darauf verlassen
können musst, dass der Compiler die zugesicherte Eigenschaft, dass 1 + 1
= 2 ist, auch sauber umsetzt, musst du dich drauf verlassen können, dass
eine zugesicherte Initialisierung auch erfolgt. Es wurde ebenfalls schon
dargelegt, dass es völlig schnuppe ist, ob du bei statischen oder
globalen Variablen noch explizit eine Initialisierung mit 0 einfügst:
der erzeugte Code ist der gleiche, denn auch bei der expliziten
Initialisierung verlässt sich der Compiler an dieser Stelle darauf, dass
der Startup-Code das auf die Reihe bekommt. Genauso, wie er sich
natürlich drauf verlässt, dass eine Initialisierung mit etwas anderem
als 0 auf irgendeine Weise die Initialwerte in die Variablen
transportiert.
Funktionieren diese Mechanismen mal nicht (was man im Embedded-Bereich
dadurch verhunzen kann, dass Startup-Code und Linkerscript nicht
zueinander passen, denn die beiden sind aufeinander angewiesen), dann
ist man so oder so der Gelackmeierte.
> Schon der Compiler sollte eine Warnung> ausgeben wenn du eine Variable nutzt, und vorher nicht gesetzt hast.
Macht er ja auch, an den Stellen, wo die Variable nicht ohnehin
implizit initialisiert ist (also bei einer automatischen Variablen).
> Nicht so toll bei einer Saefty-Programmierung mit SIL Einstufung.> Spätestens eine statische Codeanalyse wird darüber nicht hinwegsehen.
Selbst MISRA ist nicht so doof, und verlangt die explizite
Initialisierung ausdrücklich nur für automatische Variablen. Genauer
gesagt: es verlangt, dass sie nicht verwendet wird, bevor sie gesetzt
worden ist.
Eine "ich initialisiere immer alle Variablen in der
Definition"-Mentalität kann sich an dieser Stelle übrigens sogar
kontraproduktiv auswirken: Nimm ein Stück Code, bei dessen Erstellung
der Codefluss sichergestellt hat, dass eine bestimmte Variablen in jedem
Falle einen Wert gesetzt bekommen hat (bspw. durch passende
if/else-Konstrukte), bevor sie verwendet wird. Jetzt fügt jemand einen
weiteren Zweig in den if/else-Konstrukt hinzu und vergisst, dass er dort
dieser Variablen ebenfalls einen dedizierten Wert zuweisen müsste. Hast
du an der Variablen keine Initialisierung gehabt, gibt es jetzt in der
Codeanalyse eine Warnung. Hast du stur dagegen alle Variablen per "ich
initialisiere immer alles mit 0" zugenagelt, gibt es keine Warnung, auch
keine von einer anderweitigen statischen Codeanalyse, und du debuggst
erstmal eine ganze Weile zur Laufzeit herum, warum alles nicht so
funktioniert wie gedacht.
Julius schrieb:> Ansonsten nennt sich es sich defensive Programmierung, wenn man sich> nicht auf irgendwas verlässt.
Ich bin ebenso ein Anhänger von defensiver Programmierung, aber ich
verstehe darunter etwas ganz anderes.
Wenn man so programmiert, dass man dem Compiler nicht traut, nenne ich
das nicht defensive Programmierung, sondern paranoide Programmierung.
Mit paranoider Programmierung läuft man übrigens Gefahr, eigene
Programmierfehler zu überdecken. Man schiebt dann zum Beispiel ein
Phänomen, dass nur bei eingeschalteter Optimierung des Compilers zutage
tritt, auf den Compiler.
In 99% aller Fälle ist dem aber nicht so, tatsächlich ist es ein
Programmierfehler. Ein paranoider Programmierer fängt dann an, unsinnige
Vorsichtsmaßnahmen zu ergreifen, bis der Fehler dann nicht mehr
auftritt. Tatsächlich hat er aber damit nur die Symptome bekämpft und
nicht den Fehler tatsächlich ausgemerzt.
Hier ein schönes Beispiel:
Unter https://mikrocontroller.bplaced.net/wordpress/?page_id=142 findet
man die Formulierung:
"Ich habe Grundsätzlich die Compiler Optimierung AUS = “none”
und benutze auch nicht die Hardware-FPU"
Der Autor Uwe B. begündet das mit den Delay-Schleifen, die dann nicht
mehr funktionieren. Tatsächlich habe ich festgestellt, dass bei
eingeschalteter Optimierung ein Großteil seiner STM32-Library gar nicht
mehr läuft, weil Uwe das Schlüsselwort "volatile" nicht kennt bzw.
ungenügend einsetzt. So funktionieren dann interrupt-basierende Module
seines Codes bei eingeschalteter Optmierung überhaupt nicht mehr - mal
ganz abgesehen von den völlig unzulänglichen Delay-Schleifen.
Ja, man könnte es "defensive Programmierung" nennen, wenn man seinen
Code nur ohne Optimizer zum Laufen bekommt. Ich halte diese Bezeichnung
jedoch für unangebracht. Es ist für mich ein klarer Programmierfehler.
P.S.
Meine Definition von defensiver Programmierung: Die API meiner Module
soweit absichern, dass sie auch mit den unsinnigsten Parametern
zurechtkommt - auch wenn es nur die Ausgabe eines Fehlers ist.
Fazit:
1
Defensive Programmierung = Verlässlichkeit.
2
Paranoide Programmierung = Misstrauen gespickt mit Unsicherheit.
Hier noch ein klassisches Beispiel für paranoide Programmerung:
1
charbuffer[16];
2
intglobal_variable;
3
4
intmain()
5
{
6
...
7
strcpy(buffer,"Das ist ein gaaaanz langer Satz\n");
8
...
9
if(global_variable==0)
10
{
11
init_foo();
12
}
13
else
14
{
15
foo();
16
}
17
...
18
}
Beim Testen stellt der Programmierer fest: "Huch, meine globale Variable
ist ja gar nicht 0, denn init_foo() wird ja gar nicht ausgeführt!".
Er schiebt das auf den Compiler und meint, er müsse den Fehler mit einer
expliziten Initialisierung von global_variable usw. at Runtime
korrigieren.
Er schreibt also folgendes:
1
charbuffer[16];
2
intglobal_variable;
3
4
staticvoid
5
init_variables(void)
6
{
7
strcpy(buffer,"Das ist ein gaaaanz langer Satz\n");
8
global_variable=0;
9
}
10
11
intmain()
12
{
13
init_variables();
14
...
15
if(global_variable==0)
16
{
17
init_foo();
18
}
19
else
20
{
21
foo();
22
}
23
....
24
}
Tatsächlich hatte er in der ersten Version seines Programms einen
klassischen Buffer-Overflow produziert - mit dem Nebeneffekt, dass er
sich die Variable global_variable mit einem Wert ungleich 0
überschrieben hat.
Mit der zweiten Version hat er seinen Buffer-Overflow zwar nicht
ausgemerzt, er hat aber das Phänomen, dass die globale Variable nicht 0
war, überdeckt. init_foo() wird nun aufgerufen und der Programmierer
ist jetzt zufrieden.
Zu Unrecht, denn der Fehler ist nachwievor da! Und vielleicht merkt er
viel später, dass der Inhalt seines Buffers durch seine paranoide
Programmierung nun trunkiert wurde - vielleicht.
P.S.
strcpy() habe ich gewählt, um den Fehler im Beispiel verständlich zu
machen. Tatsächlich sind Buffer-Overflows in der Praxis jedoch
wesentlich subtiler.
Julius schrieb:> Als Moderator ist dein Ton völlig unangemessen.
ich weiss nicht wie lange du hier schon mitliest, aber W.S. ist jemand
der selber nur austeilt, eigene Fehler aber never ever zugibt.
Und er predigt ständig gefälligst das Datenblatt zu lesen, warum schaut
er dann nicht mal in den C Standard? Anfänger werden mit seinen Tipps
der 'Bauchgefühlprogrammierung' nur verdorben. Ich habe hier im Forum
auch schon eine Menge dazugelernt, der Stil von dem ewig gestrigen ist
aber nur abschreckend.
Julius schrieb:> @Frank M.> Als Moderator ist dein Ton völlig unangemessen.
Ich empfinde Franks Beitrag überhaupt nicht als unangemessen. Dass der
letzte Absatz etwas strenger formuliert ist, ist IMHO vor folgendem
Hintergrunds (den du vielleicht nicht kennst) durchaus akzeptabel:
W.S. hat in der Vergangenheit schlechte Erfahrungen mit einem C-Compiler
(dem Sozobon C für den Atari ST, also vor ca. 30 Jahren) gemacht, weil
dieser wohl massivste Bugs enthielt. Diese Bugs konnten mit ein paar
Work-Arounds umgangen werden, dazu zählen:
- die explizite Initialisierung sämtlicher statischer Variablen, selbst
wenn der Wert 0 ist
- Verzicht auf "Kurzschlussauswertung" in logischen Ausdrücken,
stattdessen Aufteilung der Ausdrücke in mehrere if-Konstrukte
W.S. behauptet nun seit Jahren immer und immer wieder, dass solche
Work-Arounds auch bei allen neueren C-Compilern (die diese Bugs nicht
haben, aber rein theoretisch haben könnten) zwingend anzuwenden seien.
Das ist natürlich Unsinn, führt aber durch die ständige Wiederholung bei
Programmieranfängern, die den Unsinn darin noch nicht erkennen, unnötig
zu Verunsicherungen.
Auf diesen Unsinn wurde W.S. schon vielfach hingewiesen, nicht nur von
den Moderatoren, sondern auch von vielen anderen Forenteilnehmern,
leider ohne Erfolg. Da ist es nicht verwunderlich, dass manch einer
(egal ob Moderator oder nicht) inzwischen zu etwas deutlicheren Worten
greift.
Frank M. schrieb:> Der Fehler ist nachwievor da! Und vielleicht merkt er viel später, dass> der Inhalt seines Buffers trunkiert wurde - vielleicht
Das wird er wahrscheinlich schon feststellen, vermutet dann aber, dass
der Compiler längere Strings nicht verarbeiten kann und baut dafür dann
auch noch einen entsprechenden "work-around" in den Code ein.
Julius schrieb:
> Als Moderator ist dein Ton völlig unangemessen.
Und das Frank hier als Programmierer schon einiges bewiesen hat siehst
du an den Artikeln zu IRMP, Minos oder Steccy. Projeke, an denen W.S.
übrigens auch massig rumgemäkelt hat.
Yalu X. schrieb:> W.S. hat in der Vergangenheit schlechte Erfahrungen mit einem C-Compiler> (dem Sozobon C für den Atari ST, also vor ca. 30 Jahren) gemacht
das ganze Tramiel OS war eine einzige Bug Sammlung, insbesondere das C
SDK. Zu der Zeit hatte ich in einem Computershop gearbeitet und es gab
tatsächlich einen Kunden der das da für viele DM gekauft hatte. Und es
dann kurze Zeit später erbost zurückgegeben hatte weil unbenutzbar.
Rolf M. schrieb:> Das wird er wahrscheinlich schon feststellen, vermutet dann aber, dass> der Compiler längere Strings nicht verarbeiten kann und baut dafür dann> auch noch einen entsprechenden "work-around" in den Code ein.
Du hast den Nagel auf den Kopf getroffen! So kommt man von einem
Fettnapf zum nächsten...
Man kann ja durchaus mal aus dem Nähkästchen plaudern, aber diese
Work-Arounds altväterlich als Ratschläge zu verkaufen, die das "einzig
Wahre" sind, geht mir mittlerweile gehörig auf die Hutschnur.
Johannes S. schrieb:> Yalu X. schrieb:>> W.S. hat in der Vergangenheit schlechte Erfahrungen mit einem C-Compiler>> (dem Sozobon C für den Atari ST, also vor ca. 30 Jahren) gemacht>> das ganze Tramiel OS war eine einzige Bug Sammlung, insbesondere das C> SDK.
Zum Einen war damals "die andere Seite" auf der MSODS-Welt nicht viel
solider, man darf das nicht mit dem heutigen Stand vergleichen, was
Programmierumgebungen angeht.
Zum anderen gab es auch damals bessere und schlechtere Systeme.
Ich hatte auf Ataris weitgehend mit Compilern von Prospero zu tun
(Fortran, C, Pascal). Und die waren sehr sauber gemacht.
Langweilig und bieder, aber zuverlässig.
Später auch mal PureC, auch das war brauchbar.
Wenn man natürlich damals schon die schlechten gesucht hat, konnte man
die leicht finden.
(Daß TOS auch beim Kunden reifen durfte, ist natürlich wahr.
Aber ehrlich gesagt ist die MS-Welt heute noch in der einen Hälfte nicht
reif geworden, und gleichzeitig in der anderen schon lange vermodert.)
Johannes S. schrieb:> Und das Frank hier als Programmierer schon einiges bewiesen hat siehst> du an den Artikeln zu IRMP, Minos oder Steccy. Projeke, an denen W.S.> übrigens auch massig rumgemäkelt hat.
Man muss gerechterweise hier bemerken, dass W.S. sich eher über
MINOS echauffiert hat. STECCY fand er soweit ganz okay, er hat
sich sogar am STECCY-Thread konstruktiv beteiligt.
Das witzige ist, dass ich in STECCY viele Module aus dem von ihm
stark beschimpften MINOS verwende - bis auf den NIC-Compiler und
das NIC-Runtime-System eigentlich alles. Stattdessen wurde lediglich die
Z80- und ZX-Spectrum-Hardware-Emulation von mir eingefügt. Hat er aber
gar nicht gemerkt ;-)
Sorry, muss gerade laut lachen.
Johannes S. schrieb:> das ganze Tramiel OS war eine einzige Bug Sammlung
und das bessere KAOS wollte Tramiel nicht!
Von TOS 1.0 auf 1.4 war auch nur ein schlapper würgaround
Johannes S. schrieb:> insbesondere das C> SDK.
keine Ahnung welches das war, vielleicht gab es bessere, aber am Atari
hatte ich daheim in C weiter an meine Prüfprogramme gefeilt die ich dann
im C-Source auf Disk zum PC @work getragen hatte. Funktionierte immer
gut, ausser das ich am Atari schon 2,5MB hatte und der PC regelmäßig
auch mit EMM/QEMM 386 unter Speichermangel litt. Glücklicherweise hatten
wir einen SoftwareWerkstudenten der die dicksten Routinen in ASM
einstampfte.
Nop schrieb:> Das mag bei Pascal stimmen, bei C liegst Du damit mal wieder daneben.
Ach, dein Compiler übernimmt die Funktion der von dir geschriebenen
Firmware??
Natürlich nicht. Der Compiler ist zuständig dafür, daß die Quelle
korrekt in Maschinencode übersetzt wird. Die Berechnungen, die die
erzeugte Firmware zur Laufzeit macht und die Ausgabe der Resultate ist
Sache der von dir geschriebenen Firmware und nicht des Compilers.
Ist es derart schwer, dieses zu begreifen?
W.S.
Yalu X. schrieb:> - Verzicht auf "Kurzschlussauswertung" in logischen Ausdrücken,> stattdessen Aufteilung der Ausdrücke in mehrere if-Konstrukte
Du scheinst da etwas zu verwechseln. Die Abarbeitung von Ausdrücken
strikt von links nach rechts ist etwas komplett anderes, als der Abbruch
einer Boolean-Rechnung sobald das Ergenbis feststeht. Gerade sich auf
das strikte Abarbeiten in einer bestimmten Richtung zu verlassen, ist
bei Funktionen wie dem besagten 'match' eine gefährliche Sache, weil
derartige Funktionen nicht nur ein Ergebnis liefern, sondern auch ihre
Argumente verändern. Da kommt es wirklich drauf an, in welcher
Reihenfolge die Aufrufe erfolgen. Und im dortigen Beispiel war es
lediglich eine Zusammenfassung, die absolut unnötig war. So etwas sollte
man sich lieber verkneifen. Man weiß nicht mit letzter Sicherheit, was
bei höheren Optimierungsstufen damit passiert.
Genau so ist es mit dem Merken von ganzen Bergen von Regeln. Ich hatte
da mal vor Jahren einen Effekt bei Chan's FAT-FS. Nach Wechseln der
SD-Karte wurde diese nicht mehr erkannt. Grund war ein Bug, der daher
rührte, daß der Autor sich an einer Stelle beim Initialisieren der Karte
darauf verlassen hatte, daß irgend ein Zeiger oder andere Variable Null
wären, was aber nur nach dem Systemstart so war. Ist schon lange her.
Also das Nullen des RAM mag für das Erkennen von nicht initialisierten
Zeigern recht hilfreich sein, generell ist es jedoch besser, sich an die
allgemeine Regel zu halten, daß man Variablen als uninitialisiert
ansieht, solange man sie nicht beschrieben hat. Das ist eine
universellere Regel als das Glauben, daß alles, was man noch nicht
angefaßt hat, auch null zu sein hat. Was gelegentlich dabei herauskommt,
haben wir gesehen, als zwei Leute ein uninitialisiertes Array auf dem
Stack aufaddiert haben und zu völlig verschiedenen Resultaten gekommen
sind.
W.S.
Jörg W. schrieb:> Macht er ja auch, an den Stellen, wo die Variable nicht ohnehin> implizit initialisiert ist (also bei einer automatischen Variablen).
Jörg, du hackst mal wieder auf dem falschen Karnickel herum.
Bedenke mal, daß jeglicher Code in einem C Programm in irgend einer
Funktion steht. Und die muß aufgerufen werden. Und so hat wohl jeglicher
Programmteil seine Initialisierungsfunktion (z.B. UART27_Init(..) oder
so ähnlich) und NIEMAND garantiert, daß eben so eine
Initialisierungsfunktion nur ein einziges Mal nach dem Systemstart
aufgerufen wird. Sowas ist bei Pascal anders, da hat jeder Unit die
Möglichkeit, vor dem eigentlichen Programmstart irgend etwas zu tun,
also etwas einmaliges quasi beim Kaltstart. Aber wir sind hier ja bei C
und da gibt es sowas nicht.
Das alles ist auch keine Angelegenheit des Compilers und solche
Kinder-Programme wie hier, die lediglich aus main() bestehen, sind in
der Praxis gelinde gesagt unüblich. Also darf man beim Schreiben
beispielsweise eines seriellen Treibers nicht darauf bauen, daß er nur
ein einziges Mal nach Systemstart initialisiert wird. Und schon beim 2.
Aufruf der Init-Funktion ist die ganze Diskussion um Nullung am
Systemstart für die Katz. Da brauchen wir nicht mehr drauf
herumzuhacken.
W.S.
W.S. schrieb:> Du scheinst da etwas zu verwechseln. Die Abarbeitung von Ausdrücken> strikt von links nach rechts ist etwas komplett anderes, als der Abbruch> einer Boolean-Rechnung sobald das Ergenbis feststeht.
Das letztere ist aber nur in Verbindung mit dem ersteren sinnvoll,
weswegen der C-Standard für die logischen Operatoren && und || beides
(definierte Reihenfolge und Abbruch, sobald das Ergebnis feststeht)
vorschreibt.
> Was gelegentlich dabei herauskommt, haben wir gesehen, als zwei Leute> ein uninitialisiertes Array auf dem Stack aufaddiert haben und zu> völlig verschiedenen Resultaten gekommen sind.
Niemand hier schlägt vor, Variablen uninitialisiert zu verwenden. Es
wurde lediglich davon abgeraten, Variablen doppelt zu initialisieren.
W.S. schrieb:> Ist es derart schwer, dieses zu begreifen?
Du hättest es begreifen können, wenn Du den Rest des zitierten Postings
sinnerfassend gelesen hättest - Stichwort as-if-Regel. Die Du natürlich
weder kennst noch verstehst, weil Du kein C kannst.
nur um das ganze formal zu ergänzen, die Summenformel wäre eigentlich in
etwa:
int n, summe;
summe = (n * (n+1))/ 2;
..wobei das durch 2 eigentlich keine FPU oder extra Klammer benötigt* ;)
Der Hintergrund ist:
1 2 3
3 2 1
4 + 4 + 4
usw.
Das nennt sich u.a. "der kleine Gauß":
https://de.wikipedia.org/wiki/Gau%C3%9Fsche_Summenformel
Und bei Asm könnte man alles auf ein Register addieren, z.B. (so grob)
add ax, bx
inc bx
loop
wobei bei der Variante mit loop beim PC noch ein Register als Zähler
braucht, hier automagisch cx, ansonsten Vorzugsweise auch cx.
oder eben alles wichtige per Hexeditor o.ä. je nach Wunschwiederholung
kopieren, das nennt man dann z.B.
https://de.wikipedia.org/wiki/Loop_unrolling
*jetzt ist aber noch immer die Formatfrage interessant.
Es werden ja traditionell beim Multiplizieren die Register ax und dx
ergänzt. Bei 64 Bit kommt man so auf 128 Bit. Das ist schon ganz
ordentlich. Für noch größere Werte kann man z.B. das Carry-Flag bemühen.
Spannend wäre diesbezüglich auch die Frage, wie geht der Shift (nach
Rechts) über mehrere Register?
W.S. schrieb:> Da kommt es wirklich drauf an, in welcher Reihenfolge die Aufrufe> erfolgen.
Natürlich. Und deshalb ist es garantiert, dass die Evaluation der
Ausdrücke immer von links nach rechts erfolgt.
Bei
1
if(A&&B&&C)
werden die Ausdrücke B und auch C NICHT mehr evaluiert, wenn bereits A
false ist.
Etwaige Funktionsaufrufe, welche in B und C enthalten sind, werden dann
auch NICHT ausgeführt. Das ist garantiert in C.
Nur so ist der tausendfach verwendete Code
1
if(ptr&&*ptr)
überhaupt sinnvoll und absolut korrekt. Bei falscher Evaluationsrichtung
würde man sofort einen Null-Pointer-Zugriff riskieren. Nur: So einen
falsch arbeitenden Compiler, den Du da postulierst, gibt es gar nicht.
W.S. schrieb:> Also darf man beim Schreiben beispielsweise eines seriellen Treibers> nicht darauf bauen, daß er nur ein einziges Mal nach Systemstart> initialisiert wird. Und schon beim 2. Aufruf der Init-Funktion ist die> ganze Diskussion um Nullung am Systemstart für die Katz.
Überhaupt nicht, hier kann man das Feature, dass static Variablen bei
Programmstart garantiert genullt sind, sogar direkt ausnutzen:
1
intserial_driver_init(void)
2
{
3
staticintalready_called;
4
5
if(!already_called)
6
{
7
...// do initialization here!
8
already_called=1;
9
}
10
}
Schon ist Deine Argumentation für die Katz. Diese init-Funktion kannst
Du auch tausendmal aufrufen, wenn Du möchtest.
P.S.
Ich habe hier mit Absicht auf bool (bzw. true/false) verzichtet, um
nicht noch ein Fass aufzumachen.
W.S. schrieb:> Bedenke mal, daß jeglicher Code in einem C Programm in irgend einer> Funktion steht.
Nein. Es gibt etwas, das vor allen Funktionen läuft, also vor main(),
das ist der Startup-Code. In der objektorientierten Variante (die für
dich bei Pascal wahrscheinlich der gedankliche Standard ist) werden
natürlich außerdem noch die Konstruktoren statischer und globaler
Objekte davor gerufen.
Erst dann kommt main() zum Zuge.
Klar, man kann natürlich auf jegliche globale und statische Variablen
verzichten und versuchen, alles mit automatischen Variablen zu machen.
Aber selbst Standard-Pascal hat globale Variablen, und
nicht-Standard-Pascal hat üblicherweise auch statische Variablen. Sie
werden einen Grund haben, sowas da mit eingeführt zu haben. ;-)
Yalu X. schrieb:> weswegen der C-Standard für die logischen Operatoren && und || beides> (definierte Reihenfolge und Abbruch, sobald das Ergebnis feststeht)> vorschreibt
Hatte gestern gerade festgestellt, dass es einen Standard für "extended
Pascal" gibt, der dafür dann eigens die Schlüsselwörter and_then und
or_else erfunden hat.
Jörg W. schrieb:> Hatte gestern gerade festgestellt, dass es einen Standard für "extended> Pascal" gibt, der dafür dann eigens die Schlüsselwörter and_then und> or_else erfunden hat.
Es gibt mit Gnu Pascal sogar einen (und wahrscheinlich den einzigen)
Compiler, der diesen Standard weitgehend implementiert. Aber Gnu Pascal
wird schon seit langem nicht mehr weiterentwickelt.
Für den FPC wurde 2017 die zukünftige Unterstützung von Extended Pascal
angekündigt. Immerhin gibt es dafür schon die Compiler-Direktive {$mode
extendedPascal}, aber noch nicht viel mehr. Auch and_then und or_else
werden noch nicht als Schlüsselwörter erkannt.
Aber W.S. würde diese neuen Operatoren sowieso nicht verwenden, sondern
darin einen Bug wittern, der unbedingt aroundgeworkt werden muss ;-)
Ich muss (leider) W.S. in einem Ding recht geben ...
Globale und statische Variablen werden durch den Startup-Code
initialisiert (nullen des BSS-Segments) und das ist nicht Teil des
C-Standards.
Ansonsten finde ich die Diskussionen hier etwas mühsam - man kann sich
in der Regel darauf verlassen, dass es funktioniert und wenn nicht,
merkt man das auch schnell.
Ist vielleicht eine pragmatische Sicht eines Pragmatikers^^
Mampf F. schrieb:> Ich muss (leider) W.S. in einem Ding recht geben ...>> Globale und statische Variablen werden durch den Startup-Code> initialisiert (nullen des BSS-Segments) und das ist nicht Teil des> C-Standards.
Das ist falsch. Der C-Standard definiert zwar nicht, dass es einen
Startup-Code oder ein BSS-Segemnt gibt, aber dass alle Variablen mit
static storage duration implizit mit 0 initialisiert werden, sofern
keine explizite Initialisierung vorliegt, ist sehr wohl im Standard
definiert.
Rolf M. schrieb:> Das ist falsch. Der C-Standard definiert zwar nicht, dass es einen> Startup-Code oder ein BSS-Segemnt gibt, aber dass alle Variablen mit> static storage duration implizit mit 0 initialisiert werden, sofern> keine explizite Initialisierung vorliegt, ist sehr wohl im Standard> definiert.
Vielen Dank für die Korrektur!
Genauso, wie natürlich nicht mit 0 initialisierte globale und statische
Objekte "irgendwoher" ihre Initialwerte bekommen müssen. Das wie ist
auch hier nicht im Standard definiert.