Die Programmiersprache C++ - Speicherverwaltung

Martin Sulzmann

Übersicht

Manuelle Speicherverwaltung in C++ ist trickreich

Abhilfen

Wir verwenden ab jetzt C++11

Beispiel (Speicherleck)

Zu jedem “malloc” Bedarfs es eines “free” zur richtigen Zeit!

#include <stdlib.h>
#include <iostream>
#include <string>
using namespace std;


int length(char *s) {
  int n = 0;
  while(*s != '\0') {
    n++;
    s++;
  }

  return n;
}

char* reverse(char* s) {
  const int n = length(s);
  char* t = (char*)malloc(n + 1);
  int i;

  for(i = 0; i < n; i++) {
    t[i] = s[n-1-i];
  }
  t[n] = '\0';

  return t;
}

void testReverse() {
  char s[] = "Hallo";

  cout << "\n" << s;

  cout << "\n" << reverse(s);

  cout << "\n" << reverse(reverse(s));

}


void testReverse2() {
  char s[] = "Hallo";

  cout << "\n" << s;

  char* s1 = reverse(s);
  cout << "\n" << s1;
  free(s1);


  char* s2 = reverse(s);
  char* s3 = reverse(s2);
  cout << "\n" << s3;
  free(s2);
  free(s3);

}

int main() {

  testReverse();

  testReverse2();


  return 0;
}

Resource Acquisition Is Initialization (RAII)

Konstruktor legt Objekt an auf dem “Stack”.

Destruktor wird aufgerufen sobald das Objekt freigegeben wird. Annahme ist, dass das Objekt auf dem “Stack” liegt.

Dadurch wird garantiert, dass es zu jedem “new” ein passendes “delete” gibt.

In C++, wird “malloc” ersetzt durch “new” und “free” wird ersetzt durch “delete”.

Beachte:

#include <stdlib.h>
#include <iostream>
#include <string>
using namespace std;

// Resource Acquisition Is Initialization (RAII)

class String {
public:
  String() {
    s = new char[1];                // (char*)malloc(1);
    s[0] = '\0';
  }

  String(char* t) {
    int n = length_(t);
    s = new char[n+1];

    for(int i=0; i<n; i++) {
      s[i] = t[i];
    }
    s[n] = '\0';
  }


  ~String() {
    delete[] s;                      // free the sequence of chars allocated
  }

  int length() {
    return(length_(this->s));
  }

  void reverse() {
    int n = this->length();
    char* t = new char[n+1];

    for(int i = 0; i < n; i++) {
      t[i] = s[n-1-i];
    }
    t[n] = '\0';

    delete[] s;
    s = t;
  }



private:
  char* s;

  int length_(char *s) {
       int n = 0;
       while(*s != '\0') {
         n++;
         s++;
       }

       return n;
  }

  // Friends can access private attributes.
  friend ostream& operator<< (ostream &out, String &s);

};


ostream& operator<< (ostream &out, String &str) {
  for(int i=0; i<str.length(); i++) {
    out << str.s[i];
  }
  return out;
}

void testReverse() {
  char s[] = "Hallo";

  String str(s);

  cout << "\n" << str;

  str.reverse();
  cout << "\n" << str;;


}

int main() {

  testReverse();

  return 0;
}

Problem - Kopieren mit Zeigern

#include <stdlib.h>
#include <iostream>
#include <string>
using namespace std;

// Kopieren mit Zeiger
// Zwei-faches delete!

class String {
public:
  String() {
    s = new char[1];
    s[0] = '\0';
  }

  String(char* t) {
    int n = length_(t);
    s = new char[n+1];

    for(int i=0; i<n; i++) {
      s[i] = t[i];
    }
    s[n] = '\0';
  }


  ~String() {
    delete[] s;
  }

private:
  char* s;

  int length_(char *s) {
       int n = 0;
       while(*s != '\0') {
         n++;
         s++;
       }

       return n;
  }

};


void f(String str2) {

}
// Destruktoraufruf fuer str2
//  => delete[] str2.s

int main() {
  char s[] = "Hallo";

  String str(s);

  f(str); // str2.s = str.s

  return 0;
}
// Destruktoraufruf fuer str
//  => delete[] str.s
// Zweifaches delete!

