Die Null-Zeiger-Konstante nullptr

Das neue Null-Zeiger-Literal nullptr räumt mit der Mehrdeutigkeit der Zahl 0 und dem Makro NULL in C++ auf.

Die Zahl 0

Das Problem mit dem Literal 0 ist, dass es abhängig vom Kontext den Null-Zeiger (void*)0 oder die Zahl 0 bezeichnet. Zugegeben, an diese Schrägheit haben wir uns schon gewöhnt. Aber nur fast.

So birgt das kleine Programm rund um die Zahl 0 doch noch einiges an Verwirrungspotential mit sich.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// null.cpp

#include <iostream>
#include <typeinfo>

int main(){

  std::cout << std::endl;

  int a= 0;
  int* b= 0;
  auto c= 0;
  std::cout << typeid(c).name() << std::endl;

  auto res= a+b+c;
  std::cout << "res: " << res << std::endl;
  std::cout << typeid(res).name() << std::endl;
  
  std::cout << std::endl;

}

 

Die Frage ist. Von welchem Datentyp ist die Variable c in der Zeile 12 und die Variable res in der Zeile 15? 

null

Die Variable c ist vom Typ int und die Variable res ist vom Typ Zeiger auf int: int*. Eigentlich alles ganz einfach, denn in dem Ausdruck a+b+c in der Zeile 15 findet Zeigerarithmetik statt.

Das Makro NULL

Das Problem mit der Null-Zeiger-Konstante NULL ist es, dass sie sich nach int implizit konvertieren lässt. Auch nicht gerade schön.

Laut en.cppreference.com ist die Implementierung des Makros NULL abhängig von der konkreten Implementierung. Ein mögliche Implementierung ist:

#define NULL 0
//since C++11
#define NULL nullptr

 

Auf meiner Plattform scheint NULL aber vom Typ long int zu sein. Dazu gleich mehr. Der Umgang mit dem Makro NULL wirft bei mir einige Fragen auf.

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// nullMacro.cpp

#include <iostream>
#include <typeinfo>

std::string overloadTest(int){
  return "int";
}

std::string overloadTest(long int){
  return "long int";
}


int main(){

  std::cout << std::endl;
  
  int a= NULL;
  int* b= NULL;
  auto c= NULL;
  // std::cout << typeid(c).name() << std::endl;
  // std::cout << typeid(NULL).name() << std::endl;
  
  
  std::cout << "overloadTest(NULL)= " << overloadTest(NULL) << std::endl;
  
  std::cout << std::endl;

}

 

Die implizite Konvertierung nach int in Zeile 19 moniert der Compiler. Das ist nachvollziehbar. Deutlich mehr verwirrt mich aber die Warnung in der Zeile 21. Durch automatische Typableitung ermittelt der Compiler den Typ long int für die Variable c. Gleichzeitig stellt er aber fest, dass er in dem Ausdruck NULL konvertieren muss. Meine Beobachtung deckt sich auch mit dem Aufruf der Funktion overloadTest(NULL) in Zeile 26. in diesem Fall wählt der Compiler die Version in Zeile 10 für den Typ long int aus. Auf Plattformen, auf den NULL vom Typ int ist, wird der Compiler overloadTest für den Parametertyp int in Zeile 6 aufrufen. Alles im Rahmen des C++-Standards.

nullMacro

Nun interessiert mich der konkrete Typ der Null-Zeiger-Konstante NULL. Dazu kommentiere ich die Zeilen 22 und 23 des Programms aus.

nullMacroType

Für den Compiler ist NULL einerseits eine Konstante vom Typ long int, andererseits aber ein Zeiger-Konstante. Das zeigen die Warnungen aus der Kompilierung des Programms nullMacro.cpp.

Wenn dieser Abschnitt eines gezeigt hat, dann, dass das Makro NULL nicht verwendet werden soll. 

Die Rettung naht in Form des Null-Zeigers Literals nullptr.

Der Null-Zeiger nullptr

Mit den Mehrdeutigkeiten der Zahl 0 und dem Makro NULL hebt der nullptr auf. Er ist und bleibt eine Null-Zeiger-Konstante vom Typ std::nullptr_t.

Zeiger beliebigen Typs kann der Wert nullptr zugewiesen werden. Damit wird der Zeiger zu einem Null-Zeiger und verweist auf kein Datum. Ein solcher Null-Zeiger lässt sich natürlich nicht dereferenzieren. Zeiger dieses Typs lassen sich sowohl mit allen Zeigern und Zeigern auf Klassenmitglieder vergleichen und als auch zu allen Zeigern und Zeigern auf Klassenmitglieder implizit konvertieren. Zeiger dieses Typs lassen sich aber nicht zu integralen Typen - mit Ausnahme von bool - implizit konvertieren und mit ihnen vergleichen. Da sich nullptr implizit zu einem Wahrheitswert konvertieren lassen, können sie in logischen Ausdrücken verwendet werden.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// nullptr.cpp

