Zeiger / Speicheradresse (bin mir unsicher)

Guten Abend liebe Leute,

mal wieder ueber das Thema Zeiger/Speicheradresse einer Variable gestolpert.
Nur kurz zur Verifizierung... sind meine bisherigen Annahmen richtig?

Besten Dank Euch!
Grillgemuese :slight_smile:

void get_Zahl(int *dreibla)
{
  *dreibla = 13;
}

void setup() 
{
  Serial.begin(9600);
  while(!Serial){}

  int dreizehn;
  get_Zahl(&dreizehn);
  Serial.print(F("var (Variablen-Wert) = "));
  Serial.println(dreizehn);
  
  int zeiger = &dreizehn; //zeiger auf dreizehn
  Serial.print(F("&var (Speicheradresse) = "));
  Serial.println(zeiger);
  
  int *referenz = &dreizehn; //referenz auf dreizehn ?
  Serial.print(F("var (aus Referenz) = "));
  Serial.println(*referenz);
  
  Serial.print(F("Belegung (in Bytes) = "));
  Serial.println(sizeof(dreizehn));
}

void loop() 
{/*NICHTS*/}

Ausgabe:

var (Variablen-Wert) = 13
&var (Speicheradresse) = 2298
var (aus Referenz) = 13
Belegung (in Bytes) = 2
  int zeiger = &dreizehn; //zeiger auf dreizehn

Das ist mindestens unsauber.zeigerist kein Zeiger, sondern ein int.

 int* zeiger = &dreizehn;
  Serial.println((int)zeiger, HEX); // wir wissen, dass es ein Zeiger ist, wollen es aber absichtlich als int angezeigt bekommen
  Serial.println (*zeiger); // Ausgabe : 13
 int& referenz = dreizehn; //referenz auf die Variable dreizehn 
  Serial.print(F("var (aus Referenz) = "));
  Serial.println(referenz); // Ausgabe : 13

Eine Referenz kann nur bei Definition zugewiesen werden und wird verwendet wie die Variable selbst, z.B. in print und in der urspr√ľnglichen Definition. Macht so wie wir es hier verwenden, nicht ganz soviel Sinn.

void set13(int& target) { // hier wird eine Referenz √ľbergeben
   target = 13;
}

void put12(int* target) { // hier wird ein Zeiger √ľbergeben
   *target = 12;   
}

void setup() {
   Serial.begin(9600);
   int foo;
¬†  set13(foo);¬† // wir √ľbergeben eine Referenz, weil die Funktion so definiert ist
   Serial.println(foo); // Ausgabe: 13
¬†  put12(&foo);¬† // wir √ľbergeben die Adresse von foo, also einen Zeiger
  Serial.println(foo); // Ausgabe: 12
}

Keine Ahnung ob das hilft oder verwirrt :wink:

(Der compiler spuckte zumindestens keine Warnung raus)

Hier muss man zwischen C und C++ unterscheiden.

Ein Zeiger hat folgende Aspekte:

  1. Er hat einen Namen, einen Bezeichner.
  2. Er beinhaltet die Adresse, auf welche er zeigt.
  3. Er trägt den Datentype in sich, auf den er zeigt.

Die √ľbliche Zuweisungskompatibilit√§t f√ľr Datentypen (implizite Casts) wird auch von Zeigern erf√ľllt.
In diesen Fällen erfolgt keine Meldung.

Die ernsten Unterschiede fangen bei Arrays an.
In C++ unter scheiden sich die Datentypen von einem Array mit 10 Elementen von einem Array mit 8 Elementen. Klarer: Die Anzahl der Elemente ist teil des Datentyps.
Dieses subtile Feature verhindert sehr viele böse Zeigerfehlverwendungen.

bereichsbegrenzten Datentypen

Keine Ahnung, was das bedeuten soll....

Belegung (in Bytes) = 6

Das ist nat√ľrlich nur bedingt richtig, da davon nicht der dynamisch reservierte Speicher mit erfasst wird.

Liege ich denn nun richtig mit der Annahme, dass der Datentyp eines Zeigers (mit *) sowie einer Referenz gleich der Variable sein muss? (Der compiler spuckte zumindestens keine Warnung raus)

