Forum: Mikrocontroller und Digitale Elektronik Pointer auf 0x0000


von flex (Gast)


Lesenswert?

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.

von Achim (Gast)


Lesenswert?

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.

von S. Landolt (Gast)


Lesenswert?

Es wird r0 gelesen.

Quellangabe für "nicht adressierbar" und "ungültig"?

von Peter II (Gast)


Lesenswert?

Ram fängt bei 0 an. 0 ist auf einen µC eine gültige Adresse wie jede 
andere auch. Das das ganze bei C etwas unhandlich ist, ist ein andere 
Thema.

von Guest (Gast)


Lesenswert?

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.

von S. Landolt (Gast)


Angehängte Dateien:

Lesenswert?

Hier die entsprechende Seite aus dem Instruction Set Manual.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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

von Peter II (Gast)


Lesenswert?

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.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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
 LD tmp3, Y
2
 TST YH  ;Test if next is 0

 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.

von Dr. Sommer (Gast)


Lesenswert?

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!

von Dr. Sommer (Gast)


Lesenswert?

PS: Hier ists erklärt:
http://c-faq.com/null/machnon0.html

von Peter II (Gast)


Lesenswert?

Dr. Sommer schrieb:
> PS: Hier ists erklärt:
> http://c-faq.com/null/machnon0.html

Danke. Aber was passiert bei
1
int* x = (int*)2;
2
int* y = x-2;

von Nop (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Sebastian E. (sbe_15)


Lesenswert?

Vermutlich nicht das, was du jetzt erwartest:
1
int* x = (int*)2;
2
int* y = x-2;
ist identisch zu
1
int* x = (int*)2;
2
int* y = (int*)((int)x - 2*sizeof(int));
Siehe: Skalierte Zeigerarithmetik (oder wie man das schreibt).

von Nop (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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
5
or function.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Sebastian E. schrieb:
Dr. Sommer schrieb:
Peter II schrieb:
Nop schrieb:

 Und das hat mit seiner Frage was zu tun ?

 Ganz genau, gar nichts.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Peter II (Gast)


Lesenswert?

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?

von (prx) A. K. (prx)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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:
1
volatile uint32_t *x = (volatile uint32_t*) 42;

von Dr. Sommer (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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:
1
  TST YH  ;Test if next is 0
2
  BRNE yyy
3
  MOV tmp3, tmp2
4
yyy:
5
  ...
 Aber warum das Ganze ?

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Und an die Experten, welche die ganze Zeit am Thema vorbeireden und
 diesen Thread zumüllen:

 Macht ein neues Thread auf !!

 Oder ganz einfach aufhören.

: Bearbeitet durch User
von Steffen R. (steffen_rose)


Lesenswert?

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.

von flex (Gast)


Lesenswert?

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.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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:
1
  TST YH            ;Testen ob Adresse >= 0x0100
2
  BRNE yyy
3
  TST YL            ;Testen ob Adresse == 0
4
  BRNE yyy
5
  MOV tmp3, tmp2
6
 yyy:
7
  ...

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

: Bearbeitet durch Moderator
von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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.

: Bearbeitet durch User
von Ralph (Gast)


Lesenswert?

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.

von Peter II (Gast)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
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.