Geltungsbereich von Variablen in Unterprogrammen

Dies ist weniger eine Frage, sondern mehr eine Antwort. Da ich dieses Thema in den Tutorials, die ich gerade konsultiere, nicht wirklich erklärt bekam, habe ich eben selbst rumprobiert. Und möchte hier das Ergebnis zeigen. Jeder C-Programmierer weiß das natürlich, aber Anfänger könnten da Schwierigkeiten haben. Hier wird eine Variable "x" mehrfach deklariert, und die entsprechende Reaktion des Programms gezeigt. Und hier der kurze Code:

int x=555;
void setup() {
  Serial.begin(115200);
  delay(2000); //if(!Serial){ ... } funktioniert eigentlich nicht, lieber einfach warten

Serial.println(x);
Funktion1();
Funktion2();
Serial.println(x);
}

void loop() { } // bleibt diesmal ganz leer

void Funktion1() {
   x=666; // hier wird die globale Variable x überschrieben
   Serial.print("F1 "); // soll hinweisen, dass diese Ausgabe von Funktion1 kommt
   Serial.println(x);
}
void Funktion2() {
  int x=777; // hier wird die globale Variable x nicht überschrieben,
             // sondern eine andere, die nur scheinbar den selben Namen hat
   Serial.print("F2 "); // soll hinweisen, dass diese Ausgabe von Funktion2 kommt
   Serial.println(x);
}

Und die entsprechende Ausgabe am Serial Monitor:

555
F1 666
F2 777
666

Hallo waldwurm

Aus diesem Grund ist es sinnvoll, den Variablen einen beschreibenden Namen zu geben.

int hund = 555;
void setup()
{
  Serial.begin(115200);
  delay(2000);
  Serial.print(__func__), Serial.print(" "), Serial.println(hund);
  Funktion1();
  Funktion2();
  Funktion2();
  Serial.print(__func__), Serial.print(" "), Serial.println(hund);
}

void loop()
{
  // bleibt diesmal ganz leer
}

void Funktion1()
{
  hund = 666;
  Serial.print(__func__), Serial.print(" "), Serial.println(hund++);
}
void Funktion2()
{
  static int katze = 777;
  Serial.print(__func__), Serial.print(" "), Serial.println(katze++);
}

Das ist ja gerade der Witz an der Sache, globale und lokale Variablen zu unterscheiden, obwohl sie den gleichen Namen haben. Hätten alle Variablen, sowohl lokale als auch globale, definitiv unterschiedliche Namen, dann bräucht man diese Unterscheidung eigentlich nicht. Jetzt kann man nach Herzenslust Namen vergeben, auch wenn in anderer Leut's Funktionen zufällig die gleichen Namen schon deklariert waren.

1 Like

Die Unterscheidung ist ja nicht wegen der Namensgleichheit geschaffen, sie dient der Kapselung. Also dass Variablen nur dort gültig sind, wo sie auch gebraucht werden.
Normalerweise sollten globale Variablen nur eingesetzt werden, wenn es sich nicht vermeiden lässt. Schließlich sind sie auch global veränderbar.

Das kann bei größeren Programmen aber zu Problemen führen. Darum solle der Geltungsbereich möglichst klein sein, weil das hilft Fehler zu vermeiden.

Aus diesem Grund ist es sinnvoll, den Variablen einen beschreibenden Namen zu geben.

Ich persönlich mag das nicht so gerne. Ich halte es wie in der Mathematik. Der Satz von Pythagoras ist für mich: a²+b²=c². Und nicht KatheteEinsImRechtwinkligenDreieck^2 + KatheteZweiImRechtwinkligenDreieck^2 = HypertenuseImRechtwinkligenDreieck^2.
Etwas übertrieben, um den Gedanken deutlich zu machen. :wink: Aber das ist Geschmacksache, und natürlich ein ganz anderes Thema.

1 Like

Man kann das auch noch auf die Parameter, die man einer Funktion übergibt, ausweiten:

int hund = 555;
void setup()
{
  Serial.begin(115200);
  delay(500);
  Serial.print(__func__), Serial.print(" "), Serial.println(hund);
  Funktion1();
  Funktion2();
  Funktion2();
  Serial.print(__func__), Serial.print(" "), Serial.println(hund);
  int maus = 111;
  Funktion3(maus);
  Funktion4(maus);
  Serial.print(__func__), Serial.print(" "), Serial.println(maus);
}

