Freigeben über


Typumwandlungen und Typsicherheit

In diesem Dokument werden allgemeine Typkonvertierungsprobleme behandelt, und es wird beschrieben, wie Sie diese im C++-Code vermeiden können.

Beim Schreiben eines C++-Programms müssen Sie sicherzustellen, dass es typsicher ist. Das bedeutet, dass alle Variablen, Funktionsargumente und Rückgabewerte von Funktionen akzeptable Daten speichern und dass Vorgänge, bei denen Werte verschiedener Typen verwendet werden, einen „Sinn“ ergeben und nicht zu Datenverlust, falschen Interpretationen von Bitmustern oder Speicherschäden führen. Ein Programm, das nie explizit oder implizit Werte von einem Typ in einen anderen konvertiert, ist definitionsgemäß typsicher. Manchmal sind jedoch Typkonvertierungen oder sogar unsichere Konvertierungen erforderlich. Beispielsweise müssen Sie das Ergebnis eines Gleitkommavorgangs in einer Variablen des Typs intspeichern, oder Sie müssen den Wert in einer unsigned int Funktion übergeben, die eine signed int. Beide Beispiele veranschaulichen unsichere Konvertierungen, da sie zu Datenverlusten oder zur erneuten Interpretation eines Werts führen können.

Wenn der Compiler eine unsichere Konvertierung erkennt, wird entweder ein Fehler oder eine Warnung ausgegeben. Ein Fehler beendet die Kompilierung. Bei einer Warnung kann die Kompilierung fortgesetzt werden, es wird jedoch ein möglicher Fehler im Code angegeben. Auch wenn Ihr Programm ohne Warnungen kompiliert wird, kann es trotzdem noch Code enthalten, der zu impliziten Typkonvertierungen führt, welche falsche Ergebnisse erzeugen. Typfehler können auch durch explizite Konvertierungen oder Umwandlungen im Code verursacht werden.

Implizite Typkonvertierungen

Wenn ein Ausdruck Operanden verschiedener integrierter Typen enthält und keine expliziten Umwandlungen vorhanden sind, verwendet der Compiler integrierte Standardkonvertierungen , um einen der Operanden so zu konvertieren, dass die Typen übereinstimmen. Der Compiler testet die Konvertierungen in einer klar definierte Abfolge, bis eine erfolgreich ist. Wenn die ausgewählte Konvertierung eine Heraufstufung ist, gibt der Compiler keine Warnung aus. Wenn es sich bei der Konvertierung um eine Einschränkung handelt, gibt der Compiler eine Warnung zu möglichem Datenverlust aus. Ob wirklich ein Datenverlust auftritt, hängt von den verwendeten tatsächlichen Werten ab. Es wird jedoch empfohlen, diese Warnung als Fehler zu behandeln. Bei einem benutzerdefinierten Typ versucht der Compiler, die Konvertierungen zu verwenden, die Sie in der Klassendefinition angegeben haben. Wenn eine akzeptable Konvertierung nicht gefunden werden kann, gibt der Compiler einen Fehler aus und kompiliert das Programm nicht. Weitere Informationen zu den Regeln, die die Standardkonvertierungen regeln, finden Sie unter "Standardkonvertierungen". Weitere Informationen zu benutzerdefinierten Konvertierungen finden Sie unter User-Defined Konvertierungen (C++/CLI).

Erweiternde Konvertierungen (Heraufstufung)

Bei einer erweiternden Konvertierung wird ein Wert in einer kleineren Variable einer größeren Variable zugewiesen, ohne dass es zu einem Datenverlust kommt. Da die Verbreiterung immer sicher ist, führt der Compiler sie automatisch aus und gibt keine Warnungen aus. Die folgenden Konvertierungen sind erweiternde Konvertierungen.

Von Beschreibung
Beliebiger signed oder unsigned integraler Typ außer long long oder __int64 double
bool oder char Ein beliebiger anderer integrierter Typ
short oder wchar_t int, longlong long
int, long long long
float double

Einschränkende Konvertierungen (Koersion)

Der Compiler führt einschränkende Konvertierungen implizit aus, warnt jedoch vor möglichem Datenverlust. Nehmen Sie diese Warnungen ernst. Wenn Sie sicher sind, dass kein Datenverlust auftritt, da die Werte in der größeren Variable immer in die kleinere Variable passen, fügen Sie eine explizite Umwandlung hinzu, damit der Compiler keine Warnung mehr ausgibt. Wenn Sie nicht sicher sind, dass die Konvertierung sicher ist, fügen Sie Ihrem Code eine Art Laufzeitüberprüfung hinzu, um mögliche Datenverluste zu behandeln, damit Ihr Programm keine falschen Ergebnisse erzeugt.

Jede Konvertierung von einem Gleitkommatyp zu einem ganzzahligen Typ ist eine einschränkende Konvertierung, da der Bruchteil des Gleitkommawerts verworfen wird und verloren geht.

Das folgende Codebeispiel zeigt einige implizite einschränkende Konvertierungen sowie die Warnungen, die der Compiler dafür ausgibt.

