Hallo,
mein Y-Pointer zeigt in meinem Programm u.u. auf Adresse 0x0000 und
liest diese auch. Das wir später auch als Fehler erkannt und der
gelesene Wert korrigiert.
1
LD tmp3, Y
2
TST YH ;Test if next is 0
3
BREQ yyy
4
MOV tmp3, tmp2
5
RJMP yyy
Meine Frage ist nun, was theoretisch an dieser Adresse stehen könnte
(eigentlich sollte es R0 sein, aber dies ist über LD nicht adressierbar)
und ob das Lesen einer ungültigen Adresse zu Problemen im AVR führen
kann.
Der uC ist ein mega164.
PS: Ich weiß mit einem Befehl mehr könnte ich das Problem umgehen, aber
erstens möchte ich Platz sparen, zweitens ist es eine Frage aus
Interesse.
0 ist in C die default-Kennung eines ungültigen Pointers, von daher
sollte das im Programm abgefangen werden.
Was bei Deinem @0 steht, weiss ich nicht, siehe Handbuch.
Problematisch sind ungültige Pointer immer dann, wenn ein Lesezugriff
seiteneffekte hat, z.b. bei uart- oder Interruptregister.
Es gibt Cores wo der das RAM bei 0 anfängt (z.B. Renesas RX). Das kann
für ein C Programm problematisch sein wenn dort ein Objekt liegt und wir
einen Pointer darauf mit NULL vergleichen.
Ein einfacher Workaround in der Praxis ist einfach 4 Bytes zu
verschenken, d.h. im Linker File anzugeben das das RAM bei 0x04 anfängt.
flex schrieb:> Meine Frage ist nun, was theoretisch an dieser Adresse stehen könnte> (eigentlich sollte es R0 sein, aber dies ist über LD nicht adressierbar)
Doch. Wie S. Landolt bereits geschrieben hat, wird an der Datenadresse 0
der Inhalt von R0 gelesen. Da das Lesen eines Datenregisters keinen
Nebeneffekt hat, ist dein Code in Ordnung.
Achim schrieb:> 0 ist in C die default-Kennung eines ungültigen Pointers, von daher> sollte das im Programm abgefangen werden.
Ein weit verbreiteter Irrtum, denn es gibt da eine feine Unterscheidung:
Wenn man in C "int* x = 0;" schreibt, werden nicht notwendigerweise
auch tatsächlich nur 0 Bits in den Pointer geschrieben. Die
tatsächlichen Bits seines sog. Nullpointers sind Plattform abhängig, es
könnte zB auch 0xFFFFF o.ä. genutzt werden! In C selbst sieht man davon
aber nichts, der Compiler erkennt einen Nullpointer und führt die
tatsächliche Umwandlung durch. Allerdings ist auch mir kein Compiler
bekannt der Nullpointer nutzt die nicht 0 sind- bei Mikrocontrollen die
0 als gültige Adresse haben wäre das aber praktisch...
Dr. Sommer schrieb:> Wenn man in C "int* x = 0;" schreibt, werden nicht notwendigerweise> auch tatsächlich nur 0 Bits in den Pointer geschrieben.
sicher? Oder meinst du
1
int*x=null;
2
int*x=nullptr;
wenn man 0 schreibt, erwarte ich das auch 0 Bits gesetzt werden.
flex schrieb:> mein Y-Pointer zeigt in meinem Programm u.u. auf Adresse 0x0000 und> liest diese auch. Das wir später auch als Fehler erkannt und der> gelesene Wert korrigiert.
1
LDtmp3,Y
2
TSTYH;Testifnextis0
Was wird da getestet und woher willst du wissen wohin YL zeigt ?
flex schrieb:> Meine Frage ist nun, was theoretisch an dieser Adresse stehen könnte> (eigentlich sollte es R0 sein, aber dies ist über LD nicht adressierbar)
Theoretisch jeder Wert zwischen 0x00 und 0xFF.
> und ob das Lesen einer ungültigen Adresse zu Problemen im AVR führen> kann.
Diese Adresse ist genauso gültig wie jede andere auch.
Peter II schrieb:> Dr. Sommer schrieb:>> Wenn man in C "int* x = 0;" schreibt, werden nicht notwendigerweise>> auch tatsächlich nur 0 Bits in den Pointer geschrieben.>> sicher? Oder meinst du> int* x = null;
"null" gibts nicht in C; falls du NULL meinst, das ist in C auch nur 0
ggf. mit cast.
> int* x = nullptr;
Das ist C++, und dafür gilt das gleiche wie gesagt.
>> wenn man 0 schreibt, erwarte ich das auch 0 Bits gesetzt werden.
Dann ist deine Erwartung falsch ;-) ... Wenn du "int* x = 42;"
schreibst, erwartest du auch nicht dass 42 in den Pointer geschrieben
wird, sondern einen Compiler-Fhler. "int* x = 0;" ist eine
Spezial-Anweisung die sagt "schreibe einen Nullpointer-Wert in x", aber
ein Nullpointer-Wert ist eben nicht notwendigerweise 0!
Dr. Sommer schrieb:> Wenn du "int* x = 42;"> schreibst, erwartest du auch nicht dass 42 in den Pointer geschrieben> wird
Doch, ganz genau das erwarte ich, und so programmiere ich auch memory
mapped I/O.
Peter II schrieb:> Danke. Aber was passiert bei
Ein solcher cast ist nicht zulässig. Somit ist das Verhalten undefiniert
und hängt von der Plattform ab.
Bei Pointer + Zahl wird übrigens "Zahl" Elemente weiter gegangen, aber
nicht "Zahl" Bytes.
Peter II schrieb:> int* x = (int*)2;> int* y = x-2;
Je nach Plattform kann y u.U. den Wert 0 enthalten. Wenn int breiter als
16 bit ist, wird's nen wraparound geben, und y enthält dann einen recht
großen Wert, der davon abhängt, wie breit die Pointer denn sind.
Nop schrieb:> Doch, ganz genau das erwarte ich, und so programmiere ich auch memory> mapped I/O.
Schön, dann programmiert du nicht nach dem C Standard, dein Code ist
nicht konform und nicht portabel. Da Memory mapped IO onehin nicht
portabel ist ist das aber eh egal.
1) 0 ist nicht auf jedem µC eine gültige Adresse, auch wenn es solche
µC gibt.
2) C fordert nicht dass die Binärdarstellung von "null pointer" nur
aus Nullen besteht. Eine explizite Darstellung gibt der Standard nicht
vor. Ganz deutlich wird das aus einer Fußnote in 7.20.3:
1
The calloc function allocates space for an array of nmemb objects, each
2
of whose size is size. The space is initialized to all bits zero. 264)
3
4
264) Note that this need not be the same as the representation of
5
floating-point zero or a null pointer constant.
Ist zwar etwas blöde, dass nicht bereits an prominenterer Stelle
klargestellt wird, dass NULL nicht notwendig durch lauter 0en
dargestellt ist, ist aber so.
Wenn auf einer bestimmten Hardware 0 gültige Adresse ist, dann sollte
eine entsprechende C/C++ Implementation (vulgo: Compiler + Libs) also
eine andere Darstellung für null pointer wählen.
7.17 stddef.h sagt zu NULL:
1
.3 The macros are NULL which expands to an implementation-defined
2
null pointer constant [...]
Insbesondere verwirrend ist 6.3.2.3 Pointers:
1
An integer constant expression with the value 0, or such an expression
2
cast to type void *, is called a null pointer constant. If a null pointer
3
constant is converted to a pointer type, the resulting pointer, called a
4
null pointer, is guaranteed to compare unequal to a pointer to any object
Marc V. schrieb:> LD tmp3, Y> TST YH ;Test if next is 0>> Was wird da getestet und woher willst du wissen wohin YL zeigt ?
Ich glaube, der Code ist unvollständig und evtl. sogar falsch
abgeschrieben, denn ein BRNE statt des BREQ würde besser zur textuellen
Beschreibung passen.
Und für die, die es noch nicht gemerkt haben: Hier geht es um
Assemblerprogrammierung. Wie in C ein Null-Pointer behandelt wird, ist
hier völlig unerheblich.
Dr. Sommer schrieb:> Ein solcher cast ist nicht zulässig. Somit ist das Verhalten undefiniert> und hängt von der Plattform ab.> Bei Pointer + Zahl wird übrigens "Zahl" Elemente weiter gegangen, aber> nicht "Zahl" Bytes.
es ging ja nur darum, das man auch einen Zeiger auf Adresse 0 Erreichen
kann ohne das mit 0 reinschreibt.
Das kann der Compiler nicht erkennen, damit sollte es gar nicht möglich
sein das 0 auf einen andere wert als 0 gesetzt werden kann.
> Ein solcher cast ist nicht zulässig.
warum sollte so ein cast zu zulässig sein? Wie will man sonst eine
absolute Ram Adresse ansprechen?
Nop schrieb:> Doch, ganz genau das erwarte ich, und so programmiere ich auch memory> mapped I/O.
Dann aber bitte mit Cast, also
int *x = (int *)42;
Memory mapped I/O ist plattformabhängig, das interne Bitpattern vom
NULL-Pointer auch. Wenn also eine Plattform die Adresse 0 nicht für
NULL-Pointer nutzen kann, beispielsweise weil diese Adresse mitten im
vorzeichenbehafteten Adressraum liegt, dann wird ggf. ein anderes
Bitpattern dafür verwendet. Das sollte aber ein Programmierer auf dieser
Plattform dann auch wissen, denn seine I/O-Adressen kennt er ja auch.
Dr. Sommer schrieb:> Schön, dann programmiert du nicht nach dem C Standard, dein Code ist> nicht konform
Wie sollte man das sonst tun, wenn man eine Ausgabe an eine bestimmte
Adresse haben will? Inline-Assembler?
> Da Memory mapped IO onehin nicht portabel ist ist das aber eh egal.
Oder so herum. Klar sehe ich bei sowas das Problem, daß nicht alle
Plattformen einen linearen Adreßraum haben, und wenn man Pointer mit
Segmentspeicher hat, wird deren interne Darstellung wohl schon anders
aussehen.
A. K. schrieb:> Dann aber bitte mit Cast, also> int *x = (int *)42;
Ich wollte das jetzt nicht so detailiert machen, aber wenn wir schon
genau werden, dann mache ich das mit C99-Datentypen und volatile:
Peter II schrieb:> es ging ja nur darum, das man auch einen Zeiger auf Adresse 0 Erreichen> kann ohne das mit 0 reinschreibt.
Schon, aber nicht so wie im Beispiel ;-P
Peter II schrieb:> Das kann der Compiler nicht erkennen
Braucht er auch nicht, es ist nämlich verboten solchen Code zu
schreiben.
> damit sollte es gar nicht möglich> sein das 0 auf einen andere wert als 0 gesetzt werden kann.
Doch, wenn du kaputten Code schreibst, brauchst du dich nicht über
komisches Verhalten beschweren.
Peter II schrieb:> warum sollte so ein cast zu zulässig sein?
Weil das im C Standard so steht.C ist eine portable Sprache, und es wäre
ziemlich schwierig konsistentes Verhalten bei beliebigen Adresszugriffen
zu garantieren.
Peter II schrieb:> Wie will man sonst eine absolute Ram Adresse ansprechen?
Gar nicht. C erlaubt das nicht. Wenn du so etwas machst ist dein Code
nicht Standard konform und nicht portabel. Speicher wird über Variablen
Definition oder malloc angefordert, aber nicht durch Zugriff auf
irgendwelche Adressen. C definiert noch nicht einmal dass ein Pointer
eine Zahl/Adresse enthält; es ist lediglich irgendein abstraktes Ding
das Speicher identifiziert.
Nop schrieb:> Wie sollte man das sonst tun, wenn man eine Ausgabe an eine bestimmte> Adresse haben will? Inline-Assembler?>
Auch inline Assembler ist nicht im Standard definiert. An beliebige
Adressen zu schreiben ist schlicht und ergreifend nicht zulässig und
nicht definiert. Da man das aber auf Mikrocontroller Programmen braucht,
sind praktisch alle Mikrocontroller Programme falsch und funktionieren
nur zufällig weil die genutzte Version des Compilers sich so verhält wie
gewünscht.
Ich sage ja gar nicht dass man so nicht programmieren soll; man sollte
sich nur im Klaren darüber sein dass solcher Code von C nicht abgedeckt
ist und in portablen Programmen nichts zu suchen hat, und nicht alle
Eigenheiten (wie die Darstellung von Null Pointern) des genutzten
Compilers universelle Eigenschaften von C sind und sich auf keiner
Plattform unterscheiden.
Dr. Sommer schrieb:> sind praktisch alle Mikrocontroller Programme falsch
Ich würde es etwas weniger provokant formulieren: Sie entsprechen nicht
vollständig dem C Standard.
> und funktionieren nur zufällig
Implementation defined passt etwas besser, weil bei Compilern für
Mikrocontroller solche Methoden nicht wirklich dem Zufall entspringen.
Yalu X. schrieb:> Ich glaube, der Code ist unvollständig und evtl. sogar falsch> abgeschrieben, denn ein BRNE statt des BREQ würde besser zur textuellen> Beschreibung passen.
Ja.
Es sei denn, er will keine der Adressen von 0x00 bis 0xFF benutzen.
Dann ware es aber so besser:
Dr. Sommer schrieb:> Allerdings ist auch mir kein Compiler> bekannt der Nullpointer nutzt die nicht 0 sind- bei Mikrocontrollen die> 0 als gültige Adresse haben wäre das aber praktisch...
Keil C51 mit generic Pointern war hier nach meiner Erinnerung etwas
kniffelig. Speziell auch, wenn man diese mit Pointern auf spezifische
Speicherbereiche mischt.
Uiuiui, das geht hier ja ganz schön rund.
Also ein paar Erklärungen:
Es geht hier um Assembler.
Der Codeausschnitt ist aus einer Subroutine die in eine verkettete Liste
ein neues Element einfügt. Das Ende der Liste wird dadurch markiert,
dass im Zeiger auf das nächste Element die Adresse 0x0000 steht, ein
unmöglicher Wert da die Liste im RAM liegt, also ab Adresse 0x0100
aufwärts.
Der Fall Ende der Liste wird dadurch behandelt, das die Einsortierung
normal weiter läuft, ein nächste (nicht mehr vorhandenen) Element aber
nur virtuell generiert wird um der Routine zu suggerieren das sie die
richtige Stelle zum einsortieren gefunden hätte.
Da die "Fehlererkennung" nicht über den Wert an der Adresse, sondern
über die Adresse selber erfolgt, ist es vollkommen egal das ein falscher
Wert ausgelesen wird.
Die Anmerkung mit BRNE stimmt natürlich.
Mit der Adresse 0x0000 wird tatsächlich R0 aufgerufen, ich hatte das
vorher eigentlich getestete und keinen Testwert aus R0 auslesen können,
es lag aber an einem Programmierfehler meinerseits.
Danke für die Hilfe so weit.
flex schrieb:> Der Codeausschnitt ist aus einer Subroutine die in eine verkettete Liste> ein neues Element einfügt. Das Ende der Liste wird dadurch markiert,> dass im Zeiger auf das nächste Element die Adresse 0x0000 steht, ein> unmöglicher Wert da die Liste im RAM liegt, also ab Adresse 0x0100> aufwärts.
Mit der obigen Routine kannst du nur feststellen, ob die Adresse
grösser als 0x0100 ist, nicht aber ob die Adresse 0x0000 ist.
Deswegen:
Marc V. schrieb:> Mit der obigen Routine kannst du nur feststellen, ob die Adresse> grösser als 0x0100 ist, nicht aber ob die Adresse 0x0000 ist.
Da der RAM-Bereich erst bei 0x100 beginnt, ist das in seinem
Anwendungsfall auch ausreichend.
Bei der Portierung auf andere AVRs, bei denen das RAM schon bei 0x60
beginnt, muss man natürlich aufpassen.
Yalu X. schrieb:> Da der RAM-Bereich erst bei 0x100 beginnt, ist das in seinem> Anwendungsfall auch ausreichend.
Nein, glaube ich nicht.
flex schrieb:> ein neues Element einfügt. Das Ende der Liste wird dadurch markiert,> dass im Zeiger auf das nächste Element die Adresse 0x0000 steht, ein
Also muss er auf 0 prüfen.
Wieso?
Es gibt zwei Fälle zu unterscheiden:
1. Das aktuelle Listenelement ist nicht das letzte in der Liste:
Dann zeigt der Next-Pointer auf ein weiteres Element. Da dieses
ebenfalls im RAM liegt, ist dessen Adresse ≥0x100 und damit XH>0.
2. Das aktuelle Listenelement ist das letzte in der Liste:
Dann ist der Next-Pointer gleich 0x0000 und damit XH=0.
Der Next-Pointer nimmt nie Werte von 0x0001 bis 0x00FF an, da dies weder
gültige RAM-Adressen noch Listenenden sind. Deswegen genügt es, nur
dessen High-Byte abzuprüfen.
Yalu X. schrieb:> Wieso?
...
> Der Next-Pointer nimmt nie Werte von 0x0001 bis 0x00FF an, da dies weder> gültige RAM-Adressen noch Listenenden sind. Deswegen genügt es, nur> dessen High-Byte abzuprüfen.
Deswegen:
Yalu X. schrieb:> Bei der Portierung auf andere AVRs, bei denen das RAM schon bei 0x60> beginnt, muss man natürlich aufpassen.
Und damit man nicht wegen 2 Befehle mehr später Tagelang den Fehler
suchen muss.
Das einzige, was niemals vorkommen darf ist eine Adresse mit
High = 0x00 und Low = 0x00.
Alles andere darf und kann sich je nach Typ ändern.
Ralph schrieb:> Wo liegt denn beim AVR der Resetvektor ?> Viele µC haben den an Adresse 0x0000.>> Dann kann ein Zugriff auf diese Adresse einen Reset auslösen.
warum sollte ein lesender zugriff eine Reset auslösen? Er will nicht
dort hin springen.
Ralph schrieb:> Wo liegt denn beim AVR der Resetvektor ?> Viele µC haben den an Adresse 0x0000.
Beim AVR auch. Aber im Programm-Adressraum.
> Dann kann ein Zugriff auf diese Adresse einen Reset auslösen.
Ich kenne keinen µC, bei dem allein schon ein Zugriff auf die Adresse
des Resetvektors einen Reset auslöst. Eher schon ein Sprung dorthin,
aber auch das wäre nur ein halber Reset, nämlich einer ohne Reset der
Hardware.