https://github.com/torvalds/linux/blob/master/mm/memtest.c
Zeile 37 und folgende.
Der Compiler könnte doch in Zeile 52 auf die Idee kommen und sagen:
Moment mal, eben hat er den ganzen Speicherblock mit pattern gefüllt,
jetzt will er wissen ob das immer noch drin steht, mein Speicher ist
ideal, der verliert keine Bits, es gibt auch keinen Seiteneffekt, weg
mit dem unerreichbaren Code! Weg mit der ganzen Schleife, sie tut ja
nichts!
Ich persönlich hätte den Pointer als
volatile u64* p;
deklariert. Gegen meine Theorie spricht daß der Spaß immer noch 5
Minuten dauert, eigentlich hätte er IMHO beide Loops wegoptimieren
dürfen. Dafür spricht daß bei einem Rock64 hier mit hunderten von
Speicherfehlern der damit fast so unbenutzbar ist daß es kaum im ersten
Anlauf gelingt ein neues Kernel image ohne Beschädigung nach /boot/efi
zu kopieren, daß auf diesem Gerät die obige Routine nicht einen einzigen
Fehler findet! Nicht einen einzigen!
Der Speichertest ist aus noch einem anderen Grund nicht besonders toll:
es wird immer mit demselben Pattern getestet. Hängende Adreßleitungen
findet man damit nicht.
Bernd K. schrieb:> Moment mal, eben hat er den ganzen Speicherblock mit pattern gefüllt,> jetzt will er wissen ob das immer noch drin steht, mein Speicher ist> ideal, der verliert keine Bits, es gibt auch keinen Seiteneffekt, weg> mit dem unerreichbaren Code!
Bei "seinem" Speicher träfe das sicher zu - zB wenn man das auf einem
lokal definiertem Array machen würde.
1
f();
2
f();
3
4
void f() {
5
int x;
6
printf("%x %d\n", &x, x);
7
x ++; /* see you next time! */
8
}
Hier müsste der Compiler eigentlich wissen, dass er den Speicher
verändert haben sollte. Also x mit gleicher Adresse UND gleichem Wert
ist nach der Logik ein absolutes no-go.
Peter schrieb:> Jup, hast recht.>> Bei welcher Optimierungsstufe hast du compiliert?
Keine Ahnung ;-)
ich hab https://github.com/ayufan-rock64/linux-build verwendet und da
steckt ein komplexes cross-build-System drin mit docker und binfmt-misc
und haste nicht gesehn. Leider erscheinen keine vollständigen
Compileraufe an der Konsole sondern nur ein aufgehübschtes knappes log
über den Fortschritt.
Ich hab keine Ahnung wo in diesem riesigen Berg die Kompileroptionen
stecken und wie man sie zutage fördert, das müsste mir jemand sagen der
weiß wie das kernel-buildsystem funktioniert.
Ich werd heute Abend mal den besagten pointer volatile deklarieren,
damit einen kernel bauen und es nochmal versuchen.
Udo K. schrieb:> Bernd K. schrieb:>> Ich sehe keine. Zeigst Du sie mir?>> Zeile 59
Die wird nie erreicht weil aus Sicht der abstrakten Maschine Zeile 52
zwingend immer true ergeben muss. Alles unterhalb von Zeile 53 ist
unerreichbarer Code.
Bernd K. schrieb:> Ich sehe keine. Zeigst Du sie mir?
ich sehe auch keine. Ab Zeile 50 passiert nichts mehr, was über das Ende
der Funktion hinaus eine Wirkung haben könnte.
"volatile" wird im Linuxkernel nicht gerne gesehen, die Gründe werden in
https://www.kernel.org/doc/html/v4.12/process/volatile-considered-harmful.html
beschrieben.
Die "linuxtypische" Lösung wäre ein barrier(); in Zeile 50, damit wird
dem Compiler verboten, Speicherzugriffe über diese Grenze hinweg zu
optimieren oder zu verschieben. Für einen Speichertest wäre es evtl.
noch eine gute Idee, zusätzlich einen Cache Flush zu erzwingen, damit
man garantiert RAM und nicht nur Caches testet.
Na ja, die "abstrakte virtuelle Maschine" ist der gcc, und
der ist nicht allwissend...
Wenn du die erste Schleife in die zweite einbaust, dann checkt
gcc das, so aber nicht.
Schreib doch eine Patch?
Manchmal-Kernel-Hacker schrieb:> "volatile" wird im Linuxkernel nicht gerne gesehen, die Gründe werden in> https://www.kernel.org/doc/html/v4.12/process/volatile-considered-harmful.html> beschrieben.
Hab ich auch gelesen. Das Dokument geht auf die mißbräuchliche
Verwendungen von volatile ein da offenbar viele falsche Vorstellung über
den Zweck und die Wirkung von volatile kursieren, es räumt dann aber
auch Umstände ein wann es tatsächlich angebracht ist. Der Speichertest
fiele zum Beispiel in diese Kategorie, allerdings könnte eine
Compilerbarriere auch zum Ziel führen.
Udo K. schrieb:> Schreib doch eine Patch?
Das ist meine Absicht. Ich will erreichen daß memtest auf der
vorhandenen Hardware welche nachweislich hunderte von Speicherfehlern
aufweist auch ebensolche findet, zumindest einen Teil davon, im jetzigen
Zustand findet sie nicht einen einzigen! Etwas ist da oberfaul und der
besagte code ist schonmal suspekt (bzw. falsch).
Bernd K. schrieb:> Etwas ist da oberfaul und der> besagte code ist schonmal suspekt (bzw. falsch).
Wie ich schon schrieb, dieser memtest wird keinen einzigen Fehler
entdecken, der durch hängende Adreßleitungen zustandekommt. Weil das
Pattern weggeschrieben wird, aber nicht dort, wo es hinsoll, und beim
Zurücklesen derselbe Fehler.
Deswegen rotiert man so ein Pattern ja auch in der Wegschreib-Schleife,
um sowas zu entdecken.
Mit dem Buildsystem rumzuspielen wird nichts bringen.
Wie ich schon schrieb, wird der Code nicht wegoptimiert,
auch nicht mit -O3.
Wenn keine vorhandenen Fehler erkannt werden,
liegt es daran, dass der Memtest nicht besonders umfangreich ist.
Da gehts einfach um einen schnellen Check, ob überhaupt ein
Riegel drinnen steckt.
Das ist meiner Meinung nach auch Ok,
Linux ist ein Betriebssystem und kein Memory Test Program.
Udo K. schrieb:> Da gehts einfach um einen schnellen Check, ob überhaupt ein> Riegel drinnen steckt.
Nein, wenn das das Ziel wäre würden sie nicht 17 Pattern an jeder
Adresse testen. Um zu testen wieviele Riegel drinstecken muss man nicht
5 Minuten lang auf dem RAM herum rödeln. Wieviel RAM drinsteckt kann der
Kernel auch so erkennen. Der memtest hat schon das erklärte Ziel
defektes RAM zu finden und zu isolieren.
Udo K. schrieb:> Wie ich schon schrieb, wird der Code nicht wegoptimiert,> auch nicht mit -O3.
Hast Du es probiert? Wo schriebst Du das, hab ich überlesen.
Abgesehen von der o.a. volatile- und Compilerproblematik kann solch ein
RAM-Test auch nur funktionieren, wenn er auf einem nicht gecachten
Speicherbereich ausgeführt wird. Ist dies denn sichergestellt?
Andreas S. schrieb:> Ist dies denn sichergestellt?
Nein, das ist es leider auch nicht. Aber im konkreten Fall testet es 1GB
RAM und so groß ist der cache da nicht, er müsste zumindest irgendwas
finden.
@hängende Adressleitungen:
Das userspace programm memtester findet auf dieser Hardware zum Beispiel
solche Sachen wie
0x2a2a2a2a2aaa2a2a != 0x2a2a2a2a2a2a2a2a
Und zwar über den ganzen Adressraum verstreut, überall.
und überhaupt scheint es bei allen Fehlern die memtester ausspuckt immer
das exakt selbe bit zu sein. Auch bei Dateien die beim Kopieren kaputt
gingen ist immer irgendwo genau bit 7 in irgendeinem einzigen Byte
gesetzt oder gekippt, alle 60..100MB taucht eins auf, und niemals ein
anderes bit.
Bernd K. schrieb:> Das ist meine Absicht. Ich will erreichen daß memtest auf der> vorhandenen Hardware welche nachweislich hunderte von Speicherfehlern> aufweist auch ebensolche findet, zumindest einen Teil davon, im jetzigen> Zustand findet sie nicht einen einzigen!
Ist memtest denn in deinem Kernel aktiviert? Bei mir (archlinux x86_64)
ist sie nicht aktiviert.
mh schrieb:> Ist memtest denn in deinem Kernel aktiviert? Bei mir (archlinux x86_64)> ist sie nicht aktiviert.
Natürlich ist es aktiviert, deshalb hab ich mir ja wie ich oben schon
schrieb extra die Quellen gezogen und einen eigenen Kernel gebaut und
deshalb dauert der boot auch 5 Minuten wie ich oben schon schrieb.
Wenn es läuft sieht man im dmesg ein log über die Bereiche die er
getestet hat (die kann ich sehen) und man müsste auch Ausgaben sehen
über defekte Bereiche die er dann zwecks Schadensvermeidung reserviert.
Letzteres geschieht bei mir nicht.
Bernd K. schrieb:> Nein, das ist es leider auch nicht. Aber im konkreten Fall testet es 1GB> RAM und so groß ist der cache da nicht, er müsste zumindest irgendwas> finden.
Wenn der Schreibzugriff und Lesezugriff so nahe beieinander liegen, wird
einfach der gecachte Inhalte sofort wieder zurückgelesen, d.h. es
handelt sich immer um Cache Hits. Das ist unabhängig von der
Speichergröße. Um Cachingeffekte auszuschließen, müsste zunächst ein so
großer Speicherbereich geschrieben werden, dass die ersten
Schreibzugriffe bereits aus dem Cache herausgefallen sind und somit
jeder Lesezugriff zu einem Cache Miss führt.
Oder es muss eben sichergestellt werden, dass alle(!) Datencaches
deaktiviert wird.
Andreas S. schrieb:> Wenn der Schreibzugriff und Lesezugriff so nahe beieinander liegen,
Schau in den code. Er schreibt den ganzen Bereich voll und dann erst
liest er. Laut Debugausgabe ist das im Wesentlichen ein riesen Block am
Stück und noch 5 oder 6 kleine Krümel (für die mag das zutreffen).
Bernd K. schrieb:> Es ist "sein" Speicher.
Unsinn. Wäre das "sein" Speicher, würde er die Schreiberei wegstreichen,
wie mein Beispiel verdeutlicht haben sollte. Der ist allerhöchstens
geliehen.
>Nirgends ist deklariert daß noch jemand anders drauf zugreift.
Das wäre ja auch ein Ding der völligen Unmöglichkeit. Außerdem ist die
Ei-Henne-Problematik da ganz einfach. Einen read x=*((int*)532) kann der
Compiler im allgemeinen nicht einfach weglassen, wenn er das Ergebnis
dieses Reads nicht irgendwo gespeichert hat. Das ginge nur wenn
Memory-Location 532 "sein" Speicher wäre.
Heiko L. schrieb:> Außerdem ist die> Ei-Henne-Problematik da ganz einfach.
Die Problematik ist ganz einfach: Einem standardkonformen Compiler wäre
es erlaubt die komplette Funktion wegzuoptimieren weil er allein durch
statische Code-Analyse problemlos beweisen könnte daß der code unterhalb
des continue in der zweiten Schleife niemals ausgeführt würde, dazu
müsste die Maschine kaputt sein aber davon weiß der Compiler nichts. Und
nachdem er also bewiesen hat daß die zweite Schleife nichts tut darf er
sie weg optimieren. Im nächsten Schritt darf er dann auch die erste
Schleife entfernen denn sie schreibt Speicher der niemals wieder für
irgendwas verwendet wird.
Wenn der aktuelle gcc das zufällig in der Tiefe noch nicht beherrscht
ist das reine Glückssache. Kann sich morgen schon ändern und wäre nicht
das erste Mal daß so mancher plötzlich dumm aus der Wäsche schaut wenn
nach dem gcc Update sein undefinierter Code plötzlich nicht mehr macht
was er vermeintlich hätte machen sollen.
Einfach so in den Speicher zu schreiben ist sowieso verboten. Solcher
Low-Level Code ist meistens nicht standardkonform hinzubekommen. Da
hilft nur, zu hoffen dass der Compiler es trotzdem wie gewünscht
umsetzt, oder eben Assembler - aber den muss man dann manuell portieren.
Ein Gutteil von Linux funktioniert ja auch nur mit -fno-strict-aliasing,
ist also sowieso fehlerhaft im Sinne des Standards.
Dr. Sommer schrieb:> Da> hilft nur, zu hoffen dass der Compiler es trotzdem wie gewünscht> umsetzt
volatile wurde für diesen Zweck erfunden. Es sagt dem Compiler daß der
Zugriff auf eine solche Variable eine Außenwirkung (also I/O) darstellt
und daher auf der realem Maschine ausgeführt werden muss.
Dr. Sommer schrieb:> Einfach so in den Speicher zu schreiben ist sowieso verboten.
Er ist der Kernel. Der darf das. Irgendeiner muss es ja schließlich tun.
Bernd K. schrieb:> Er ist der Kernel. Der darf das. Irgendeiner muss es ja schließlich tun.
Klar, aber im Standard gibt's keinen Kernel :) Pointer zu
dereferenzieren die irgendwo in den Speicher zeigen ist ziemlich
undefiniert.
Bernd K. schrieb:> Die Problematik ist ganz einfach: Einem standardkonformen Compiler wäre> es erlaubt die komplette Funktion wegzuoptimieren weil er allein durch> statische Code-Analyse problemlos beweisen könnte daß der code unterhalb> des continue in der zweiten Schleife niemals ausgeführt würde, dazu> müsste die Maschine kaputt sein aber davon weiß der Compiler nichts.
Nope, der write ist Pflicht. Ist ja nicht sein Speicher.
Aufrufe an opake Funktionen wie "pr_info" und was da alles rumschwirrt
sind implizite Speicherbarrieren. Davon abgesehen gehört der Baum nicht
wirklich dem Hund, nur weil er dagegen pisst.
Dr. Sommer schrieb:> Bernd K. schrieb:> Er ist der Kernel. Der darf das. Irgendeiner muss es ja schließlich tun.>> Klar, aber im Standard gibt's keinen Kernel :) Pointer zu> dereferenzieren die irgendwo in den Speicher zeigen ist ziemlich> undefiniert.
Nö, das ist völlig in Ordnung, was glaubst du was wir nebenan im
Mikrocontroller Forum den ganzen Tag machen?
Heiko L. schrieb:> Nope, der write ist Pflicht. Ist ja nicht sein Speicher.
Wo steht das geschrieben? Ich sehe kein volatile das das Schreiben
erzwingen würde.
Bernd K. schrieb:> Wo steht das geschrieben? Ich sehe kein volatile das das Schreiben> erzwingen würde.
Die Welt besteht nicht nur aus volatile.
Heiko L. schrieb:> implizite Speicherbarrieren
Oliver
Wenn sich ein Speicherinhalt unvorhergesehen ändert bzw. ändern kann,
z.B. auch durch einen Fehler in der Hardware, dann sind die
Speicherzellen grundsätzlich als volatile zu betrachten.
Auch wenn explizite volatiles durch eine Memory-Barrier ersetzbar sein
sollten, kann ich den Vorteil einer Barrier nicht erkennen: Die zu
testenden Speicherzellen sind immer noch zu schreiben und zu lesen;
Zugriffe bzw. Zugriffszeiten können also schwerlich eingespart werden —
und wenn, ist die Barrier überaus fraglich oder wurde falsch eingesetzt.
Zudem ist eine Barrier nicht portabel, und die Barriert steht an anderer
Stelle als der eigentliche Zugriff. Bei volatile ist hingegen der
eigentliche Zugriff selbst mit einem Qualifier belegt.
Je nach Hardware bzw. Speichermodell kann auch ein volatile zu schwach
sein, etwa wenn echte Parallelität gegeben ist und wo u.U. C/C++ atomic
angezeigt ist.
Features wie Caches, Load- und Store-Buffer sind besonders zu
berücksichtigen. Wenn die Zielapplikation zum Beispiel Caches
verwendet, dann ist ein Speichertest doch so zu designen, dass er sowohl
mit Caching als auch ohne Caching durchgeführt wird?
Falls ein Speichertest Fehler wirft, sollte auch Zusammenhänge geprüft
werden mit: Spannungsversorgung bzw. Transienten, Temperatur,
Konfigurationen für Takt, PLL, VCO, Wait-States, etc.
Bernd K. schrieb:> Wo steht das geschrieben? Ich sehe kein volatile das das Schreiben> erzwingen würde.
Du meinst also, dass die Funktion
1
memcpy(void*dst,void*src,intn);
nicht nach *dst schreiben muss, weil ja nichts (in der Funktion memcpy)
mit dem Speicher passiert? Das "letzte" Schreiben muss immer erfolgen,
ein 10-faches Schreiben nacheinander kann dagegen gerne durch das zehnte
ersetzt werden.
Achim S. schrieb:> Du meinst also, dass die Funktionmemcpy(void *dst, void *src, int n);> nicht nach *dst schreiben muss,
Wenn dst hinterher nicht mehr verwendet wird oder sein Inhalt nicht das
observable-behavior der abstrakten Maschine beeinflusst muss sie das
nicht. Der Compiler kann dann sogar ganzen Aufruf von memcpy einfach
weglassen.
1
5.1.2.3 Program execution
2
3
(5) The least requirements on a conforming implementation are:
4
5
- At sequence points, volatile objects are stable in the sense that
6
previous accesses are complete and subsequent accesses have not
7
yet occurred.
8
9
- At program termination, all data written into files shall be
10
identical to the result that execution of the program according to
11
the abstract semantics would have produced.
12
13
- The input and output dynamics of interactive devices shall
14
takeplace as specified in 7.19.3. The intent of these requirements
15
is that unbuffered or line-buffered output appear as soon as possible,
16
to ensure that prompting messages actually appear prior to aprogram
Oliver S. schrieb:> Die Welt besteht nicht nur aus volatile.
Volatile ist einer der drei einzigen Eckpfeiler mit denen die abstrakte
Maschine mit der realen Welt verbunden ist.
Die anderen beiden sind Lesen/Schreiben von Dateien und Eingabe/Ausgabe.
Nicht jedoch der Zustand von RAM Speicher.
Johann L. schrieb:> Falls ein Speichertest Fehler wirft, sollte auch Zusammenhänge geprüft> werden mit: Spannungsversorgung bzw. Transienten, Temperatur,> Konfigurationen für Takt, PLL, VCO, Wait-States, etc.
Da hab ich keinen Einfluss drauf. Alles was vor dem Kernel geschieht hat
der Hersteller verbrochen. Ein identisches Austauschgerät aus der selben
Charge scheint fehlerfrei zu sein.
Bernd K. schrieb:> Wenn dst hinterher nicht mehr verwendet wird oder sein Inhalt nicht das> observable-behavior der abstrakten Maschine beeinflusst muss sie das> nicht.
meine memcpy-Quelltexte sind alle ohne volatile, zum Teil von namhaften
Herstellern. Haben die alle versagt?
Oder meinst Du C++ (Deine Referenz zeigt ja eindeutig auf C)-
Ich würde mich einfach mal drauf verlassen, dass der Linux memtest
genau das macht was er soll, einen minimalen RAM Test.
Deine HW ist wahrscheinlich Buggy, oder du hast irgendwo anders
einen grossen Knoten.
Lass doch einmal Memtest86 laufen, dann parallel Prime95,
und berichte was rauskommt!
Auf Volatile rumzureiten, führt zu nichts.
Je nach Compiler, Version und Kommandozeile
macht das schon mal was anderes, und der Code
wird auf jedenfall grottenlangsam.
Darauf würde ich mich nicht verlassen wollen.
Der Standard ist eine Sache, wie es dann in der Praxis
ausschaut eine ganz andere, wenn deine abstrakte Maschine auf
die Realität trifft...
sobald z.b. irgendein Interrupt passiert, ist das "undefined behaviour".
Und nebenbei finde ich es idiotisch von Compiler zu verlangen,
den mühsam geschriebenen Code wieder rauszuwerfen?
Wer braucht denn sowas?
Wenn der Compiler so weit ist, wird er dich sowieso arbeitslos machen.
Grüße, Udo
Udo K. schrieb:> sobald z.b. irgendein Interrupt passiert, ist das "undefined behaviour".
Nö. C ist gerade für Lowleve-Systemprogrammierung entworfen worden.
> Wer braucht denn sowas?
Jeder, der performanten Code haben will. Deswegen gibt's in C die
as-if-Regel.
Nop schrieb:>> sobald z.b. irgendein Interrupt passiert, ist das "undefined behaviour".>> Nö. C ist gerade für Lowleve-Systemprogrammierung entworfen worden.
Dann lies im Standard nach...
>>> Wer braucht denn sowas?>> Jeder, der performanten Code haben will. Deswegen gibt's in C die> as-if-Regel.
Die kenne ich nicht...
Ich mache das immer so:
#ifdef FEATURE_X
/* feature_x drinnen */
#else
/* feature_x draussen */
#endif
Das funktioniert einfach.
Udo K. schrieb:>> Jeder, der performanten Code haben will. Deswegen gibt's in C die>> as-if-Regel.>> Die kenne ich nicht...
Solltest Du aber, wenn Du C verstehen willst. C++ übrigens genauso.
> Ich mache das immer so:
ifdef hat damit nichts zu tun. Es geht darum, daß der Compiler den Code
optimieren kann.
@Nop:
Du bist ja ein richtiger Schlaumeiner, willst mir nicht
auf die Sprünge helfen?
Ich traue mich aber zu wetten, dass mein Code schneller läuft,
als deiner :-)
Udo K. schrieb:> Du bist ja ein richtiger Schlaumeiner, willst mir nicht> auf die Sprünge helfen?
Nö. Wenn Dir die as-if-Regel nicht geläufig ist, kannste ja googeln.
Oder hab ich den Ninja-Smiley übersehen?
> Ich traue mich aber zu wetten, dass mein Code schneller läuft,> als deiner :-)
Wenn Du alle ifdefs ausblendest bestimmt.
Udo K. schrieb:> willst mir nicht> auf die Sprünge helfen?
Der relevante Auszug aus dem Standard wurde oben gepostet, welchen Teil
davon hast Du nicht verstanden?
Achim S. schrieb:> meine memcpy-Quelltexte sind alle ohne volatile
Was daher kommt, daß char * alles aliasen darf, nach Standard. Das
bedeutet übrigens auch, daß optimierte memcpy-Routinen nicht in
Übereinstimmung mit dem C-Standard machbar sind.
Achim S. schrieb:> meine memcpy-Quelltexte sind alle ohne volatile, zum Teil von namhaften> Herstellern. Haben die alle versagt?>> Oder meinst Du C++ (Deine Referenz zeigt ja eindeutig auf C)-
Du brauchst kein volatile im memcpy solange das Ergebnis in irgendeiner
Weise die Ausgabe des Programms beeinflußt. Es muss auch nicht volatile
sein weil es keine Rolle spielt ob es ins RAM oder in die Hirnrinde des
Compilerschreibers kopiert wird, Hauptsache das Programm liefert das
selbe Ergebnis an seinen I/O Schnittstellen.
Volatile dient dazu solche I/O Schnittstellen zu definieren. Genau dazu
dient es und zu sonst nichts.
Das hier hat erzeugt eine einzige Ausgabe:
1
intmain(void){
2
u32*p=(u32*)0x1FFFFC00;
3
*p=42;
4
if(*p!=42){
5
return*p;
6
}
7
return0;
8
}
Und ist nachweislich äquivalent zu folgendem und darf entsprechend
optimiert werden:
1
intmain(void){
2
return0;
3
}
Wenn Du sicher stellen willst daß physikalischer Speicher tatsächlich
geschrieben und gelesen wird und nicht nur abstrakter Speicher dann
musst Du physikalischen und abstrakten Speicher mit volatile aneinander
koppeln, den betreffenden Speicher also zur Ein-/Ausgabeschnittstelle
erklären. Ansonsten ist es Glücksache ob der Inhalt des RAM auch nur
annähernd das widerspiegelt was in der abstrakten Maschine vor sich
geht.
Im Falle des memtest muß man den betreffenden Speicher als I/O
deklarieren, nur dann wird die abstrakte Maschine ihn auch als solchen
behandeln und Eingaben von der Außenwelt (gekippte Bits) über diesen
Kanal überhaupt in Erwägung ziehen.
Bernd K. schrieb:> Udo K. schrieb:>> willst mir nicht>> auf die Sprünge helfen?>> Der relevante Auszug aus dem Standard wurde oben gepostet, welchen Teil> davon hast Du nicht verstanden?
Ich habe denn gar nicht gelesen.
Das klingt alles ziemlich theoretisch.
Ich dachte du willst ein praktisches Problem lösen?
Nop schrieb:>> meine memcpy-Quelltexte sind alle ohne volatile>> Was daher kommt, daß char * alles aliasen darf, nach Standard. Das> bedeutet übrigens auch, daß optimierte memcpy-Routinen nicht in> Übereinstimmung mit dem C-Standard machbar sind.
So ein Blödsinn.
So was kommt raus, wen Theoretiker an einem
Standard rumbasteln...
Udo K. schrieb:> So was kommt raus, wen Theoretiker an einem> Standard rumbasteln...
Die aliasing-Regeln haben ihren Grund, und der liegt maßgeblich darin,
daß Fortran immer noch schneller als C ist, was wiederum daher kommt,
daß aliasing in Fortran überhaupt nicht erlaubt ist.
Gerade C wurde sehr pragmatisch zusammengestellt, weil dahinter nämlich
ein konkretes Projekt stand. Das unterscheidet C von dem ganzen
Wirth-Universum.
Udo K. schrieb:> Ich habe denn gar nicht gelesen.> Das klingt alles ziemlich theoretisch.>> Ich dachte du willst ein praktisches Problem lösen?
Wenn Leute anfangen, ohne jede Standardkenntnis sich irgendwas in C
zusammenzuhacken, geht das große Geschrei los, wenn nach einem
Compilerupdate auf einmal existierender Code nicht mehr funktioniert.
Wenn man sowas nicht will, hält man sich entweder an den Standard, oder
man dokumentiert sauber, wo man abweicht - dann geht die Fehlersuche
später nämlich schneller.
Udo K. schrieb:> Ich habe denn gar nicht gelesen.> Das klingt alles ziemlich theoretisch.
Das ist nicht theoretisch das ist praktisch. Da steht im Wesentlichen:
Der Compiler garantiert daß das Programm nach Außen hin exakt das
macht was der Programmierer von ihm verlangt. Wie es das intern
erreicht spielt keine Geige.
Außerdem steht da genau was unter "nach außen" zu verstehen ist und es
gibt genau exakt ein einziges Schlüsselwort mit dem man zusätzliche
Außenschnittstellen schaffen kann und das ist "volatile".
> Ich dachte du willst ein praktisches Problem lösen?
Und das löse ich Deiner Meinung nach am besten indem ich den Standard
ignoriere und mich auf undefiniertes Verhalten verlasse oder was?
Gähn, ist ja schlimmer mit euch als ich dachte.
Nop schrieb:> Die aliasing-Regeln haben ihren Grund, und der liegt maßgeblich darin,> daß Fortran immer noch schneller als C ist, was wiederum daher kommt,> daß aliasing in Fortran überhaupt nicht erlaubt ist.
Zeig mir doch mal den Code der durch irgendwelche aliasing
Reglen auch nur 1%% schneller wird.
Das ist doch nur Hirnwixerei für überbezahlte gelangweilte
Akademiker bei der C++ Standardisierungsbhörde.
Aber um zum sachlichen zu kommen:
Erstens ist Fortran Code heute schon lange nicht mehr schneller.
Schon lange nicht mehr auf einem Core-i7,
mit massiver SIMD Unterstützung.
Grund: Da gibt es keinen Fortran Code mehr :-)
Zweitens ist Fortran Code schneller, weil die Leute die den
programmiert haben eine Ahnung hatten.
Die haben ihre Zeit nicht mit dem Lesen von 2000 Seiten
Standards vergeudet.
Die haben sich den Assemblercode angeschaut, der hinterher
rauskommt, Standard hin oder her.
Drittens hilft auf einem modernen Core-i7 aliasing Reglen nix,
da brauchst du entweder massive Assemblerkenntnisse,
oder die Intel Performance Libraries.
Und virtens: Was hat das jetzt alles damit zu tun,
das der Rechner vom TE schadhafte Ramriegel hat?
Warum postet der hier, anstatt die Ursache zu finden?
Dr. Sommer schrieb:> Der Laufzeitunterschied dürfte dazwischen liegen.
Bei Numerik dürfte er sogar höher sein, sofern man auch konsequent mit
const und restrict arbeitet.
Nop schrieb:> Bei Numerik dürfte er sogar höher sein, sofern man auch konsequent mit> const und restrict arbeitet.
In der Tat! Deswegen wird FORTRAN heutzutage sehr wohl noch verwendet,
auch auf Core-i-Prozessoren, weil es für wissenschaftliches Rechnen
schneller als C ist, sofern man eben nicht sehr genau auf aliasing
achtet und restrict anwendet.
Vielleicht sollte jemand dem Standard-Comittee mal eine Mail schreiben,
dass die Mist bauen. Oder ist es einfach nur schlechtes zitieren?
EGal...
Bernd K. schrieb:> Der Compiler garantiert daß das Programm nach Außen hin exakt das> macht was der Programmierer von ihm verlangt.
Mal ganz ab vom Standard: Gemeinsam genutzter Speicher ist DIE i/o
Schnittstelle schlechthin. Folglich bezieht sich dieses "außen"
logischerwise auf alles, was nicht das Programm selbst ist.
gehört dem Programm - und das ganz Standard-Konform: Das Array ist in
ihm deklariert und daher Teil des Programms. Wenn der Standard besagt,
dass eine erfolgreiche Deduktion der Irrelevanz eines Schreibzugriffes
auch dann vorliegt, wenn das Programm in Speicherbereiche schreibt, die
nicht als Teil seiner selbst deklariert sind, wiedersprechen sich die
Standard-Autoren selbst: Observable Behaviour liegt ganz klar vor, wenn
die Black-Box "Programm" auf Bereiche außerhalb seiner selbst wirkt.
Und wir sind uns doch denke ich einig, dass zB bei
int*p = (int*)0;
die Addresse von p und nicht Zelle 0 der Speicher des Programms ist -
sonst frag einfach mal den Kernel, indem du da reinschreibst.
Bei Lesezugriffen ebenso: Es kann nicht einfach geschlossen werden,
dass, weil das Programm (noch) nicht auf eine beliebige Memory-Zelle
geschrieben hat das Resultat eines Reads folglich nicht definiert sein
kann, also man auch einfach im Register behalten kann, was sowieso schon
drin steht.
Wenn der Standard anderes besagt, ist das schon Pech: Er manövriert die
Sprache in Richtung java-mäßigens Autismus und treibt die Leute, die
einfach nur nach einem Weg suchen, der CPU Befehle zu übermitteln (statt
ein Programm ihre Programmierfehler beheben zu lassen) mit Notwendigkeit
hin zum guten, alten Makroassembler.
Eine Library (oder ein Kernel-Modul), das eine öffentliche Schnittstelle
zur verfügung stellt, etwa
1
struct S* createS();
muss da aus gutem Grund kein "volatile struct S*" schreiben. Sonst
könnte der Code zum Ausfüllen des S prinzipiell nicht optimiert werden.
Aber zurück zum eigentlichen Thema:
Bernd K. schrieb:>> Ich dachte du willst ein praktisches Problem lösen?>> Und das löse ich Deiner Meinung nach am besten indem ich den Standard> ignoriere und mich auf undefiniertes Verhalten verlasse oder was?
Das wäre wohl das beste. Es soll ja der Hauptspeicher getestet werden
und nicht der potentiell allwissende CPU cache. Oder garantiert der
Standard bei volatile, dass CPU-caches umgangen werden?
Heiko L. schrieb:> Und wo ich darüber nachdenke: "undefined value" - gilt für die> eigentlich der Satz der Identität? Muss alsovoid f() {> int x;> printf("%d\n", x);> printf("%d\n", x);> }> zweimal den selben Wert ausgeben? Nein, oder?
Nein. Er muß sogar überhaupt keinen Wert ausgeben.
Heiko L. schrieb:> Bei Lesezugriffen ebenso: Es kann nicht einfach geschlossen werden,> dass, weil das Programm (noch) nicht auf eine beliebige Memory-Zelle> geschrieben hat das Resultat eines Reads folglich nicht definiert sein> kann
Doch, das kann man. Das ist die abstrakte Maschine, die hinter C steht.
Ein read auf eine Zelle, die nie geschrieben wurde, ist undefiniertes
Verhalten, in welchem Fall der Compiler tun und lassen darf, was er
will.
Es sei denn, es ist mit volatile markiert, denn dann muß der Compiler
annehmen, daß die Zelle außerhalb seines Sichtbarkeitsbereiches
beschrieben wurde.
> Wenn der Standard anderes besagt, ist das schon Pech
Nein, ist es nicht. Es ist der Grund, wieso C-Compiler heftig optimieren
können. Der compilierte Code braucht nämlich nur in den Fällen dasselbe
zu tun wie der Quellcode, in denen kein undefiniertes Verhalten
vorliegt. Folglich kann sich der Compiler beim Optimieren auf diese
Fälle beschränken.
> und treibt die Leute, die> einfach nur nach einem Weg suchen, der CPU Befehle zu übermitteln (statt> ein Programm ihre Programmierfehler beheben zu lassen) mit Notwendigkeit> hin zum guten, alten Makroassembler.
Nein. Für alle wesentlichen Fälle stellt C entsprechende Mechanismen zur
Verfügung. Gegen Aliasing-Probleme hilft beispielsweise memcpy, was vom
Compiler übrigens auch wegoptimiert wird und stattdessen als castender
Zugriff umgesetzt wird. Oder seit C99 (nicht aber in C++) type punning
über unions.
> Eine Library (oder ein Kernel-Modul), das eine öffentliche Schnittstelle> zur verfügung stellt, etwastruct S* createS();> muss da aus gutem Grund kein "volatile struct S*" schreiben.
Nein, weil sie selber entweder den Speicher alloziert, das sieht der
Compiler dann aber, oder weil der Aufrufer den Speicher bereitstellt,
und auch das sieht der Compiler.
> Oder garantiert der> Standard bei volatile, dass CPU-caches umgangen werden?
Für solche Hacks in Anwesenheit von CPU-Caches muß man ohnehin memory
fences einsetzen. Die garantieren, daß die Daten in den Speicher
synchronisiert wurden. Logisch ist das plattformspezifisch, das ist ein
Speichertest aber ohnehin.
Nop schrieb:> Heiko L. schrieb:>> Und wo ich darüber nachdenke: "undefined value" - gilt für die>> eigentlich der Satz der Identität? Muss alsovoid f() {>> int x;>> printf("%d\n", x);>> printf("%d\n", x);>> }>> zweimal den selben Wert ausgeben? Nein, oder?>> Nein. Er muß sogar überhaupt keinen Wert ausgeben.
Grundsätzlicher betrachtet ging es eigentlich darum, dass ich
ontologische Bestimmungen des Inhalts einer imperativen
Programmiersprache für absolut unangemessen halte. Dort wird gesagt, was
getan werden soll, und nicht, was etwas sein soll.
Heiko L. schrieb:> Grundsätzlicher betrachtet ging es eigentlich darum, dass ich> ontologische Bestimmungen des Inhalts einer imperativen> Programmiersprache für absolut unangemessen halte.
Das ist ja schön, aber die Designer von C hatten keine
Philosophie-Lehrstunde im Auge, sondern eine Sprache, die beachtliche
Optimierungen des resultierenden Maschinencodes zuläßt.
Heiko L. schrieb:> Wenn der Standard anderes besagt, ist das schon Pech: Er manövriert die> Sprache in Richtung java-mäßigens Autismus
Sowohl für Java als auch für C und C++ definiert der jeweilige Standard
bzw. Spezifikation ein bestimmtes Verhalten für bestimmte
Programm-konstrukte. Konkrete Implementationen setzen dies um, sodass
Programme, die sich nur auf das definierte Verhalten verlassen, immer
konsistent funktionieren. Diese Idee ermöglicht erst portable Software
und dass es mehr als einen Compiler gibt. Sowohl in Java als auch C und
C++ gibt es auch Konstrukte mit undefinierten Verhalten, die dann
"zufällige" Resultate haben; in Java aber viel weniger. So etwas wie
Speicher Zugriffe für IO oder Speicher Tests können nicht sinnvoll
portabel vom Standard definiert werden, weshalb so etwas im Sinne des
Standards quasi immer "falsch" ist. Die Implementation, d.h. Compiler
und C(++) Standard Library müssen aber notwendiger Weise unportable
Dinge nutzen, um u.a. IO zu ermöglichen. Das wird hinter einem
standardisierten Interface gekapselt, sodass normale C-Programme damit
nichts zu tun haben. Die Interna sind nicht von Standard abgedeckt und
funktionieren "zufällig" weil sie auf den Compiler angepasst sind. Bspw
wird sich irgendwo indirekt in fwrite () ein 'asm volatile ("int 80h")'
oder "syscall" oder "svc" befinden, je nach Plattform. Gleiches gilt für
die Java Ausgabe-Funktionen. Die Funktion dieses Assembler Codes kann
unmöglich vom Standard abgedeckt werden. Somit ist Java hier keineswegs
"schlechter", und C war nie dafür gedacht Dinge wie Speichertests
konform (!) zu unterstützen. Unter bestimmten Bedingungen funktioniert
es eben doch. Nicht ohne Grund kann der Linux Kernel nur mit dem GCC und
nur mit bestimmten Optionen kompiliert werden. Das ist auch nicht
schlimm, damit muss man leben. Die einzige absolut korrekte Alternative
wäre es, das alles in Assembler zu schreiben- manche fänden das super,
aber praktikabel ist das nicht. Mir ist jedenfalls keine portable(!)
Sprache bekannt, welche Speicher Tests, Syscalls, direkte Speicher
Zugriffe für IO konsistent standardisiert. Dir etwa?
Nop schrieb:> Doch, das kann man. Das ist die abstrakte Maschine, die hinter C steht.> Ein read auf eine Zelle, die nie geschrieben wurde, ist undefiniertes> Verhalten, in welchem Fall der Compiler tun und lassen darf, was er> will
Es gibt keine abtrakte Maschinen. Ob die Zelle beschrieben wurde oder
nicht kann der Compiler nicht zeigen, wenn sie nicht in den Segmenten
des Programms liegt. Dort gibt es Regeln, wie sie auszufüllen sind oder
sie fallen als Variablen in den Verfügungsbereich des Compilers, der sie
mit beliebigen Werten füllen dürfte. Das trifft auf unbestimmte
Speicherzellen nicht zu. Insbesondere sollten zwei Programme, die eine
unbestimmte Speicherzelle lesen sollen (das besagt die Anweisung des
Programmierers), den selben Wert ausgeben, wenn denn der Wert dort
konstant ist. Auch dann, wenn da kein volatile steht. Sonst ist man auf
dem Weg in eine Phantasie-Welt per Default.
Ach ja, und sie wollten auch keinen Makro-Assembler schaffen, sondern
(für damalige Maßstäbe) eine Hochsprache. Deswegen arbeitet C in einer
abstrakten Maschine, welche man mittels C-Anweisungen befeuert.
Wer CPU-Befehle exakt umgesetzt haben will, kann das mit
Assemblerfunktionen tun, die lediglich ABI-kompatibel zum C-Compiler auf
dieser Plattform sein müssen. Oder mit Inline-Assembler.
Ist für Speichertests eigentlich ohnehin besser, weil man dann sicher
sein kann, daß man nicht aus Versehen z.B. den Stack erwischt, was
besonders auf von-Neumann-Rechnern fatal wäre. Zumindest realisiere ich
für embedded meine Speichertests auf diese Weise.
Heiko L. schrieb:> Insbesondere sollten zwei Programme, die eine> unbestimmte Speicherzelle lesen sollen (das besagt die Anweisung des> Programmierers), den selben Wert ausgeben, wenn denn der Wert dort> konstant ist.
Der Compiler muß dazu aber weder die Schreib-Anweisung noch die
Lese-Anweisung umsetzen, weil man dasselbe einliest wie das, was man
gerade rausgeschrieben hat. Solche im funktionalen Sinne überflüssigen
Anweisungen zählen zu dem, was der Compiler getrost wegoptimieren darf.
Oder, falls Du auf das printf anspielst: Das ist im Sinne des Standards
undefiniertes Verhalten, und der Compiler braucht nur Codepfade
umzusetzen, die sowas nicht enthalten. Das erleichetrt die Optimierung.
Nop schrieb:> Der Compiler muß dazu aber weder die Schreib-Anweisung noch die> Lese-Anweisung umsetzen, weil man dasselbe einliest wie das, was man> gerade rausgeschrieben hat. Solche im funktionalen Sinne überflüssigen> Anweisungen zählen zu dem, was der Compiler getrost wegoptimieren darf.
Bitte was? Programm 1 schreibt den Wert.
Nichts weiter läuft.
Programme 2 und 3 sollen den Wert 5 Minuten später lesen.
Es wäre paradox und höchst amüsant, um einen konstanten Wert auszulesen,
diesen im Programm als "volatile" deklarieren zu müssen.
Man kann da entweder mehr in Richtung der Widerspiegelung des realen
Systemzustandes steuern oder in die genau andere, wie mir scheint.
Heiko L. schrieb:> Mal ganz ab vom Standard: Gemeinsam genutzter Speicher ist DIE i/o> Schnittstelle schlechthin.
Gemeinsam mit wem? Mit sich selbst? Das findet er automatisch raus.
Gemeinsam mit irgendwas anderen auf der selben Maschine von dem er
nichts wissen kann? Sowas wie ein RAM-Riegel der über verschluckte Bits
Zufallszahlen liefert? Dann muss man einfach dem Compiler bekannt geben
daß der besagte Speicherbereich als I/O zu behandeln ist, dann wird dort
alles so ausgeführt wie hingeschrieben.
> die Black-Box "Programm" auf Bereiche außerhalb seiner selbst wirkt.
Solche Bereiche werden mit volatile gekennzeichnet sonst gelten sie
nicht als "außerhalb seiner selbst". Alles was nicht explizit als
Außenwirkung gekennzeichnet ist gehört dem Compiler und er kann damit
machen oder nicht machen was er will. Ist doch ganz einfach zu
verstehen? Du willst Außenwirkung per gemeinsamem Speicher? Dann
deklariere das entsprechend, die Sprache C hat ein Mittel dafür.
> Und wir sind uns doch denke ich einig, dass zB bei> int*p = (int*)0;> die Addresse von p und nicht Zelle 0 der Speicher des Programms ist -
Nein, wir sind uns nicht einig! Es ist nur irgendein Speicherbereich der
später nicht mehr benutzt wird, also ist es für das Programm
Zeitverschwendung da überhaupt was hinzuschreiben. Wenn Du willst daß es
als Außenwirkung gilt dann gib dem Speicher einfach die Typklasse
volatile. Dann zählt es als Ausgabe und wird auch pflichtgemäß
ausgeführt.
Geh doch einfach mal durch die Header eines AVR oder eines ARM
controllers und mach spaßeshalber alle volatiles bei den
Registerdefnitionen weg und dann schau mal ob noch irgendein Programm so
läuft wie vorgesehen. Die Hälfte der Zugriffe wird er weglassen oder
umsortieren weil ihm nicht bewußt ist daß irgendwer außer ihm selbst
sich für die Inhalte dieser Speicherzellen interessieren könnte.
> sonst frag einfach mal den Kernel, indem du da reinschreibst.
Der Compiler weiß nicht was ein Kernel ist und fragt auch nicht danach.
Bernd K. schrieb:> Es ist nur irgendein Speicherbereich der> später nicht mehr benutzt wird, also ist es für das Programm> Zeitverschwendung da überhaupt was hinzuschreiben.
Wenn man einen Speicherbereich im Programm deklarieren will, macht man
das Mittels eines Arrays o.Ä. Zeiger sind Zeiger und nicht das worauf
sie zeigen. Nicht deklariert heißt nicht angemeldet. Weiß man also gar
nix drüber.
Bernd K. schrieb:> Der Compiler weiß nicht was ein Kernel ist und fragt auch nicht danach.
Das ist imho Teil des Problems. In gcc gibt es übrigens ein "pure"
attribut, mit dem man sein Programm als "pure" deklarieren kann.
Heiko L. schrieb:> Zeiger sind Zeiger und nicht das worauf> sie zeigen. Nicht deklariert heißt nicht angemeldet.
Wenn Du Außenwirkung "anmelden" willst dann deklariere es als volatile.
Nirgends im Sprachstandard wird irgendwas von dem garantiert was Du Dir
da die ganze Zeit aus dem Finger saugst. Nur 3 Dinge werden garantiert
und die hab ich heute morgen zitiert (volatile Zugriffe, Dateizugriffe
und interaktive I/O). Die sind aber ausreichend um damit alles zu machen
was man will bis hinunter zum letzten Bit auf der Hardware. Man muss es
nur richtig deklarieren.
Bernd K. schrieb:> Nirgends im Sprachstandard wird irgendwas von dem garantiert was Du Dir> da die ganze Zeit aus dem Finger saugst
Ich rede ja auch nicht von nicht-existenten abstrakten Maschinen,
sondern davon, wie es wirklich ist.
Heiko L. schrieb:> Das ist imho Teil des Problems.
Kernel und Betriebssysteme sind zu unterschiedlich als dass es sinnvoll
wäre diese oder ihre Abwesenheit (im Falle von Embedded Systemen oder so
so etwas wie DOS, was keinen richtigen Kernel hat) im C Standard mit zu
spezifizieren. Merke: C ist und war nie eine Möglichkeit, unter einem
bestimmten Betriebssystem auf einer bestimmten Plattform eine bestimmte
Aufgabe zu erfüllen. C ist eine portable Sprache die nur ganz bestimmte
Dinge festlegt, um möglichst portabel zu sein. Sogar portabler als Java,
.NET & Co. Dies ist frustrierend wenn man "nur" eine bestimmte Low Level
Operation erledigen will, wie einen Speicher Test, aber so ist es eben
konzipiert.
Dr. Sommer schrieb:> Heiko L. schrieb:>> Das ist imho Teil des Problems.>> Kernel und Betriebssysteme sind zu unterschiedlich als dass es sinnvoll> wäre diese oder ihre Abwesenheit (im Falle von Embedded Systemen oder so> so etwas wie DOS, was keinen richtigen Kernel hat) im C Standard mit zu> spezifizieren. Merke: C ist und war nie eine Möglichkeit, unter einem> bestimmten Betriebssystem auf einer bestimmten Plattform eine bestimmte> Aufgabe zu erfüllen. C ist eine portable Sprache die nur ganz bestimmte> Dinge festlegt, um möglichst portabel zu sein. Sogar portabler als Java,> .NET & Co. Dies ist frustrierend wenn man "nur" eine bestimmte Low Level> Operation erledigen will, wie einen Speicher Test, aber so ist es eben> konzipiert.
Ja, und portabel deklariert man Programmspeicher in einer entsprechenden
Variable und nicht mit einem Zeiger auf 532.
Heiko L. schrieb:> sondern davon, wie es wirklich ist.
Dinge auf einer konkreten Plattform zu verwenden wir sie wirklich sind,
geht in C einfach nicht konform. Dafür braucht es Assembler oder die
alten unstandardisierten Varianten von Basic, Pascal & Co. In C
programmiert man genau wie in Java für eine abstrakte Plattform.
Dr. Sommer schrieb:> Heiko L. schrieb:>> sondern davon, wie es wirklich ist.>> Dinge auf einer konkreten Plattform zu verwenden wir sie wirklich sind,> geht in C einfach nicht konform. Dafür braucht es Assembler oder die> alten unstandardisierten Varianten von Basic, Pascal & Co. In C> programmiert man genau wie in Java für eine abstrakte Plattform.
"In C" ist schon der Fehler. Besser man räumt gleich die
Unvollständigkeit des Systems ein als dass man auf dessen Widerspruch
zusteuert.
Heiko L. schrieb:> Bernd K. schrieb:>> Nirgends im Sprachstandard wird irgendwas von dem garantiert was Du Dir>> da die ganze Zeit aus dem Finger saugst>> Ich rede ja auch nicht von nicht-existenten abstrakten Maschinen,> sondern davon, wie es wirklich ist.
Und ich rede davon wie es auch morgen noch sein wird, nicht von Zufällen
die heute noch aus einer Laune heraus existieren und im nächsten
Compiler vielleicht schon nicht mehr.
Bernd K. schrieb:> Heiko L. schrieb:>> Bernd K. schrieb:>>> Nirgends im Sprachstandard wird irgendwas von dem garantiert was Du Dir>>> da die ganze Zeit aus dem Finger saugst>>>> Ich rede ja auch nicht von nicht-existenten abstrakten Maschinen,>> sondern davon, wie es wirklich ist.>> Und ich rede davon wie es auch morgen noch sein wird, nicht von Zufällen> die heute noch aus einer Laune heraus existieren und im nächsten> Compiler vielleicht schon nicht mehr.
Och, normative Prozesse können in die eine oder andere Richtung gehen.
Sehr praktisch ist zB der gcc-switch für 8-bit integer.
Heiko L. schrieb:> "In C" ist schon der Fehler. Besser man räumt gleich die> Unvollständigkeit des Systems ein
Hä? C war nie dafür gedacht vollständig alles zu beherrschen was die
Plattform kann. Es ist eine Turing-Vollständige effiziente portable(!)
Sprache, mit bestimmten definierten IO-Möglichkeiten, die den größten
gemeinsamen Nenner der allermeisten Plattformen kennt, aber mehr auch
nicht. Bspw ist nichtmal der Minimum-Wert von "int" fix vorgegeben.
Dr. Sommer schrieb:> Dinge auf einer konkreten Plattform zu verwenden wir sie wirklich sind,> geht in C einfach nicht konform.
Das geht schon. Man muss nur verstanden haben wie die Sprachmittel
einzusetzen sind und was sie bedeuten um das gewünschte Verhalten zu
garantieren.
Bernd K. schrieb:> Das geht schon
Aber nicht indem man nur wohldefiniertes Verhalten des Standards nutzt.
Oder wie willst du einen Kontext-wechsel im Kernel programmieren...
Dr. Sommer schrieb:> Bernd K. schrieb:>> Das geht schon>> Aber nicht indem man nur wohldefiniertes Verhalten des Standards nutzt.> Oder wie willst du einen Kontext-wechsel im Kernel programmieren...
Gut, ok, ein paar Fetzen Assember braucht man hin und wieder.
Aber die meiste übrige Peripherie ist so konstruiert daß man sie
problemlos mit C benutzen kann.
Dr. Sommer schrieb:> Bspw ist nichtmal der Minimum-Wert von "int" fix vorgegeben.
Tja, aber das minimale Maximum mussten die Herren Spezialisten ja
festlegen. Ich bezweifle, dass das im Sinne der Erfinder war.
Q: "Was ist die naheliegenste Größe auf 8-bit Systemen für den Typ, der
per Default von allen nicht deklarierten Funktionen zurückgegeben wird
und den implizit alle Zwischenergebnisse annehmen, es sei denn einer der
Operanden hat einen größeren(!)?"
A: "16-bit natürlich."
"Ko-r-r-ekt."
Bernd K. schrieb:> Aber die meiste übrige Peripherie ist so konstruiert daß man sie> problemlos mit C benutzen kann.
Klar, das ist ja Absicht. Aber auch das ist natürlich nicht
wohldefiniert, funktioniert aber trotzdem.
Dr. Sommer schrieb:> Bernd K. schrieb:>> Aber die meiste übrige Peripherie ist so konstruiert daß man sie>> problemlos mit C benutzen kann.>> Klar, das ist ja Absicht. Aber auch das ist natürlich nicht> wohldefiniert
Zum Beispiel?
Heiko L. schrieb:> A: "16-bit natürlich."
Tja, 8-Bit-Systeme waren damals schon out! Die Integer Rechen Regeln in
C sind ziemlich schlimm, insbesondere in C++ welches die erbt und dann
in template-Kontexten für sehr viel Spaß sorgt.
Bernd K. schrieb:> Zum Beispiel?
Wo im C-standard steht, dass das Schreiben von 1 auf die Adresse
0x40000000 den Takt für das GPIOA-Modul aktiviert? Wo steht, dass ein
Zugriff ala
Heiko L. schrieb:> Bitte was? Programm 1 schreibt den Wert.> Nichts weiter läuft.> Programme 2 und 3 sollen den Wert 5 Minuten später lesen.
Davon wissen die aber nichts.
> Es wäre paradox und höchst amüsant, um einen konstanten Wert auszulesen,> diesen im Programm als "volatile" deklarieren zu müssen.
Das wäre überhaupt nicht paradox sondern völlig logisch. Genau dafür ist
volatile da. In der Praxis sind Programm 2 und 3 gerne auch Interrupts.
Vergessenes volatile führt erwartunggemäß dazu, daß das Programm ohne
Optimierungen noch geht, aber mit Optimierung aktiviert eben nicht mehr.
Sehr beliebter Anfängerfehler.
Btw., Free Pascal kennt kein volatile und behandelt alle Variablen als
volatile. Die Folge ist, daß globale Variablen dauernd weggeschrieben
werden müssen, während der Compiler das in C nicht unbedingt tut.
Performance-Gewinn.
Dr. Sommer schrieb:> Wo im C-standard steht, dass das Schreiben von 1 auf die Adresse> 0x40000000 den Takt für das GPIOA-Modul aktiviert?
Was hätte das im C-Standard zu suchen? Du musst eine Ausgabe machen die
ein anderes Gerät wiederum als Eingabe akzeptiert. Wie man in C
standardkonform eine Ausgabe über Speicherzugriffe macht haben wir ja
jetzt breitgetreten. Was das andere Gerät damit dann macht steht in der
Dokumentation des betreffenden Gerätes.
> Wo steht, dass ein Zugriff ala> *((volatile uint32_t*) 0x400000000) = 1;> überhaupt definiertes Verhalten hat?
Daß der Integer sich in einen Pointer casten lässt der dann auf die
richtige Adresse zeigt oder worauf willst Du hinaus?
Nop schrieb:> Davon wissen die aber nichts.
Wovon? Dass sie den Wert lesen sollen? Das habe ich doch aber
geschrieben.
Nop schrieb:> In der Praxis sind Programm 2 und 3 gerne auch Interrupts.
Nein. Es ist ein großer Unterschied, ob man selbst schon in die Zelle
geschrieben hat oder nicht. Ein "good guess" des Compilers, dass der
Wert im Register noch stimmt ist eine Sache - von einem Wert, von dem
man weiß, dass man ihn noch nicht kennen kann einfach irgendetwas
anzunehmen, eine andere.
Es ist nicht alles dieses eine C-Programm. Das sollte die Sprache imho
nicht zu verdrängen suchen.
Insbesondere sollte die Grundannahme eines irgendwann gestarteten
C-Programms, bevor es auch nur ein byte Speicher gelesen hat, nicht sein
"Ich weiß alles über das reale System auf dem ich laufe." sondern "Ich
weiß nichts."
Heiko L. schrieb:> Es wäre paradox und höchst amüsant, um einen konstanten Wert auszulesen,> diesen im Programm als "volatile" deklarieren zu müssen.
Ernsthaft? Du hängst Dich daran auf welchen Namen man dem Schlüsselwort
"volatile" gegeben hat? Nur weil es in Deinem einen Beispiel zufällig
konstant ist? Es bezeichnet "Eingabe/Ausgabe" und als solches ist es
sehr wohl flüchtig denn der Compiler muss es so behandeln als ob es sich
jederzeit ändern könnte und bei jedem Einlesen einen anderen (zum
Beispiel den nächsten aus einer Queue) Wert annehmen könnte.
Heiko L. schrieb:> Bitte was? Programm 1 schreibt den Wert.
Wohin? Hoffentlich schreibt es in eine offiziell als Ausgabe deklarierte
Speicherstelle oder anderen standardmäßigen Ausgabekanal (z.B. Rückgabe
von main() wäre auch als offizielle Ausgabe erlaubt). Ansonsten schreibt
es eventuell gar nichts.
> Nichts weiter läuft.> Programme 2 und 3 sollen den Wert 5 Minuten später lesen.
Woher? Hoffentlich von einer offiziell als Eingabe deklarierten
Speicherstelle oder anderen standardmäßigen Eingabekanal, ansonsten hast
Du undefiniertes Verhalten.
Bernd K. schrieb:> Ernsthaft? Du hängst Dich daran auf welchen Namen man dem Schlüsselwort> "volatile" gegeben hat?
Da der Wert ja nur einmal gelesen wird, sollte er eigentlich als const
deklariert werden :D:D:D:D
Heiko L. schrieb:> Da der Wert ja nur einmal gelesen wird, sollte er eigentlich als const> deklariert werden :D:D:D:D
Dann ist es keine Eingabe sondern Teil des Programms.
Es gibt aber auch const volatile. Das wäre für reine Eingaben ohne
Ausgabe zu gebrauchen, hat aber nicht zu bedeuten daß es sich nicht
ändern kann! Ja die Namensgebung führt zu seltsamen Ausdrücken, daher
auch die vielen Mißverständnisse, nichtsdestotrotz ist es so vorgesehen
und funktioniert sauber und portabel.
Heiko L. schrieb:> Es ist nicht alles dieses eine C-Programm. Das sollte die Sprache imho> nicht zu verdrängen suchen.
Ein C-Programm läuft NICHT auf physikalischer Hardware, sondern in einer
abstrakten C-Maschine. Und ja, da IST es das einzige Programm. Wenn Du
mit externen Sachen interfacen willst, gibt es dafür u.a. volatile.
> Insbesondere sollte die Grundannahme eines irgendwann gestarteten> C-Programms, bevor es auch nur ein byte Speicher gelesen hat, nicht sein> "Ich weiß alles über das reale System auf dem ich laufe."
Doch, genau das sollte es. Das ermöglicht eben erhebliche Optimierungen.
Wenn der Programmierer ausnahmsweise abweichen will, hat er u.a.
volatile. Das ist auch genau richtig herum so, weil volatile die
Ausnahme und nicht die Regel ist.
Wenn Du mit externen Sachen interfacen willst, aber Dich nicht mit
volatile anfreunden kannst, ist eine Sprache wie C für Dich nicht
geeignet. Dann nimm einfach z.B. Free Pascal. Ist zwar langsamer, weil
etliche Optimierungen nicht gehen, aber es wird Dir besser gefallen.
Nop schrieb:> Heiko L. schrieb:>>> Wenn x const ist>> Du hast sichtlich nicht verstanden, was "const" überhaupt bedeutet. Es> bedeutet NICHT "Konstante".
Ja... doch... ist ja nicht volatile, oder?
Bernd K. schrieb:> Wie man in C standardkonform eine Ausgabe über Speicherzugriffe macht> haben wir ja jetzt breitgetreten.
So ganz überzeugt bin ich nicht, dass C es wohldefiniert erlaubt an eine
beliebige Speicher Stelle zu schreiben, d.h. einen Pointer zu
dereferenzieren, der nicht auf ein "normal" angelegtes Objekt verweist.
C kennt ja streng genommen nicht einmal das Konzept von Adressen - ein
Pointer könnte auch eine Referenznummer wie eine Java-Referenz enthalten
statt einer Speicher-adresse.
Heiko L. schrieb:> Ja... doch... ist ja nicht volatile, oder?
"const" heißt nur "diese Variable wird nicht von innerhalb des
C-Programmes beschrieben". Deswegen wäre es auch zulässig, wenn bei den
printfs zweimal verschiedene Werte rauskämen. Undefiniert heißt eben
genau DAS.
Ebenso wie es zulässig wäre, die komplette Funktion wegzuoptimieren, und
auch jeden Codepfad, in dem sie aufgerufen wird.
C ist von der Grundphilosophie her keine freundliche Programmiersprache,
sondern hat die Grundannahme, daß der Programmierer weiß, was er tut.
Dazu zählt übrigens auch, daß er den C-Standard kennt.