Vermutlich ja.

Eine Referenz ohne speziellen Datentyp, der referenziert wird, kann es nicht geben. Auch keine Referenz auf nichts, oder eine, deren Ziel sich im Programm-Ablauf √§ndert. (Das sind all die ‚ÄúSchweinereien‚ÄĚ, die man mit Zeigern machen kann und die gern zu Problemen f√ľhren)

Auch Zeiger sollte man immer als typisierte Zeiger denken (auch wenn es formal einen void* gibt).

int * xp; kann man auf zwei Arten lesen und verstehen:
a) xp ist vom Datentyp int* ( ein Zeiger auf int )
b) *xp ist ein int

Nat√ľrlich gibt es Zeiger auf alles, auch auf andere Zeiger, Objekte, oder Arrays.
Wobei Array und Zeiger bei C/C++ sehr eng verwoben sind.

Referenzen machen haupts√§chlich Sinn bei der √úbergabe von Parametern an eine Funktion, wenn man der keine eigene Kopie eines Werts geben will, sondern direkten Zugriff auf die interessierende Variable selbst. ( Weil diese ein gro√ües Objekt ist, oder weil sie ge√§ndert werden soll ) Dieser Parameter kann dann mit der einfachen Syntax behandelt werden, ohne mit Sternchen oder ‚Üí hantieren zu m√ľssen.

Viel Spaß noch :wink:


In C++ unter scheiden sich die Datentypen von einem Array mit 10 Elementen von einem Array mit 8 Elementen. Klarer: Die Anzahl der Elemente ist teil des Datentyps.
Dieses subtile Feature verhindert sehr viele böse Zeigerfehlverwendungen.

Lass dich nicht verwirren: Die ‚ÄúArduino-Sprache‚ÄĚ ist zwar C++, aber das merkt der avr-gcc Compiler nichtselten, und solche Fehler werden zur Laufzeit locker falsch ausgef√ľhrt.

int pre[10];
int test[10];
int post[10];
void setup() {
  for (int i = 0; i < 10; i++)
     test[i] = i; 

  test[10] = 10;  // nicht mal eine Warnung mit arduino 1.8.3
  test[11] = 11;
  Serial.begin(9600);

  for (int i = 0; i < 10; i++) {
    Serial.print(pre[i]); Serial.print("\t");
    Serial.print(test[i]);
    Serial.print("\t");Serial.println(post[i]);
  }
}

void loop() { }

Lass dich nicht verwirren: Die "Arduino-Sprache" ist zwar C++, aber das merkt der avr-gcc Compiler nichtselten, und solche Fehler werden zur Laufzeit locker falsch ausgef√ľhrt.

Verwirren ist nicht mein Ziel!
Auch wenn du das glaubst.

Und zusätzlich:
Ebenso wie C hat C++ keinen Schutz vor Bereichs√ľberschreitungen eingebaut. Die Gr√ľnde daf√ľr liegen in der C Geschichte.
Eine Dumme Anwendung von Zeigern f√ľhrt in beiden Sprachen zu fatalen Fehlern.

Mit keinem Wort habe ich versucht dem C++ einen Schutz vor Bereichs√ľberschreitung ans Hemd zu kleben.

// ---

Ich versuche es klarer zu formulieren:

Wenn du in C einen Zeiger auf ein Array an eine Funktion √ľbergibst, dann musst du ihr auch die Anzahl Elemente √ľbergeben. Das ist dann eine doppelte Information, und damit ein zus√§tzlicher Fehlerquell.

Wenn du in C++ einen Zeiger, auf ein Array √ľbergibst, ist die Gr√∂√üe des Arrays Teil des Zeiger Typs. Ein Fehlerquell weniger. Sch√ľtzt aber nicht vor anderen Dummheiten.

Und das macht die Arduino Umgebung genau so gut, wie alle anderen C++ Umgebungen.

Wenn du in C einen Zeiger auf ein Array an eine Funktion √ľbergibst, dann musst du ihr auch die Anzahl Elemente √ľbergeben. Das ist dann eine doppelte Information, und damit ein zus√§tzlicher Fehlerquell.

