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.