int i = INT_MAX + 1; //warning C4307:'+':integral constant overflow
wchar_t wch = 'A'; //OK
char c = wch; // warning C4244:'initializing':conversion from 'wchar_t'
              // to 'char', possible loss of data
unsigned char c2 = 0xfffe; //warning C4305:'initializing':truncation from
                           // 'int' to 'unsigned char'
int j = 1.9f; // warning C4244:'initializing':conversion from 'float' to
              // 'int', possible loss of data
int k = 7.7; // warning C4244:'initializing':conversion from 'double' to
             // 'int', possible loss of data

Konvertierungen zwischen Typen mit und ohne Vorzeichen

Ein ganzzahliger Typ mit Vorzeichen und seine Entsprechung ohne Vorzeichen haben immer die gleiche Größe, sie unterscheiden sich jedoch in der Interpretation des Bitmusters für die Werttransformation. Das folgende Codebeispiel zeigt, was geschieht, wenn das gleiche Bitmuster als Wert mit Vorzeichen und als Wert ohne Vorzeichen interpretiert wird. Das sowohl in num als auch in num2 gespeicherte Bitmuster weicht nie von dem in der Abbildung oben Gezeigten ab.

using namespace std;
unsigned short num = numeric_limits<unsigned short>::max(); // #include <limits>
short num2 = num;
cout << "unsigned val = " << num << " signed val = " << num2 << endl;
// Prints: "unsigned val = 65535 signed val = -1"

// Go the other way.
num2 = -1;
num = num2;
cout << "unsigned val = " << num << " signed val = " << num2 << endl;
// Prints: "unsigned val = 65535 signed val = -1"

Beachten Sie, dass die Werte in beide Richtungen neu interpretiert werden. Wenn Ihr Programm seltsame Ergebnisse erzeugt, in denen das Vorzeichen des Werts anders als erwartet umgekehrt erscheint, suchen Sie nach impliziten Konvertierungen zwischen ganzzahligen Typen mit und ohne Vorzeichen. Im folgenden Beispiel wird das Ergebnis des Ausdrucks ( 0 - 1) implizit in intunsigned int den Speicherort numkonvertiert. Dies führt zu einer Neuinterpretation des Bitmusters.

unsigned int u3 = 0 - 1;
cout << u3 << endl; // prints 4294967295

Der Compiler warnt nicht vor impliziten Konvertierungen zwischen signierten und nicht signierten integralen Typen. Daher wird empfohlen, signierte zu nicht signierte Konvertierungen vollständig zu vermeiden. Wenn Sie sie nicht vermeiden können, fügen Sie eine Laufzeitüberprüfung hinzu, um zu ermitteln, ob der konvertierte Wert größer oder gleich Null und kleiner als oder gleich dem Maximalwert des signierten Typs ist. Werte in diesem Bereich werden von Typen mit Vorzeichen in Typen ohne Vorzeichen oder Typen ohne Vorzeichen in Typen mit Vorzeichen übertragen, ohne neu interpretiert zu werden.

Zeigerkonvertierungen

In vielen Ausdrücken wird ein Array im C-Format implizit in einen Zeiger auf das erste Element im Array konvertiert, und konstante Konvertierungen können automatisch ausgeführt werden. Dieser Prozess ist zwar sinnvoll, kann aber auch fehleranfällig sein. Beispielsweise scheint das folgende schlecht gestaltete Codebeispiel unsinnig zu sein, und dennoch wird es kompiliert und erzeugt ein Ergebnis von "p". Zuerst wird die literale Zeichenfolgenkonstante "Help" in einen char* konvertiert, der auf das erste Element des Arrays zeigt. Dieser Zeiger wird dann um drei Elemente erhöht, damit er auf das letzte Element "p" zeigt.

char* s = "Help" + 3;

Explizite Konvertierungen (Umwandlungen)

Mithilfe eines Umwandlungsvorgangs können Sie den Compiler anweisen, einen Wert eines bestimmten Typs in einen anderen Typ zu konvertieren. Der Compiler löst in einigen Fällen einen Fehler aus, wenn die beiden Typen vollständig nicht miteinander verknüpft sind, aber in anderen Fällen wird kein Fehler ausgelöst, auch wenn der Vorgang nicht typsicher ist. Verwenden Sie Umwandlungen möglichst selten, da Konvertierungen von einem Typ in einen anderen eine potenzielle Quelle für Programmfehler darstellen. Manchmal sind Umwandlungen jedoch erforderlich, und nicht alle Umwandlungen sind gleichermaßen gefährlich. Eine effektive Verwendung einer Umwandlung besteht darin, dass Ihr Code eine schmale Konvertierung ausführt und Sie wissen, dass die Konvertierung das Programm nicht dazu veranlasst, falsche Ergebnisse zu erzielen. Dadurch erfährt der Compiler, dass Sie wissen, was Sie tun, und wird angewiesen, keine weiteren Warnungen mehr dazu auszugeben. Eine weitere Verwendungsmöglichkeit ist die Umwandlung von Zeigern auf eine abgeleitete Klasse in Zeiger auf eine Basisklasse. Eine weitere Verwendung besteht darin, die Konstität einer Variablen zu entfernen, um sie an eine Funktion zu übergeben, die ein Nicht-Const-Argument erfordert. Die meisten dieser Umwandlungsvorgänge gehen mit gewissen Risiken einher.

