Zeiger Intermezzo

Hallo,

warum zur Hölle mault mich der Compiler voll?
Wollte schnell mal was probieren und nun verstehe ich die Welt nicht mehr.
Runtergebrochen bleibt folgender Code übrig.
Es soll doch nur der Wert 7654 in die Adresse geschrieben werden auf die ptr zeigt.

In CodeBlocks (das vom Buch) gibts keine Fehler, ebenfalls C++17.

avr-gcc\7.3.0-atmel3.6.1-arduino4/bin/avr-g++" -c -g -Os -w -std=gnu++17

Zeiger_Intermezzo:5:6: error: expected constructor, destructor, or type conversion before '=' token

*ptr = 7654;

exit status 1
expected constructor, destructor, or type conversion before '=' token

int *ptr = nullptr;  
               
*ptr = 7654;

void setup(void) {

}

void loop(void) {
  
}

*ptr = 7654;

Problem 1 (ist dir evtl noch gar nicht aufgefallen)
Du hast zwar einen Pointer definiert, aber nicht das reserviert, worauf er zeigt.

Problem 2 (ist dir gerade auf die Füße gekracht)
Außerhalb von Funktionen/Methoden existiert keine Möglichkeit der Zuweisung.
(Zuweisungen sind nur in Anweisungsblöcken erlaubt/sinnvoll.)

Hallo,

ich weiß ungefähr was du mir sagen möchtest, verstehe es jedoch noch nicht ganz.
Im setup Block kann ich den Wert dem Zeiger zuweisen.
Bezieht sich das Problem nur auf Zeiger?
Denn ich kann doch jede beliebige Variable außerhalb eines Blocks initialisieren.
Nur einen Zeiger demzufolge nicht?

int a = 7654;    // okay
int *p = 7654;   // Fehler

zu deinem (1)
Der deklarierte Zeiger zeigt auf eine Adresse die für einen int Wert reserviert ist.
Die Adresse auf die er zeigt kann mir egal sein, muss ich nicht unbedingt wissen.
Aus meiner Sicht ist die Adresse für den Zeiger reserviert.

Noch eine Korrektur. In CodeBlocks klappt das natürlich auch nicht. Hier meckert der Compiler ebenfalls. Hatte das in geistiger Umnachtung "dummwerweise" in den main Block geschrieben und nicht außerhalb des Blocks.

Denn ich kann doch jede beliebige Variable außerhalb eines Blocks initialisieren.

Initialisieren ja. Weil das direkt mit der Definition gekoppelt ist. Das ist aber was anderes als reine reguläre Zuweisung. Du solltest inzwischen schon gemerkt haben dass Initialisierung eine Art Sonderfall ist und eigene Regeln hat

Du kannst global eine Zeiger-Variable definieren und dieser dann in einem Block etwas zuweisen

int *p = 7654; // Fehler

Das ist (theoretisch) ein Zeiger der auf die Adresse 7654 zeigt

Doc_Arduino:
Denn ich kann doch jede beliebige Variable außerhalb eines Blocks initialisieren.
Nur einen Zeiger demzufolge nicht?

Das was Du geschrieben hast, ist aber keine Initialisierung, sondern eine Zuweisung.

int *ptr = nullptr; // Initialisierung
*ptr = 7654;        // Zuweisung

Einfach einen Zeiger anlegen reserviert noch keinen Speicher für den Inhalt, sondern nur für den Zeiger.

int a;              // Speicher für ein int namens a reservieren
int *ptr = &a; //  Speicher für einen Zeiger reservieren und diesen auf a zeigen lassen

void setup() {
  *ptr = 7654; // in a steht jetzt 7654
}

Gruß Tommy

int a = 7654;    // okay

int *p = 7654;   // Fehler

Die Zeigerinitialisierung ist kein Fehler!
Aber dennoch tut es nicht das was du beabsichtigst.
Ist somit logisch falsch.
Es wirft allerdings eine Warnung, welche man auch beachten muss.

Du initialisierst den Zeiger so dass er auf die Adresse 7654 zeigt.
Und das sagt dir auch die Warnung.

int *ptr = nullptr; // Initialisierung

*ptr = 7654;        // Zuweisung

Hier die Alternativschreibweise für Initialisierungen:

int a {7654};        // Initialisierung
int *ptr {nullptr};  // Initialisierung
int *p {(int*)7654}; // Initialisierung
int *p {(int*)7654}; // richtet den Pointer auf die Adresse 7654

