Forum: PC-Programmierung memtest.c - sollte Schreib/Lesezeiger nicht volatile sein?


von Bernd K. (prof7bit)


Lesenswert?

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
von Peter (Gast)


Lesenswert?

Jup, hast recht.

Bei welcher Optimierungsstufe hast du compiliert?

von Nop (Gast)


Lesenswert?

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.

von Heiko L. (zer0)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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.

von udok (Gast)


Lesenswert?

Der Code ist OK, da braucht es kein "volatile".
Es gibt ja Seiteneffekte...

von Bernd K. (prof7bit)


Lesenswert?

udok schrieb:
> Es gibt ja Seiteneffekte...

Ich sehe keine. Zeigst Du sie mir?

von Bernd K. (prof7bit)


Lesenswert?

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
von Udo K. (Gast)


Lesenswert?

Bernd K. schrieb:
> Ich sehe keine. Zeigst Du sie mir?

Zeile 59

von Bernd K. (prof7bit)


Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

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.

von Manchmal-Kernel-Hacker (Gast)


Lesenswert?

"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.

von Udo K. (Gast)


Lesenswert?

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?

von Bernd K. (prof7bit)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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
von Nop (Gast)


Lesenswert?

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.

von Udo K. (Gast)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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.

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


Lesenswert?

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?

von Bernd K. (prof7bit)


Lesenswert?

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
von mh (Gast)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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.

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


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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
von Heiko L. (zer0)


Lesenswert?

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
von Bernd K. (prof7bit)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Heiko L. (zer0)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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?

von Bernd K. (prof7bit)


Lesenswert?

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
von Oliver S. (oliverso)


Lesenswert?

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

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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
von Bernd K. (prof7bit)


Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

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)-

von Udo K. (Gast)


Lesenswert?

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

von Nop (Gast)


Lesenswert?

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.

von Udo K. (Gast)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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.

von Udo K. (Gast)


Lesenswert?

@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 :-)

von Nop (Gast)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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?

von Nop (Gast)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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.

von Udo K. (Gast)


Lesenswert?

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?

von Udo K. (Gast)


Lesenswert?

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...

von Nop (Gast)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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?

von Udo K. (Gast)


Lesenswert?

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?

von Nop (Gast)


Lesenswert?

Udo K. schrieb:
> Gähn, ist ja schlimmer mit euch als ich dachte.

[Laiengebrabbel gelöscht]

Oh mann.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Heiko L. (zer0)


Lesenswert?

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?

von Heiko L. (zer0)


Lesenswert?

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?

von Nop (Gast)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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.

von Heiko L. (zer0)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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?

von Heiko L. (zer0)


Lesenswert?

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
von Nop (Gast)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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.

von Heiko L. (zer0)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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
von Heiko L. (zer0)


Lesenswert?

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
von Heiko L. (zer0)


Lesenswert?

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
von Bernd K. (prof7bit)


Lesenswert?

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
von Heiko L. (zer0)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Heiko L. (zer0)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Heiko L. (zer0)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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.

von Heiko L. (zer0)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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...

von Bernd K. (prof7bit)


Lesenswert?

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.

von Heiko L. (zer0)


Lesenswert?

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."

von Dr. Sommer (Gast)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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?

von Dr. Sommer (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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?

von Nop (Gast)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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
von Heiko L. (zer0)


Lesenswert?

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."

von Bernd K. (prof7bit)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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
von Heiko L. (zer0)


Lesenswert?

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

von Bernd K. (prof7bit)


Lesenswert?

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.

von Heiko L. (zer0)


Lesenswert?

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. ;)

von Nop (Gast)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

Heiko L. schrieb:

> Wenn x const ist

Du hast sichtlich nicht verstanden, was "const" überhaupt bedeutet. Es 
bedeutet NICHT "Konstante".

von Heiko L. (zer0)


Lesenswert?

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?

von Dr. Sommer (Gast)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.