Bei der Programmierung im C-Format wird der gleiche Umwandlungsoperator im C-Format für alle Arten von Umwandlungen verwendet.

(int) x; // old-style cast, old-style syntax
int(x); // old-style cast, functional syntax

Der Umwandlungsoperator im C-Format ist mit dem Aufrufoperator () identisch und daher unauffällig im Code und leicht zu übersehen. Beide sind schlecht, weil sie auf einen Blick schwer zu erkennen sind oder suchen, und sie sind unterschiedlich genug, um eine beliebige Kombination von static, und const.reinterpret_cast Zu ermitteln, was bei einer Umwandlung im alten Stil tatsächlich geschieht, kann kompliziert und fehleranfällig sein. Wenn eine Umwandlung erforderlich ist, empfiehlt es sich aus diesen Gründen, einen der folgenden C++-Umwandlungsoperatoren zu verwenden, die manchmal deutlich typsicherer sind und die Programmierabsicht genauer zum Ausdruck bringen:

  • static_cast, für Umwandlungen, die zur Kompilierungszeit überprüft werden. static_cast gibt einen Fehler zurück, wenn der Compiler erkennt, dass Sie zwischen Typen umwandeln möchten, die vollständig inkompatibel sind. Eine Verwendung für Umwandlungen zwischen Zeigern auf eine Basisklasse und Zeigern auf eine abgeleitete Klasse ist ebenfalls möglich. Für den Compiler ist jedoch nicht immer erkennbar, ob solche Konvertierungen zur Laufzeit sicher sind.

    double d = 1.58947;
    int i = d;  // warning C4244 possible loss of data
    int j = static_cast<int>(d);       // No warning.
    string s = static_cast<string>(d); // Error C2440:cannot convert from
                                       // double to std::string
    
    // No error but not necessarily safe.
    Base* b = new Base();
    Derived* d2 = static_cast<Derived*>(b);
    

    Weitere Informationen finden Sie unter static_cast.

  • dynamic_cast, für sichere, laufzeitgecheckte Umwandlungen von Zeiger-zu-Basis zu Zeiger-zu-Zeiger abgeleitet. Eine dynamic_cast ist sicherer als eine static_cast für Downcasts, aber die Laufzeitüberprüfung verursacht etwas Mehraufwand.

    Base* b = new Base();
    
    // Run-time check to determine whether b is actually a Derived*
    Derived* d3 = dynamic_cast<Derived*>(b);
    
    // If b was originally a Derived*, then d3 is a valid pointer.
    if(d3)
    {
       // Safe to call Derived method.
       cout << d3->DoSomethingMore() << endl;
    }
    else
    {
       // Run-time check failed.
       cout << "d3 is null" << endl;
    }
    
    //Output: d3 is null;
    

    Weitere Informationen finden Sie unter dynamic_cast.

  • const_cast, um die const-ness einer Variablen zu entfernen oder eine Nichtvariableconst zu konvertieren const. constUmwandlungsabwandlung -ness durch Verwendung dieses Operators ist genauso fehleranfällig wie die Verwendung eines C-Stil-Casts, außer dass bei const_cast Ihnen die Wahrscheinlichkeit, dass die Umwandlung versehentlich ausgeführt wird. Manchmal müssen Sie die const"-ness" einer Variablen entfernen, z. B. um eine const Variable an eine Funktion zu übergeben, die einen Nicht-Parameterconst verwendet. Das folgende Beispiel zeigt die erforderliche Vorgehensweise.

    void Func(double& d) { ... }
    void ConstCast()
    {
       const double pi = 3.14;
       Func(const_cast<double&>(pi)); //No error.
    }
    

    Weitere Informationen finden Sie unter const_cast.

  • reinterpret_cast, für Umwandlungen zwischen nicht verknüpften Typen wie einem Zeigertyp und einem int.

    Hinweis

    Dieser Umwandlungsoperator wird nicht so oft verwendet wie die anderen, und es ist nicht garantiert, dass er für andere Compiler portierbar ist.

    Das folgende Beispiel veranschaulicht, wie reinterpret_cast sich die Unterschiede unterscheiden static_cast.

    const char* str = "hello";
    int i = static_cast<int>(str);//error C2440: 'static_cast' : cannot
                                  // convert from 'const char *' to 'int'
    int j = (int)str; // C-style cast. Did the programmer really intend
                      // to do this?
    int k = reinterpret_cast<int>(str);// Programming intent is clear.
                                       // However, it is not 64-bit safe.
    

    Weitere Informationen finden Sie unter reinterpret_cast "Operator".

Siehe auch

C++-Typsystem
Willkommen bei C++
C++-Sprachreferenz
C++-Standardbibliothek