zu deinem (1)
Der deklarierte Zeiger zeigt auf eine Adresse die für einen int Wert reserviert ist.
Die Adresse auf die er zeigt kann mir egal sein, muss ich nicht unbedingt wissen.
Aus meiner Sicht ist die Adresse für den Zeiger reserviert.

Naja...
Du richtest den Zeiger auf einen Bereich, worüber du keine Ahnung hast was da ist. :smiling_imp:
Und das ist dir dann auch noch egal :o
Somit nagelst du dir eine Frikadelle ans Knie. 8)

Denn ich kann doch jede beliebige Variable außerhalb eines Blocks initialisieren.
Nur einen Zeiger demzufolge nicht?

Doch. Hier sind Initialisierungen, die auch außerhalb einer Funktion gehen:

int a = 7654;
int* pa = &a;   // *pa ist 7654
int& ra = a; // Referenz. Wird aber üblicherweise nicht so, sondern eher bei Funktionsaufrufen verwendet.

Hallo,

das ist ganz schön viel auf einmal. Danke für die Aufmerksamkeit. Nochmal langsam zum mitmeißeln.

Irgendwas passt noch nicht zusammen. Jedenfalls bei mir. :confused:
Ihr sagt alle das hier ist eine Zuweisung einer Adresse! nicht eines Wertes?
int *p = 7654

Dann verstehe ich ehrlich gesagt nichts mehr.
Warum? Passt mal auf ... ich habe das Buch "Der C++ Programmierer" (aktuelle 5. Auflage) vor mir liegen.

Hier wird auf Seite 202 mit *ip = 100 dem Zeiger der Wert! 100 zugewiesen.
Es wird nicht gesagt das man dem Zeiger die Adresse 100 zuweist.

Auf Seite 203 wird ip = &i dem Zeiger ip die Adresse der Variablen i zugewiesen. Sodass der Zeiger den Wert von i hat. Genau das kann ich nachvollziehen und auch nicht erst seit heute.

Allerdings wird auf der gleichen Seite direkt darunter mit
i = 99
*ip =99
*ip2 = 99
gesagt das wäre alles das gleiche, also alle hätten den Wert 99.
Irgendwas kann jetzt nicht stimmen. Also entweder wird mit
*ip = 99 der Wert zugewiesen oder die wir ihr sagt die Adresse.

Irgendwas stimmt mit dem Dereferenzierer nicht?

Die Seiten lösche ich wieder wenn alles geklärt ist.

Hier wird auf Seite 202 mit *ip = 100 dem Zeiger der Wert! 100 zugewiesen.

Das ist auch was ganz anderes. Das dereferenziert den Zeiger und dann kannst du auch einen Wert zuweisen

Das andere ist die Definition eines Zeigers und dessen Initialisierung

Hier ist kein Dereferenzierer.

int *p; // Das ist eine Deklaration eines Zeigers auf ein int.
int *p = 7654; // Das ist auch eine Delaration eines Zeigers auf ein int und die Zuweisung von 7654 an diesen Zeiger.

Der * ist hier in beiden Fällen oben lediglich die Kennzeichnung als Zeiger.

int a;
p = &a; // Zuweisung der Adresse von a an p
*p = 1234; // hier kommt die Dereferenzierung

Weil es keine Deklaration ist, macht * hier die Dereferenzierung.

Leider wird für beides das gleiche Symbol benutzt, das kann zu Verwirrungen führen. Man muss also genau hinschauen, ob es eine Deklaration int *p oder eine Dereferenzierung *p ist.

Gruß Tommy

Hier wird auf Seite 202 mit *ip = 100 dem Zeiger der Wert! 100 zugewiesen.
Es wird nicht gesagt das man dem Zeiger die Adresse 100 zuweist.

Das ist eine Zuweisung!
Hier wird der Wert 100 an die Speicherstelle geschrieben, auf welche der Zeiger zeigt.

Ihr sagt alle das hier ist eine Zuweisung einer Adresse! nicht eines Wertes?
int *p = 7654

Nein, sagen wir nicht.
Wir sagen, das ist eine Initialisierung des Zeigers.
Der Zeiger wird mit dem Wert 7654 initialisiert
Danach zeigt der Zeiger auf die Adresse 7654.

int *p; // Zeiger definieren

int *p = (int*)7654; // Zeiger definieren und initialisieren


int *p {(int*)7654}; // Zeiger definieren und initialisieren