void loop()
{
  // bleibt diesmal ganz leer
}

void Funktion1()
{
  hund = 666;
  Serial.print(__func__), Serial.print(" "), Serial.println(hund++);
}
void Funktion2()
{
  static int katze = 777;
  Serial.print(__func__), Serial.print(" "), Serial.println(katze++);
}
void Funktion3(int tier)
{
  Serial.print(__func__), Serial.print(" "), Serial.println(tier);
  tier = 0;  // sinnlos
}
void Funktion4(int &tier)
{
  Serial.print(__func__), Serial.print(" "), Serial.println(tier);
  tier = 0;
}

Das wird dann allerdings schnell länglich :wink:

Es geht in der Hauptsache ja nicht um Formeln. Es ist ein Unterschied ob man im Code

constexpr uint8_t x {3};
constexpr uint8_t y {4};
constexpr uint8_t z {5};

stehen hat oder

constexpr uint8_t pinLED {3};
constexpr uint8_t pinSchalter {4};
constexpr uint8_t maxDevices {5};

Ich würde jedem die zweite Variante empfehlen. Aber natürlich bleibt das jedem selbst überlassen.

Deine zweite Variante sieht in der Tat besser aus. Vielleicht ein Kompromiss, nicht all zu lange Namen, aber doch beschreibend.
Außerdem hätte man ja noch die Möglichkeit, hinzuschreiben, was was ist:
constexpr uint8_t x {3}; // die Nummer des Pins, mit dem die Led angesteuert wird
Jetzt natürlich nicht plump mit "x", sondern schon etwas kurz beschreibender.

Ich denke auch, dass man einen Hund einen Hund nennen sollte.
Dafür haben wir ja Geltungsbereiche, damit sich das nicht ins Gehege kommt

#include <Streaming.h> // die Lib findest du selber ;-)
Print &cout = Serial; // cout Emulation für "Arme"

namespace Namespace
{
  int x = 122;
};

int x = 123;


struct Test
{
  int x;
  
  Test(int x = 125):x{x}{}
  
  void test(int x = 124)
  {
    cout << F("Parameter: ")  << x           << endl;
    cout << F("Instace: ")    << this->x     << endl;
    cout << F("Global: ")     << ::x         << endl;
    cout << F("Namespace: ")  << Namespace::x << endl;
  }
}test;

void setup() 
{
  Serial.begin(9600);
  cout << F("Start: ") << F(__FILE__) << endl;
  test.test();
}

void loop() 
{

}

Man kann das natürlich machen. Aber wenn man ein größeres Programm hat, ist es besser wenn man es (fast) sprachlich lesen kann. Außerdem neigen Kommentare dazu schnell falsch zu werden, weil sie bei Programmänderungen oft nicht angepasst werden.

Das sieht man hier im Forum oft genug.

Spätestens, wenn Du größere Programme hast oder längere Zeit nichts mit einem bestimmten Programm gemacht hast, wirst Du sprechende Variablennamen schätzen lernen.
Falls Du beruflich in einem Team entwickeln willst, sind sie, neben anderen Festlegungen zur Schreibweise, Pflicht.

Der Gültigkeits-Bereich ist übrigens in jedem C/C++-Buch eindeutig beschrieben. Ebenso die Lebensdauer.

Gruß Tommy

Auch sehr schön. Ich hab das mal laufen lassen. Und was raus kam war:

setup 555
Funktion1 666
Funktion2 777
Funktion2 778
setup 667

Ins besondere die Verwendung von static wird hier nochmal deutlich. Und natürlich Serial.print(__func__) ist praktisch, das kannte ich noch nicht. Ich komm langsam immer weiter.
P.S. nicht so ganz wird hier der Sinn von static klar, denn "katze" wird ja nirgends woanders noch einmal definiert.
noch mal P.S. doch nicht. Denn wenn "katze" nicht als static deklariert wäre, würde es ja bei jedem Aufruf von Funktion2 wieder auf 777 zurückgesetzt werden.

Serial.print(__PRETTY_FUNCTION__)
Kann noch mehr.
Gerade in Methoden oder Template Funktionen interessant/wichtig.

Statische Variablen sind problematisch, wenn nicht sogar böse.
Ich rate zum Verzicht!