7 Stimmen

Wie lassen sich die Auswirkungen von implementierungsabhängigen Sprachmerkmalen in C++ begrenzen?

Der folgende Text ist ein Auszug aus dem Buch von Bjarne Stroustrup, The C++ Programming Language:

Abschnitt 4.6:

Einige Aspekte der grundlegenden Typen von C++, wie z. B. die Größe eines int, sind durch die Implementierung definiert (Abschnitt C.2). Ich weise auf diese Abhängigkeiten hin und empfehle oft, sie zu vermeiden oder Schritte zu unternehmen, um ihre Auswirkungen zu minimieren. Warum sollten Sie sich die Mühe machen? Menschen, die auf einer Vielzahl von Systemen programmieren oder eine Vielzahl von Compilern verwenden, sind sehr daran interessiert, weil sie sonst gezwungen sind, Zeit damit zu verschwenden, obskure Fehler zu finden und zu beheben. Diejenigen, die behaupten, dass sie sich nicht um die Portabilität kümmern, tun dies in der Regel, weil sie nur ein einziges System verwenden und glauben, dass sie sich die Einstellung leisten können, dass "die Sprache das ist, was mein Compiler implementiert". Dies ist eine enge und kurzsichtige Sichtweise. Wenn Ihr Programm ein Erfolg ist, wird es wahrscheinlich portiert werden, so dass jemand Probleme im Zusammenhang mit implementierungsabhängigen Funktionen finden und beheben muss. Außerdem müssen Programme oft mit anderen Compilern für dasselbe System kompiliert werden, und selbst eine zukünftige Version Ihres Lieblingscompilers könnte einige Dinge anders machen als der aktuelle. Es ist viel einfacher, die Auswirkungen von Implementierungsabhängigkeiten zu kennen und zu begrenzen, wenn ein Programm geschrieben wird, als zu versuchen, das Durcheinander hinterher zu entwirren.

Es ist relativ einfach, die Auswirkungen von implementierungsabhängigen Sprachmerkmalen zu begrenzen.

Meine Frage lautet: Wie lassen sich die Auswirkungen von implementierungsabhängigen Sprachmerkmalen begrenzen? Bitte erwähnen Sie implementierungsabhängige Sprachmerkmale und zeigen Sie dann, wie man ihre Auswirkungen begrenzen kann.

1voto

Eine der wichtigsten Möglichkeiten, die Abhängigkeit von bestimmten Datengrößen zu vermeiden, besteht darin, persistente Daten als Text und nicht binär zu lesen und zu schreiben. Wenn binäre Daten verwendet werden müssen, dann müssen alle Lese-/Schreiboperationen in einigen wenigen Methoden zentralisiert werden und Ansätze wie die hier bereits beschriebenen Typedefs verwendet werden.

Eine zweite Möglichkeit besteht darin, alle Warnungen des Compilers zu aktivieren, z. B. mit der Option -pedantisch Flag bei g++ warnt Sie vor vielen potentiellen Portabilitätsproblemen.

0voto

greyfade Punkte 24134

Wenn Sie sich Sorgen um die Übertragbarkeit machen, können Dinge wie die Größe eines int ohne große Schwierigkeiten bestimmt und gehandhabt werden. Viele C++-Compiler unterstützen auch C99-Funktionen wie die int-Typen: int8_t , uint8_t , int16_t , uint32_t usw. Wenn Ihr System diese nicht von Haus aus unterstützt, können Sie jederzeit die <cstdint> o <sys/types.h> die in den meisten Fällen über diese typedef Hrsg. <limits.h> enthält diese Definitionen für alle Grundtypen.

Die Norm garantiert nur die Mindestgröße eines Typs, auf die man sich immer verlassen kann: sizeof(char) < sizeof(short) <= sizeof(int) <= sizeof(long) . char muss mindestens 8 Bit betragen. short y int muss mindestens 16 Bit betragen. long muss mindestens 32 Bit betragen.

Andere Dinge, die durch die Implementierung definiert werden können, sind die ABI und die Namensverwechslungsschemata (das Verhalten von export "C++" speziell), aber wenn Sie nicht mit mehr als einem Compiler arbeiten, ist das normalerweise kein Problem.

0voto

Bill Punkte 11237

Das Folgende ist auch ein Auszug aus dem Buch von Bjarne Stroustrup, The C++ Programming Language:

Abschnitt 10.4.9:

Es werden keine implementierungsunabhängigen Garantien für die Reihenfolge der Konstruktion von nichtlokalen Objekten in verschiedenen Kompiliereinheiten gegeben. Zum Beispiel:

// file1.c:
    Table tbl1;
// file2.c:
    Table tbl2;

Ob tbl1 konstruiert wird, bevor tbl2 oder umgekehrt ist abhängig von der Implementierung. Es ist nicht einmal garantiert, dass die Reihenfolge in jeder bestimmten Implementierung festgelegt ist. Dynamisches Linking oder selbst eine kleine Änderung im Kompilierungsprozess kann die Reihenfolge verändern. Die Reihenfolge der Zerstörung ist ebenfalls von der Implementierung abhängig.

Ein Programmierer kann eine ordnungsgemäße Initialisierung sicherstellen, indem er die Strategie implementiert, die die Implementierungen normalerweise für lokale statische Objekte verwenden: einen First-Time-Switch. Zum Beispiel:

class Zlib {
    static bool initialized;
    static void initialize() { /* initialize */ initialized = true; }
public:
    // no constructor

    void f()
    {
        if (initialized == false) initialize();
        // ...
    }
    // ...
};

Wenn es viele Funktionen gibt, die den erstmaligen Wechsel testen müssen, kann dies mühsam sein, aber es ist oft zu bewältigen. Diese Technik stützt sich auf die Tatsache, dass statisch zugewiesene Objekte ohne Konstruktoren auf 0 . Der wirklich schwierige Fall ist der, in dem die erste Operation zeitkritisch sein kann, so dass der Aufwand für das Testen und die mögliche Initialisierung schwerwiegend sein kann. In diesem Fall ist ein weiterer Trick erforderlich (§ 21.5.2).

Ein alternativer Ansatz für ein einfaches Objekt besteht darin, es als Funktion darzustellen (§ 9.4.1):

int& obj() { static int x = 0; return x; } // initialized upon first use

Wer zum ersten Mal umschaltet, wird nicht mit jeder denkbaren Situation fertig. So ist es zum Beispiel möglich, Objekte zu erstellen, die sich während der Konstruktion aufeinander beziehen. Solche Beispiele sind am besten zu vermeiden. Wenn solche Objekte notwendig sind, müssen sie sorgfältig und schrittweise konstruiert werden.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X