p = (int*)4567;  // ab jetzt zeigt der Zeiger auf eine neue Adresse

*p = 100; // der Wert 100 wird an die neue Adresse geschrieben

Definitionen mit Initialisierungen dürfen sowohl innerhalb, als auch außerhalb von Anweisungsblöcken auftauchen

Zuweisungen dürfen nur in Anweisungsblöcken auftauchen.

Tipp:
Unterscheide streng zwischen Initialisierung und Zuweisung.

Auch wenn diese durch das = ähnlich aussehen, ist es dennoch etwas grundverschiedenes

Genau so hier:

int wert = 2 * 3; // hier ist der * der Multiplikationsoperator
int *p; // hier ist der *  Teil des Datentype, es wird ein Zeiger definiert
*p = 100; // hier ist der * der Zeigerdereferenzierungsoperator

Es sind also 3 gänzlich unterschiedliche Sterne!

Hallo,

wir kommen dem schon näher. Die Formulierungen sind das Schwierigste.
Ist folgende Behauptung richtig?
"Bei einer Initialisierung eines Zeigers kann man diesem nur eine Adresse zuweisen, keinen Wert."

Jetzt habe ich weiter rumgespielt. Sind die Kommentare richtig?
Was nicht stimmt ist die Ausgabe der 345. Hier erhalte ich Sonderzeichen. ?
Sollte eigentlich funktionieren, da ich endlos den Inhalt der Adresse ändern kann solange alles int bleibt.

Und warum kann ich mir die Adresse des Zeigers nicht anzeigen lassen? Das muss doch auch gehen.

int var = 3456;         // Initialisierung  
int *p = (int*)7654;    // Initialisierung (Adresszuweisung) 

void setup (void)
{
  Serial.begin(9600);

  Serial.println(*p);     // gibt mir den Wert von der Adresse 7654 worauf 'p' zeigt 
  
  int *ptr = nullptr;     // sichere Initialisierung   
  Serial.println(*ptr);   // gibt mir den Wert von der Adresse worauf 'ptr' zufällig zeigt, nicht die Adresse selbst  

  // Dereferenzierer, * auf der linken Seite vom =
  
  *ptr = 123;             // ändert den Wert auf den 'ptr' zeigt auf 123
  Serial.println(*ptr); 

  *ptr = 345;             // ändert den Wert auf den 'ptr' zeigt auf 345
  Serial.println(*ptr);  

  ptr = &var;             // ändert die Adresse worauf 'ptr' zeigt auf die Adresse von 'var'
  Serial.println(*ptr);   // und zeigt damit den Wert von 'var' an

  Serial.println(&ptr);   // falsch, soll Adresse anzeigen auf die 'ptr' zeigt
}

void loop (void)
{ }

Das hatten wir Dir schon am Anfang gesagt: Dein Zeiger zeigt ins Blaue.
Dort wo er hin zeigt, ist kein Speicher für einen int reserviert.

int *ptr = nullptr;

*ptr = 123; // Du schreibst an eine beliebige Stelle im Speicher 123. Das kann zufällig funktionieren, muss aber nicht.

// richtiger Weg:
int *ptr = nullptr;
int zahl;  // Platz für einen int reservieren
ptr = &zahl; // ptr auf diesen int zeigen lassen
*ptr = 123; // jetzt kannst Du problemlos den Inhalt ändern

Für Zeiger gibt es keine Print-Routine. Da auf den kleinen Arduinos Zeiger aber in einem (unsigned ? sollte es sein, aber ist es das auch?) int gehalten werden, kannst Du sie Dir mit einem Typecast anzeigen lassen.

Gruß Tommy

Hähhh**?** Was mache ich anders wie du?

Abgesehen von der Zeigeradressausgabe.

Du schreibst 2x das Gleiche und sagst einmal falsch und einmal richtig. :o

Du machst:

  int *ptr = nullptr;     // sichere Initialisierung  
  
  *ptr = 123;             // ändert den Wert auf den 'ptr' zeigt auf 123 <<<---- ptr zeigt ins blaue

Ich mache:

int *ptr = nullptr;
int zahl;  // Platz für einen int reservieren   <<<------
ptr = &zahl; // ptr auf diesen int zeigen lassen  <<<------
*ptr = 123; // jetzt kannst Du problemlos den Inhalt ändern

Gruß Tommy