Copy Semantik

Jedesmal wenn ein Objekt (mit einem Zeiger) kopiert wird

Wie und wann werden Objekte kopiert?

Beides kann überladen werden.

#include <stdlib.h>
#include <iostream>
#include <string>
using namespace std;

// Copy Semantik

class String {
public:
  String() {
    s = new char[1];
    s[0] = '\0';
  }

  String(char* t) {
    int n = length_(t);
    s = new char[n+1];

    copy_(t,n,s);
    s[n] = '\0';
  }

  // Copy Konstruktor
  // Standard Definition ist: this->s = src.s
  // Gilt auch fuer Zuweisung.
  String(const String& src) {

    int n = length_(src.s);
    this->s = new char[n+1];
    copy_(src.s,n,this->s);
    this->s[n] = '\0';

  }

  // Copy Zuweisung
  String& operator=(const String& src) {

    if(this != &src) {

      delete[] this->s;
      int n = length_(src.s);
      this->s = new char[n+1];
      copy_(src.s,n,this->s);
      this->s[n] = '\0';

    }

    return *this;
  }

  ~String() {
    delete[] s;
  }

private:
  char* s;

  int length_(char *s) {
       int n = 0;
       while(*s != '\0') {
         n++;
         s++;
       }

       return n;
  }

  void copy_(char* s, int n, char* t) {
       int i = 0;
       while(i < n) {
         t[i] = s[i];
         i++;
       }
}

};


void f(String str2) {

}

int main() {
  char s[] = "Hallo";

  String str(s);

  f(str); // Copy str

  String str2 = str; // Copy str

  f(str2);

  return 0;
}

Copy + Move Semantik

Jedes mal eine (deep)copy durchzuführen ist nicht sehr effizient.

Man betrachte

In solchen Fällen führen einen “move” durch

#include <stdlib.h>
#include <iostream>
#include <string>
using namespace std;

// Copy/Move Semantik

class String {
public:
  String() {
    s = new char[1];
    s[0] = '\0';
  }

  String(char* t) {
    int n = length_(t);
    s = new char[n+1];

    copy_(t,n,s);
    s[n] = '\0';
  }

  // Copy Konstruktor
  // Standard Definition ist: this->s = src.s
  // Gilt auch fuer Move und Zuweisung.
  String(const String& src) {

    int n = length_(src.s);
    this->s = new char[n+1];
    copy_(src.s,n,this->s);
    this->s[n] = '\0';

  }

  // Copy Zuweisung
  String& operator=(const String& src) {

    if(this != &src) {

      delete[] this->s;
      int n = length_(src.s);
      this->s = new char[n+1];
      copy_(src.s,n,this->s);
      this->s[n] = '\0';

    }

    return *this;
  }


  // Move Konstruktor
  String(String&& src) {
    this->s = src.s;
    src.s = nullptr;
  }

  // Move Zuweisung
  String& operator=(String&& src) {

    if(this != &src) {

      delete[] this->s;
      this->s = src.s;
      src.s = nullptr;

    }

    return *this;
  }

  ~String() {
    delete[] s;
  }

  void print() {
    cout << this->s;
  }

private:
  char* s;

  int length_(char *s) {
       int n = 0;
       while(*s != '\0') {
         n++;
         s++;
       }

       return n;
  }

  void copy_(char* s, int n, char* t) {
       int i = 0;
       while(i < n) {
         t[i] = s[i];
         i++;
       }
}

};


void f(String str2) {

}

int main() {
  char s[] = "Hallo";

  String str(s);

  f(str); // Copy str

  String str3 = str; // Copy str

  f(str3);

  f(String()); // Move eines temporaeren String Objekts

  String str4 = move(str);

  f(str4);

  // str.print();
  // Liefert segmentation fault
  // wegen "move"

  return 0;
}

Zusammenfassung

  1. Destruktor

  2. “copy” Konstruktor (für lvalue refs)

  3. “copy” Zuweisung (für lvalue refs)

Definiere (geeignet)

  1. Destruktor

  2. “copy” Konstruktor (für lvalue refs)

  3. “copy” Zuweisung (für lvalue refs)

  4. “move” Konstruktor (für rvalue refs)

  5. “move” Zuweisung (für rvalue refs)