Merkwürdiges Programmverhalten bei Array als Funktionsargument
-
Es geht um folgendes Programm, dass eine Klasse für ein zweidimensionales Array von bool-Werten implementieren soll und durch zwei Klammeroperatoren einen Zugriff auf ein beliebiges Element array[i][j] erlauben soll. Dabei soll der erste Klammeroperator ein Objekt der Klasse RowProxy zurückliefern, dieses repräsentiert die Zeile i. Aus diesem Objekt wird dann mittels des zweiten Klammeroperators auf das j-te Element zugegriffen.
#include <iostream> /* Der Zugriff auf das zweidimens. Array erfolgt anders als beim gewöhnlichen Array, da die Indizierung bei (1,1) beginnt: TwoDBoolArray[i][j] liefert den j-ten Eintrag in der i-ten Zeile. */ // ein Objekt das vom operator[] zurueckgegeben wird class RowProxy { public: // Konstruktor RowProxy(const bool* const daten, int zeilenindex, int spaltenzahl ); // der "innere" Klammerzugriffsoperator bool operator [](int j ); bool* _daten; int Zeilenindex; int Spaltenzahl; private: }; /* erzeugt eine "Zeile" vom Index Zeilenindex aus der Matrix mithilfe der linearen Daten, indem bis zum Feld (Index - 1) * * Spaltenzahl gegangen wird (erstes Feld der gesuchten Zeile), dann werden von dort beginnend alle Zeilenelemente in ein array _daten geschrieben. */ RowProxy::RowProxy (const bool* const Daten, int zeilenindex, int spaltenzahl) { bool array[spaltenzahl]; for (int i = 0; i < spaltenzahl; i = i + 1) { array[i] = Daten[((zeilenindex - 1) * spaltenzahl) + i]; } _daten = array; Zeilenindex = zeilenindex; Spaltenzahl = spaltenzahl; } /* liefert das j-te Element der Zeile */ bool RowProxy::operator[](int j) { return _daten[j-1]; } class TwoDBoolArray { public: // Initialisiere ein n x m Array TwoDBoolArray( int n, int m); // Copy-Konstruktor TwoDBoolArray(const TwoDBoolArray& other ); // Destruktor ~TwoDBoolArray(); // Zuweisungsoperator TwoDBoolArray& operator=(const TwoDBoolArray& other ); // Gebe Zeilenzahl zurueck int rows(); // Gebe Spaltenzahl zurueck int cols(); // der "aeussere" Klammerzugriffsoperator RowProxy operator[](int i ); bool* daten; int m, n; private: // Array, das alle m*n Daten linear speichert. // m Spaltenzahl, n Zeilenzahl }; // ------------------------------------------------ TwoDBoolArray::TwoDBoolArray( int N, int M) { bool Array[N*M]; for (int i = 0; i < N * M; i = i + 1) { Array[i] = true; } daten = Array; m = M; n = N; } TwoDBoolArray::~TwoDBoolArray() { } TwoDBoolArray::TwoDBoolArray(const TwoDBoolArray& other ) { n = other.n; m = other.m; bool Array[n*m]; for (int i = 0; i < n * m; i = i + 1) { Array[i] = other.daten[i]; } daten = Array; } TwoDBoolArray& TwoDBoolArray::operator=(const TwoDBoolArray& other ) { n = other.n; m = other.m; bool Array[n*m]; for (int i = 0; i < n * m; i = i + 1) { Array[i] = other.daten[i]; } daten = Array; } int TwoDBoolArray::rows() { return n; } int TwoDBoolArray::cols() { return m; } RowProxy TwoDBoolArray::operator[](int i ) { RowProxy Row(daten, i, m); return Row; } //------------------------------------------ int main() { TwoDBoolArray array(3, 5); std::cout << array[2][3] << std::endl; }Der Code kompiliert und wird auch ohne Fehlermeldung ausgeführt. Allerdings wird als Ergebnis 0 ausgegeben und nicht, wie zu erwarten, 1. Durch den debugger konnte ich das Problem schon recht gut eingrenzen. Es scheint an dem Konstruktor von Row im ersten Klammeroperator zu liegen. Lässt man sich auf dem Debugger Schritt für Schritt die Werte ausgeben, so sieht man, dass die Initilisierung noch funktioniert. Bevor der Konstruktor im ersten Klammeroperator ausgeführt wird, ist daten ein Array mit 15 true-Einträgen. Sobald aber der Operator aufgerufen wird (aber noch bevor irgendetwas in seinem Rumpf ausgeführt wird), ist daten auf einmal verändert worden. Hier, was der debugger sagt:
Breakpoint 1, main () at 2DArray.cc:141
141 TwoDBoolArray array(3, 5);
(gdb) next
142 std::cout << array[2][3] << std::endl;
(gdb) print array.daten
$1 = (bool
0xbffff130
(gdb) print array.daten[0]
$2 = true
(gdb) print array.daten[14]
$3 = true
(gdb) step
TwoDBoolArray::operator[] (this=0xbffff178, i=2) at 2DArray.cc:132
132 RowProxy Row(daten, i, m);
(gdb) print array.daten
No symbol "array" in current context.
(gdb) print daten
$4 = (bool
0xbffff130
(gdb) print daten[3]
$5 = true
(gdb) step
RowProxy::RowProxy (this=0xbffff184, Daten=0xbffff130, zeilenindex=2,
spaltenzahl=5) at 2DArray.cc:32
32 bool array[spaltenzahl];
(gdb) print daten
No symbol "daten" in current context.
(gdb) print Daten
$6 = (const bool * const) 0xbffff130
(gdb) print Daten[0]
$7 = false
(gdb) print Daten[14]
$8 = 4Woran das liegt, kann ich mir aber nicht erklären. Um Hilfe wäre ich sehr dankbar.
-
bool Array[N*M]; // ... daten = Array;So erstellt man kein Array und lässt eine Membervariable darauf zeigen. Erstens sind N und M Parameter und die Größe des Arrays daher keine Konstante. Sowas nennt sich Variable Length Array und gibt es eigentlich nur in C, aber nicht in C++ (Ich tippe du nutzt GCC, compilier mal mit -pedantic). Zweitens lässt du deinen daten Pointer auf eine lokale Variable zeigen. Nachdem der Constructor fertig ist zeigt der Pointer auf Speicher der nicht mehr dir gehört. Schau dir mal
std::vectoran*. Damit kannst du einfacher dynamische Arrays nutzen.*Beachte, dass es eine Spezialisierung für
vector<bool>gibt die nur 1 Bit pro Wert braucht. Das klingt zwar toll, sorgt aber für viele komische Eigenheiten vonvector<bool>. Nehm liebervector<unsigned char>, auch wenn du nur true oder false speichern willst.
-
Lokale Variablen sind nach dem Ende des Skopes nicht mehr gültig, dü leakst sie aber mehr als einmal:
RowProxy::RowProxy (const bool* const Daten, int zeilenindex, int spaltenzahl) { bool array[spaltenzahl]; for (int i = 0; i < spaltenzahl; i = i + 1) { array[i] = Daten[((zeilenindex - 1) * spaltenzahl) + i]; } _daten = array; //Böse Zeilenindex = zeilenindex; Spaltenzahl = spaltenzahl; }