Wenn du in C++ einen Zeiger, auf ein Array √ľbergibst, ist die Gr√∂√üe des Arrays Teil des Zeiger Typs. Ein Fehlerquell weniger. Sch√ľtzt aber nicht vor anderen Dummheiten.

Meinst du dies?

int fc ( char* data, byte len ) {
¬† // √ľbliche c - Praxis: L√§nge eines arrays mitgeben
  int result = 0;
  for (byte i = 0; i < len; i++)
     result += data[i];
  return result;
}

int fcpp ( const char  data[] ) {
  Serial.println(data);
  return (int)(sizeof data);// Wie soll das gehen ?
}

Ich verstehe dich offensichtlich nicht richtig?

Nein. Er meint einen Zeiger auf ein Array. Das macht man aber selten, weil man dann nur Arrays dieser Gr√∂√üe √ľbergeben kann, was die Flexibilit√§t der Funktion stark einschr√§nkt.

Das macht man aber selten, weil man dann nur Arrays dieser Gr√∂√üe √ľbergeben kann,

Naja, vielleicht bin ich anders, als die Anderen…
Aber bei mir, in meiner kleinen Arduinowelt, sind Arrays mit der gleichen Größe weiter verbreitet als Arrays mit unterschiedlicher größe, bei gleichem Elementtype.

F√ľr mich gilt das also ~~selten/~~h√§ufig.

Und selbst wenn…

Hier mal ein Beispiel, wie ‚Äúsch√∂n‚ÄĚ es sein kann, wenn man die mitgegebene Typinformation nutzt:

int  testarrayA[] ={1,1,1,1,};
int  testarrayB[] ={1,1,1,1,1,1,};
byte testarrayC[] ={1,1,};



long summe(auto &array)
{
  long result = 0;
  for(auto n:array) result += n;
  return result;
}


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

  Serial.print("Summe A: ");  Serial.println(summe(testarrayA));
  Serial.print("Summe B: ");  Serial.println(summe(testarrayB));
  Serial.print("Summe C: ");  Serial.println(summe(testarrayC));
 }

void loop() {}

(Wenn ich mal Lust/Zeit habe, baue ich ein Beispiel, um C vs. C++ Zeiger vorzuf√ľhren)

Es sollte aber entsprechend publiziert werden, wenn Du Dir schon die Muehe machst.

? ?
Wieso...?
Wenn ich das baue, dann hauptsächlich, um MIR das zu verdeutlichen.
Wenn dann andere auch was von haben, ist das ein netter Seiteneffekt.

-uebergebe an summe(auto &array) den Zeiger???

Zeiger
Referenz

Tipp:
Wenn du die Wahl hast, zwischen Zeiger und Referenz, dann verwende eine Referenz.
Es sei denn, es sprechen unwiderlegbare Argumente f√ľr Zeiger.

Du hast ja keinen Variablennamen in (),

Das verstehe ich nicht.

long summe(auto &array)
auto ist ein C++ Schl√ľsselwort.
array ist ein von mir gewählter Bezeichner.
Da k√∂nnte auch zwiebelkuchen stehen, w√ľrde allerdings eher verwirren, als das Verst√§ndnis f√∂rdern.

Es gibt dort also eine Variable, namens array, vom Type auto.

summe(testarrayA);
array wird zu einer Referenz auf testarrayA vom Type int[4]
auto ist in diesem konkreten Einzelfall ein Stellvertreter f√ľr int[4]

for(auto n:array)
Aus der C Welt ist for(Ausdruck;Ausdruck;Ausdruck) bekannt.
C++ bietet eine weitere Variante f√ľr den Betrieb mit Iteratoren.
Dummerweise sind unsere ¬ĶC meist zu klein um das ganz auszukosten, aber mit Arrays geht das.
Denn da bietet der Compiler einen eingebauten Iterator.

n ist hier die Variable.
auto ihr Type.
Da die Referenz in diesem Fall int[4] ist, ist jedes Element des Arrays vom Type int
auto kompiliert zu int

Das ist schon interessant, was die neuen C-Kompiler so bringen.

Gruß Tommy

Ich hatte bei array (in IDE farbig hinterlegt) nicht an einen Variablennamen gedacht.

