C++ Core Guidelines: Lifetime Safety und die Regeln prüfen

Modernes C++  –  3 Kommentare

Das Lifetime-Safety-Profil der C++ Core Guidelines lässt sich auf einen Punkt reduzieren: Dereferenziere keinen Zeiger, der möglicherweise ungültig ist. Ein Zeiger kann ungültig sein, wenn er nicht initialisiert oder ein Nullzeiger ist. Ein Zeiger kann aber auch ungültig sein, wenn er auf ein Element außerhalb seines Gültigkeitsbereichs oder auf ein bereits gelöschtes Element verweist.

Wenn du einen möglicherweise ungültigen Zeiger nicht dereferenzierst, besitzt dies laut den C++ Core Guidelines einige Auswirkungen auf dein Programm:

  • Du eliminierst eine der wichtigsten Ursachen für hässliche Fehler in C++.
  • Du eliminierst die wichtigste Ursache für mögliche Sicherheitsprobleme.
  • Du verbesserst die Performanz, da du auf sich häufig wiederholende "paranoide" Checks verzichten kannst.
  • Dein Vertrauen in die Richtigkeit deines Programms wächst.
  • Du vermeidest undefiniertes Verhalten, da du eine wichtige C++ Regel einhältst.

Für mich besitzt der Umgang mit Zeigern eine noch größere Dimension: Besitzverhältnisse. Besitzverhältnisse meint, dass zu jedem Zeitpunkt klar sein muss, wer für die Lebenszeit eines Objekts verantwortlich ist. Vereinfachend gesagt, kennt C++11 sechs verschiedene Besitzverhältnisse:

  • Lokale Objekte: Die C++-Laufzeit als Besitzer verwaltet automatisch den Lebenszyklus seiner Ressourcen. Dasselbe gilt für globale Objekte oder Mitglieder einer Klasse. Die Guidelines nennen diese lokalen Objekte "scoped objects".
  • Referenzen: Ich bin nicht der Besitzer. Ich habe mir die Ressource, die nicht null sein kann, nur ausgeliehen.
  • Nackte Zeiger: Ich bin nicht der Besitzer. Ich habe mir die Ressource, die null sein kann, nur ausgeliehen. Ich darf die Ressource nicht freigeben.
  • std::unique_ptr: Ich bin der exklusive Besitzer der Ressource. Ich darf die Ressource freigeben.
  • std::shared_ptr: Ich teile mir die Ressource mit anderen Besitzern. Ich darf meine Besitzverhältnisse explizit freigeben.
  • std::weak_ptr: Ich bin nicht der Besitzer der Ressource, aber ich kann zeitweise zum geteilten Besitzer werden, indem ich die Methode std::weak_ptr::lock verwende.

Vergleich doch diese feinjustierbaren Besitzverhältnisse mit einem nackten Zeiger. Genau das ist der Punkt, den ich an modernem C++ sehr schätze.

Nun stellst du dir vermutlich die Fragen: Schön, dass wir Regeln haben, aber wie lässt sich prüfen, ob mein Sourcecode diese Regeln einhält? Dank der Guidelines Support Library (GSL) lassen sich die Regeln der C++ Core Guidelines automatisch prüfen.

Die Regeln der Guidelines automatisch prüfen

Die GSL ist eine kleine Bibliothek, die die Regeln der C++ Core Guidelines unterstützt. Es gibt ein paar Implementierungen der GSL: Die GSL besteht nur aus Headerdateien, sodass sich ihre Funktionen und Datentypen recht einfach anwenden lassen. Die bekannteste Umsetzung ist die von Microsoft, die auf GitHub gehostest ist: Microsoft/GSL. Die Microsoft-Implementierung setzt C++14 voraus und kann auf vielen Plattformen verwendet werden. Hier sind ein paar der populären Plattformen:

  • Windows using Visual Studio 2015
  • Windows using Visual Studio 2017
  • Windows using Visual Studio 2019
  • Windows using Clang/LLVM 3.6
  • Windows using Clang/LLVM 7.0.0
  • Windows using GCC 5.1
  • Windows using Intel C++ Compiler 18.0
  • GNU/Linux using Clang/LLVM 3.6-3.9
  • GNU/Linux using Clang/LLVM 4.0
  • GNU/Linux using Clang/LLVM 5.0
  • GNU/Linux using Clang/LLVM 6.0
  • GNU/Linux using Clang/LLVM 7.0
  • GNU/Linux using GCC 5.1

Lasse mich ausprobieren, welche Einsicht ich zu einer einfachen Sourcecodedatei mit der GSL erhalte. Das folgenden Programm bricht die Profile Type Safety, Bounds Safety und Lifetime Safety.

Bruch der Profile Type Safety, Bounds Safety und Lifetime Safety
// gslCheck.cpp

#include <iostream>

void f(int* p, int count) {
}

void f2(int* p) {
int x = *p;
}

int main() {

// Break of type safety
// use of a c-cast
double d = 2;
auto p = (long*)&d;
auto q = (long long*)&d;

// Break of bounds safety
// array to pointer decay
int myArray[100];
f(myArray, 100);

// Break of Lifetime Safety
// a is not valid
int* a = new int;
delete a;
f2(a);

}

Die Kommentare in dem Sourcecode dokumentieren eindeutig meine Verletzungen der Profile. Nun werde ich Visual Studio 2019 verwenden und meine Schritte vorstellen, um meine Verletzungen zu detektieren.

Codeanalyse im Buildprozess verwenden

Du musst die Checkbox aktivieren. Per Default sind die Regeln zur Type Safety, Bounds Safety und Lifetime Safety kein Bestandteil der Native Recommended Rules von Microsoft.

Konfiguriere deine zu verwendenden Regeln

Wie du aus dem Screenshot erkennen kannst, habe ich meine eigene Kollektion von Regeln definiert und diese unter den Namen CheckProfile gespeichert. Diese Kollektion besteht aus den Regeln C++ Core Guidelines Bounds Rules, C++ Core Guidelines Type Rules und C++ Core Guidelines Lifetime Rules.

Führe die Codeanalyse aus

Die Anwendung meines eigenen Regelsatzes war sehr erfolgreich:

Alle Verletzungen der Profile wurden erkannt. Für jede Verletzung wie zum Beispiel die der ersten erhalte ich die Zeilennummer (17) und die Regel des betroffenen Profils (type.4).

Warnungen unterdrücken

Manchmal sollen spezifische Warnungen unterdrückt werden. Dies lässt sich mit Attributen erreichen. In meinem nächsten Beispiel wende ich zweimal eine Array-to-Zeiger-Vereinfachung (decay) an. Nur der zweite Aufruf führt zu einer Warnung:

// gslCheckSuppress.cpp

#include <iostream>

void f(int* p, int count) {
}

int main() {

int myArray[100];

// Break of bounds safety
[[gsl::suppress(bounds.3)]] { // suppress warning
f(myArray, 100);
}

f(myArray, 100); // warning

}

Das Attribut gsl::suppress(bounds.3) erfüllt seinen Job wie erwartet. Es ist nur in seinem Bereich gültig. Lediglich die zweite Verletzung der Bounds Safety führt zu einer Warnung:

Wie geht's weiter?

Ich werde die nächste Sektion der C++ Core Guidelines überspringen, da ich bereits einen Artikel zu der Guidelines Support Library geschrieben habe. Ich denke aber, dass der darauffolgende Abschnitt zu kontroversen Reaktionen führt: "Naming and layout rules".