Die Wolken lichten sich. Ich formuliere es anders. Da ich den Zeiger mit nullptr initialisiere zeigt er ins Nichts.
An diese Adresse irgendeinen Wert schreiben geht schief. Korrekt?
Deswegen weist du erst die Adresse irgendeiner Variablen zu.
Ab dann kann man den Wert über den Zeiger der Variablen ändern. Korrekt?
Wenn ja, hab ichs langsam geschnallt.

Genau so, aber auch wenn Du den Zeiger nicht mit nullptr initialisiert hättest, wäre es so.
Die Initialisierung mit nullptr macht man, um im Ablauf prüfen zu können, ob der Zeiger auf etwas sinnvolles zeigt.

if (ptr != nullptr) {
  // Du kannst mit dem Pointer arbeiten
}

Gruß Tommy

Hallo,

jetzt ist mir vieles klarer, was ich bisher mit Zeigern wohl eher zufällig durch abgucken hinbekommen habe.
Alleine mit dem Buch habe ich das nie verstanden.
Ich Danke euch allen für die Geduld mit mir. Der Tag war erfolgreich. :slight_smile:

Zum Abschluss noch ein geänderter Sketch wo hoffentlich auch andere nachvollziehen können was mit dem Zeiger passiert.

/  https://forum.arduino.cc/index.php?topic=616496.0

int var = 3456;         // Initialisierung  

// Initialisierung mit Adresszuweisung, sollte man nicht machen, 
// man weiß nicht was in der Adresse steht und den Inhalt zufällig und ungewollt ändern
int *n = (int*)7654; 
int *p = nullptr;         // sichere Initialisierung, zeigt ins Nichts

void setup (void)
{
  Serial.begin(9600);

  Serial.print(F("Inhalt var: "));
  Serial.println(var);
    
  int *ptr = nullptr;     // sichere Initialisierung 
  Serial.print(F("zufaelliger Inhalt *ptr: "));  
  Serial.println(*ptr);   // gibt mir den Wert von der Adresse worauf 'ptr' zufällig zeigt, nicht die Adresse selbst  
  Serial.println();  
    
  ptr = &var;             // ändert die Adresse worauf 'ptr' zeigt auf die Adresse von 'var'
  Serial.println(F("Adresszuweisung &var an ptr. ")); 
  Serial.print(F("Inhalt *ptr: "));  
  Serial.println(*ptr);   // und zeigt damit den Wert von 'var' an
  
  // Dereferenzierer, * auf der linken Seite vom =

  Serial.println(F("Wertzuweisung '123' an Zeigeradresse von *ptr")); 
  *ptr = 123;             // ändert den Wert auf den 'ptr' zeigt auf 123
  Serial.print(F("Inhalt *ptr: ")); 
  Serial.println(*ptr); 
  Serial.print(F("Inhalt var:  ")); 
  Serial.println(var);  

  Serial.println(F("Wertzuweisung '345' an Zeigeradresse von *ptr"));  
  *ptr = 345;             // ändert den Wert auf den 'ptr' zeigt auf 345
  Serial.print(F("Inhalt *ptr: ")); 
  Serial.println(*ptr); 
  Serial.print(F("Inhalt var:  ")); 
  Serial.println(var);  
}

void loop (void)
{ }

Doc_Arduino:
Schade das man die Zeigeradresse nicht ausgeben kann, dann könnte man das im Terminal noch genauer nachvollziehen was mit den unterschiedlichen Zuweisungen passiert. Wäre zum debuggen hilfreich.

Das ist heute nicht Dein Tag?

Das hatte ich Dir in #12 geschrieben, wie das gehen sollte:

Serial.println((unsigned int)ptr,HEX);

Gruß Tommy

if (ptr) // ausreichend

{
 // Du kannst mit dem Pointer arbeiten
}

Da ich den Zeiger mit nullptr initialisiere zeigt er ins Nichts.
An diese Adresse irgendeinen Wert schreiben geht schief. Korrekt?

Nicht ganz!
nullptr entspricht NULL welches im Endeffekt der numerischen 0 entspricht.

Dein Pointer zeigt also auf die Adresse 0x0000
Wenn da Speicher ist, kannst du da auch rein schreiben.

Es ist also nicht korrekt, dass das automatisch schief geht!
Es geht nur schief, wenn es nicht das ist was du willst.

Manchmal kann es also sogar Sinn machen einen Zeiger mit 0 zu initialisieren und ihn dann auch so zu benutzen.
Selten, aber manchmal schon!
Zumindest hat der Compiler keine Einwände und folgt einem solchen Auftrag ohne Murren.