Hmmm ..

Das ist dann in einer deiner keywords.txt so festgelegt.
Denn: Bei mir ist das nicht so.

Das Verfahren der Farbgebung nach keywords-Dateien ist nur Spaß und sollte man nicht ernst nehmen.
Mir ist z.B. verleidet, eine Variable status zu nennen. Aus der roten Farbe zu schliessen, es sei ein Schl√ľsselwort oder eine bereits definierte Variable, f√ľhrt nur in die Irre.

Arrays erkennt man an den eckigen Klammern, das Wort array ist zur freien Verf√ľgung.

Zum Datentypautohabe ich ein zwiespältiges Verhältnis:
Wenn der Compiler rauskriegt, was der Datentyp ‚Äúwirklich‚ÄĚ sein muss, sollte man das auch als Programmierer hinkriegen. Wenn man da (noch) Probleme hat, z.B weil man Zeiger nicht von Referenzen unterscheiden kann (nur mal so als Beispiel, sorry :wink: ), ist die Verwendung von auto auf Verdacht keine L√∂sung, die ich gut finde.

Die Sache mit auto ist ja mal interessant, danke daf√ľr (y)...in Verbindung mit der Neuerung an der for-Schleife (habe ich so auch noch nicht gesehen, nochmal danke^^) gibts da durchaus Potential. Arrays √ľbergeben k√∂nnen ohne vorher deren L√§nge berechnen und zus√§tzlich einer Funktion geben zu m√ľssen: top
Allerdings ist der Iterator der Schleife eben nicht sichtbar... hei√üt das auch, ohne Zugriff? Habe damit mal ein bisschen rumgespielt: Wenn man z.B. ein Zeichen an einer bestimmten Stelle austauschen m√∂chte, dann kommt man nicht umhin, eine zus√§tzliche Variable f√ľr den Index mit in der for-Schleife hochz√§hlen zu lassen, oder? So in etwa:

void aendereZeichen(auto &array, byte index, char zeichen){
  int counter=0;
  for(auto n:array){
    counter++;
    if(counter==(index)){
      array[counter]=zeichen;
      break;
    }
  }
}

Das kommt daher weil das Beispiel nicht so toll ist. Da wird eine Kopie des Elements erstellt. Man kann auch eine Referenz nehmen und es dann auch √§ndern. Eine Referenz braucht man auch sp√§testens wenn man √ľber Objekte iteriert.

Sobald man den index braucht, sollte man den alten for Schleifen Type einsetzen, denke ich mal.
auto kann man ja trotzdem verwenden.
Naja..
Es ist ein weiterer Pfeil im Köcher.
Ein Pfeil, welcher manche Aufgabengebiete genial abdeckt.
Nicht mehr und nicht weniger.

void aendereZeichen(auto &array, byte index, char zeichen){
  int counter=0;
  for(auto & n:array){ // n ist jetzt eine Referenz auf ein Arrayelement
    counter++;
    if(counter==(index)){
     n = zeichen;
      break;
    }
  }
}

Hi

Könnte ich in diesem Beispiel nicht einfach
array[index]=zeichen;
schreiben?

Mir erschließt sich der (Mehr?)Wert dieser Schleifenart nicht wirklich - wobei ich ebenfalls so meine Probleme mit Zeigern und Referenzen habe - wird bei Zeit aber Alles noch Mal durchgekaut.

MfG

Könnte ich in diesem Beispiel nicht einfach
array[index]=zeichen;
schreiben?

Sicherlich.

Mir erschließt sich der (Mehr?)Wert dieser Schleifenart nicht wirklich -

Wenn man keinen expliziten Iterator braucht, ist diese Schleife eine erhebliche Vereinfachung.
Sie ist "sicherer" weil Bereichs√ľberschreitungen im Design schon ausgeschlossen sind.
(fast täglich sehen wir hier im Forum Fehltritte beim Schleifenindex)

Auch wenn es f√ľr unsere kleinen AVR selten zum tragen kommt:
Nicht jede Menge, √ľber welche man hinweg iterieren kann, ist ein Array, oder √ľberhaupt in irgendeinem Speicher vorhanden.