#include <iostream>
#include <string>

std::string overloadTest(char*){
  return "char*";
}

std::string overloadTest(long int){
  return "long int";
}

int main(){

  std::cout << std::endl;

  long int* pi = nullptr;       
  // long int i= nullptr;       // ERROR
  auto nullp= nullptr;          // type std::nullptr_t
  
  bool b = nullptr;           
  std::cout << std::boolalpha << "b: "  << b << std::endl;
  auto val= 5;
  if ( nullptr < &val ){ std::cout << "nullptr < &val" << std::endl; }  

  // calls char*
  std::cout << "overloadTest(nullptr)= " <<  overloadTest(nullptr)<< std::endl;

  std::cout << std::endl;

}

  

Mit dem nullptr lässt sich ein Zeiger vom Typ long int (Zeile 18) initialisieren. Hingegen kann dieser in Zeile 18 nicht automatisch in einen long int Typ konvertiert werden. Interessant ist auch die automatische Typableitung in Zeile 20. Dadurch wird nullp zum Wert vom Typ std::nullptr_t. Die Null-Zeiger-Konstante verhält sich wie ein Wahrheitswert, der mit false initialisiert wurde. Das zeigen die Zeilen 22 - 25. Bekommt der nullptr die Wahl zwischen einem long int und einem Zeiger, so entscheidet er sich für den Zeiger (Zeile 28).

Zum Abschluss noch die Ausgabe.

nullptr 

 Die einfach Regel heißt. Verwende nullptr anstelle von 0 oder NULL. Immer noch nicht überzeugt? Dann muss ich mit einem stärkeren Geschütz aufwarten.

Generischer Code

Die Literale 0 und NULL offenbaren in generischem Code ihre wahre Natur. Dank (oder auch undank) Template Argument Ableitung stehen sie im Körper des Funktions-Templates nur noch als integrale Typen zur Verfügung. Kein Hinweis bleibt erhalten, dass sie ursprünglich Nullzeiger-Konstanten waren.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// generic.cpp

#include <cstddef>
#include <iostream>
 
template<class P >
void functionTemplate(P p){
  int* a= p;
}
 
int main(){
  int* a= 0;           
  int* b= NULL;              
  int* c= nullptr;
 
  functionTemplate(0);   
  functionTemplate(NULL);
  functionTemplate(nullptr);  
}

 

Zwar können 0 und NULL dazu verwendet werden, die int Zeiger in den Zeilen 12 und 13 zu initialisieren. Werden die Werte 0 und NULL aber als Argumente für das Funktions-Template verwendet, quittiert das der Compiler mit einer sehr deutlichen Fehlermeldung. Für den Compiler ist der Typ von 0 (Zeile 8) im Funktions-Template int, der Typ von NULL long int. Da verhält sich der nullptr deutlich berechenbarer. Sowohl in der Zeile 14 als auch in der Zeile 8 ist er vom Typ std::nullptr_t. 

 generic

Wie geht's weiter?

In den aktuellen Beiträgen habe ich viele Feature von C++11 vorgestellt, die deinen Code sicherer machen. Welche? Das zeigt der Abschnitt  Hohe Sicherheitsanforderungen  auf der Einstiegsseite.  Die entscheidende Idee all dieser Features ist es, die Intelligenz des Compilers geschickt zu nutzen. Damit folgen wir dem wichtigen Prinzip in C++: Kontrolle zur Übersetzungszeit ist besser als zur Laufzeit.

Mit diesem Artikel verlassen ich vorerst die Feature in C++, die aus sicherheitskritischen Aspekten besonders wichtig sind und wende mich der Performanz zu. Im nächsten Artikel werde ich mir das Schlüsselwort inline genauer anschauen. Dank inline kann der Compiler den Funktionsaufruf durch seinen Funktionskörper ersetzen. Dadurch wird der teure Aufruf der Funktion zur Laufzeit des Programms überflüssig. 

 

 

 

 

 

 

title page smalltitle page small Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library".   Hole dir dein E-Book. Unterstütze meinen Blog.

 

 

Tags: nullptr

Mentoring

Stay Informed about my Mentoring

 

Rezensionen

Tutorial

Besucher

Heute 1839

Gestern 3725

Woche 7640

Monat 34957

Insgesamt 3887671

Aktuell sind 595 Gäste und keine Mitglieder online

Kubik-Rubik Joomla! Extensions

Abonniere den Newsletter (+ pdf Päckchen)

Beiträge-Archiv

Sourcecode

Neuste Kommentare