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!
:
Bearbeitet durch User
Jup, hast recht. Bei welcher Optimierungsstufe hast du compiliert?
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.
Der Code ist OK, da braucht es kein "volatile". Es gibt ja Seiteneffekte...
Heiko L. schrieb: > Bei "seinem" Speicher träfe das sicher zu Es ist "sein" Speicher. Nirgends ist deklariert daß noch jemand anders drauf zugreift.
:
Bearbeitet durch User
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).
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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).
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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, int n); |
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 |
17 | waiting for input. |
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.
:
Bearbeitet durch User
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 | int main(void) { |
2 | u32* p = (u32*)0x1FFFFC00; |
3 | *p = 42; |
4 | if (*p != 42) { |
5 | return *p; |
6 | }
|
7 | return 0; |
8 | }
|
Und ist nachweislich äquivalent zu folgendem und darf entsprechend optimiert werden:
1 | int main(void) { |
2 | return 0; |
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?
Udo K. schrieb: > Gähn, ist ja schlimmer mit euch als ich dachte. [Laiengebrabbel gelöscht] Oh mann.
Udo K. schrieb: > Zeig mir doch mal den Code der durch irgendwelche aliasing > Reglen auch nur 1%% schneller wird.
1 | long foo (int* a, long* b) { |
2 | *a = *a + *b; |
3 | return *b; |
4 | }
|
Mit strict Aliasing Regel aktiviert:
1 | <foo>: |
2 | 0: 48 8b 06 mov (%rsi),%rax |
3 | 3: 01 07 add %eax,(%rdi) |
4 | 5: c3 retq |
Ohne strict aliasing (-fno-strict-aliasing):
1 | <foo>: |
2 | 0: 48 8b 06 mov (%rsi),%rax |
3 | 3: 01 07 add %eax,(%rdi) |
4 | 5: 48 8b 06 mov (%rsi),%rax |
5 | 8: c3 retq |
Sind also 33% mehr Instruktionen und 50% mehr Programmspeicher. Der Laufzeitunterschied dürfte dazwischen liegen.
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.
1 | static uint8_t all_the_memory[<Hier Speichergröße einsetzen>]; |
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?
Und wo ich darüber nachdenke: "undefined value" - gilt für die eigentlich der Satz der Identität? Muss also
1 | void f() { |
2 | int x; |
3 | printf("%d\n", x); |
4 | printf("%d\n", x); |
5 | } |
zweimal den selben Wert ausgeben? Nein, oder?
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.
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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
1 | *((volatile uint32_t*) 0x400000000) = 1; |
überhaupt definiertes Verhalten hat?
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?
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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.
Ooh... Das ist 'ne coole Erweiterung zu "undefined":
1 | void main() { |
2 | const int x; |
3 | printf("%d\n", x); |
4 | printf("%d\n", x); |
5 | } |
Wenn x const ist und sein Wert nicht definiert, müsste der Satz der Identität einerseits gelten andererseits aber auch nicht. ;)
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.
Heiko L. schrieb: > Wenn x const ist Du hast sichtlich nicht verstanden, was "const" überhaupt bedeutet. Es bedeutet NICHT "Konstante".
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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.