[C++] Schutz vor Änderungen in Klassen

Kriz

Mitglied
Nabend allerseits,

ich möchte mal ein Thema zur Diskussion unter den erfahrenen C++'lern stellen, was mir schon seit Anbeginn meiner C++ Zeit Kopfschmerzen bereitet. Eventuell gibt es ja einige C++ Programmierer unter euch, die für das besagte Thema eine Lösung oder gar ein Workaround gefunden haben.

Es geht darum, daß die Sprachspezifikation von C++ gemäß den C Regeln ein "Modul" durch eine deklarierende Headerdatei und eine implementierende Quelldatei definiert. In die Headerdatei kloppen wir unsere Klassendeklaration usw. rein und in die Quelldatei die Methodenimplementierungen usw. Und in dieser Handhabung hat C++ einen bösen "Designfehler" zu verzeichnen...

Das Problem ist nun, daß ich beim Programmieren von Bibliotheken dem "Kunden" die Headerdateien und die vorkompilierte Bibliothek aushändige. Normalerweise laufe ich nicht Gefahr, daß der "Kunde" durch Änderungen an den Headerdateien sich unerlaubte Vorteile oder Zugriffe auf Bereiche der Bibliothek verschaffen kann. Wenn er beispielsweise also die Parameterliste eines Konstruktors ect. ändert und meint, damit irgendwelche Dinge bewirken zu können, die nicht vorgesehen waren, so landet er spätestens beim Kompilieren in der Kloschüssel, weil die Bibliothek bereits vorkompiliert mit entsprechenden Parameterlisten usw. vorliegt.

Allerdings gibt es in C++ ja diverse Möglichkeiten, Klassen so zu designen, daß sie bestimmten Richtlinien unterliegen. So verhindert man die Möglichkeit zur Ableitung von Kindklassen praktischerweise dadurch, indem man sowohl den Standard- als auch den Kopierkonstruktor als "private" deklariert (Java-Coder deklarieren Klassen als "final"). Und hier kommt nun das Problem:

Der "Kunde" kann nun mutwillig die Zugriffsrechte in der Headerdatei von private auf public setzen und so die Klasse wieder ableitbar machen, was nicht vorgesehen war (aus welchen Gründen auch immer). Da die Zugriffsrechte in der Quelldatei gemäß der Syntaxspezifikation von C++ nirgends an- bzw. wiedergegeben werden dürfen, sind sie auch innerhalb der Quelldatei mehr oder weniger "unbekannt". Nur was in der Klassendeklaration steht, hat Auswirkungen auf das Zugriffssystem. Beim erneuten Kompilieren mit der verfälschten Headerdatei meckert der Compiler nicht und der "Kunde" kann die Klasse nun vorsätzlich ableiten, d.h. er hat sie mutwillig ableitbar gemacht.

Leider gibt es nun Klassendesigns in Bibliotheken, die unbedingt solche Ableitungen verhindern MÜSSEN, um die gewünschte Wirkung haben zu können (z.B. Singletons oder reine Factorys). Ein Singleton, welches nachträglich ableitbar gemacht wird, ist per Definition kein Singleton mehr! Da der "Kunde" in Java-Fällen nur die Class- bzw. JAR-Dateien bekommt und nicht die Java-Dateien, ist man dort auf der sicheren Seite. In C++ hingegen kann man solche Definitionen aushebeln...

Also, kann man effektiv in C++ verhindern, daß nachträgliche Änderungen am Zugriffssystem einer Klasse Wirkung haben? Ich denke nein, aufgrund des "schlecht" durchdachten Designs der Sprache. Würde die Syntax erlauben, die Zugriffsqualifizierer auch in den Quelldateien vor jeder Klassenmethode inkl. der Konstruktoren usw. zu schreiben, dann könnte man dieses Problem sofort unterbinden. Jegliche Änderungen am Zugriffssystem seitens der Headerdatei würde einen Konflikt mit den Zugriffen in den Quelldateien beim Kompilieren erzeugen. Wie gesagt, würde...

Für eine anregende Diskussion mit eventuellen Lösungen oder Ansätzen habe ich nun ein sehr offenes Öhrchen :)
 
private, public, const, etc sind keine "Schutzmechanismen" um den Sourcecode vor Unbefugtem Zugriff zu schützen. Dies wird imho oft falsch verstanden. Es geht vielmehr darum bestimmte Aussagen über einzelne Teile der Klassen zu machen die dann vom Compiler geprüft werden können.

Wenn ich z.B. eine Variable als private Deklariere und dann versehentlich doch versuche darauf zuzugreifen, dann kann der Compiler dies bemerken und anmeckern. Je mehr solcher Aussagen ich über meine Klassen mache, sprich je genauer private, public, const usw gesetzt sind, desto weniger Flüchtigkeitsfehler kann ich machen, bzw. desto mehr zwingt mich der Compiler dazu sauber zu arbeiten, weil schnelle "Hacks" schon vom Compiler abgeschmettert werden.

Aber wie eingangs gesagt, diese Mechanismen dienen zur Hilfe bei der Programmierung, nicht zum Schutz des Sourcecodes. Beim Information-Hiding geht es nicht darum, den Sourcecode vor den Entwicklern zu verstecken, sondern ihnen klar zu sagen, Finger weg von diesen Variablen.

Von daher betrachte ich die beschreibene Situation nciht als Designfehler der Sprache, denn es war nie Absicht den Code vor unbefugtem Zugriff zu schützen.
 
Hallo,

das mit dem Zugriffssystem ist mir schon durchaus bekannt und welchem Zweck es dient auch, aber darum ging es mir ja hier nicht. ;)

Es ist ja nun unwiderlegbar so, daß C++ die Schutzfunktion zwischen Deklaration und Implementierung nur halbherzig umsetzt. Wie gesagt am Beispiel Singleton MUSS die Klasse ohne Wenn und Aber vor jeglicher Ableitung geschützt sein. Befindet sich nun in einer Bibliothek eine Singletonklasse, dann MUSS ich auch 100%ig sicher sein, daß Manipulationen an der distributierten Deklaration (sprich Header) durch Dritte keine Auswirkungen auf den definierten Zustand der Klasse haben!

Nur wenn ich aus ebendiesen Gründen in die Header schreibe, daß diese Klasse X nicht ableitbar sein darf, aber C++ "erlaubt" es Dritten dennoch sie ableitbar zu machen, dann habe ich als SW-Entwickler ein großes Problem, denn ich bin letztendlich dafür verantwortlich, daß die Integrität und Stabilität meiner Arbeit (Headers + Libs) auch jederzeit gewährleistet ist, sonst krieg ich vom Kunden und vom Chef schwer was auf den Deckel ;-)

Es gibt eine recht umständliche Methode (die zudem auch nicht 100%ig sicher ist), wie man ein solches Problem "lösen" kann (Stroustrup hat es auf seiner Tech-FAQ in ähnlicher Weise beschrieben), aber das löst ja nicht das Problem im Kern, sondern umgeht es nur auf unsicheren Wegen. Ich für mein Teil würde mich daher sehr freuen, wenn im zukünftigen C++0x Standard ein wirksamer Schutz vor mutwilligen oder achtlosen Manipulationen eingeführt werden würde. Man könnte sich sowas wie folgt vorstellen:

Code:
// Deklaration x.hpp

class X
{
    // Privater Teil
    X();
    X(const X&);
    ~X();

public:
    // Öffentlicher Teil
    ...
};

// Implementierung x.cpp

#include "x.hpp"

private X::X() {}

private X::X(const X &x) {}

private X::~X() {}
Würde der Compiler nun auf verschiedene Zugriffsdeklarationen stossen, dann müsste es eine Fehlermeldung geben. So ein Feature würde 1. Codestabilität gewährleisten und 2. auch achtlose Fehler Dritter bekämpfen, die aus Unwissenheit oder Neugier an den Headers rumspielen.

Der Mehraufwand beim Schreiben des Codes wäre mir ehrlich gesagt schnuppe, wenn ich eine Garantie dafür bekommen könnte, daß auch alles so funktioniert, wie ich es vorher eingeplant habe.
 
Hi,

Gegenfrage: Was hindert dich daran dem "Kunden" eine in sich abgeschlossene API inform einer DLL zu übergeben?
Dazu lieferst du die API-Dokumentation und der "Kunde" hat keine Möglichkeit den Source zu ändern weil er keinen Hat.

Daniel
 
Hi,

das Problem mit DLLs ist, daß sie nicht portabel sind. Ich brauche meistens eine Lösung, die portabel ist. Eine DLL gehört (leider) nicht dazu. Gut, auf *NIX-Systemen könnte man noch mit SOs arbeiten, aber was wäre mir wirklich exotischen Systemen?

(Ich möchte btw. nicht als "Proprietärsfreund" gelten, sowas liegt mir fern. In der Freizeit programmiere ich ausschließlich nur OpSo, aber im Beruf muß ich auf solche Dinge achten.)

Es ist schade, daß es keine gute Lösung für dieses Problem gibt. Eventuell sollte ich mal den Meister selber anmailen, was er dazu sagt. Falls ich eine Antwort bekommen sollte, poste ich die mal hier rein.
 
Nabend,

nein, da hast du was falsch verstanden. Es geht nicht darum, ob ich in C++ eine Klasse ableitbar machen kann oder nicht. Es geht nur darum, daß C++ jedermann die Möglichkeit einräumt, basierend auf den Headerdateien die Zugriffsrechte zu ändern und diese Änderungen automatisch auf den vorkompilierten Code einer Library übernommen wird - ob ich das nun möchte oder nicht. Wenn man nun in den Quellcodes ebenfalls die Zugriffsrechte nochmals verteilen müsste, dann könnte man solchen Manipulationen entgegenwirken.
 
Würde der Compiler nun auf verschiedene Zugriffsdeklarationen stossen, dann müsste es eine Fehlermeldung geben. So ein Feature würde 1. Codestabilität gewährleisten und 2. auch achtlose Fehler Dritter bekämpfen, die aus Unwissenheit oder Neugier an den Headers rumspielen.
Inwiefern würde das dein Problem lösen? Wenn ich dich richtig verstanden habe, bekommt der Benutzer den Quelltext der Implementation ja nicht. Ergo kann der Compiler diese Restriktion beim Benutzer auch nicht umsetzen.

Grüße,
Matthias
 
Hi,

ich demonstriere das ganze mal anhand eines Beispiels, eventuell wird es klarer dadurch:

Code:
/* Wir basteln uns eine Library namens "test.a".
Die Library soll an den Endkunden gehen. Er bekommt dazu
die Library und die benötigte Headerdatei "test.hpp"

Die ganze Klasse soll nur eine einzige, statische Methode
haben und sonst nicht instanzierbar sein, weder durch direkte
Instanzen noch durch Instanzkopien oder Zuweisungen. */

// test.hpp

class Test
{
    Test();
    Test(const Test&);
    ~Test();

    Test& operator=(const Test&);

public:
    static int getInteger();
};

// test.cpp

#include "test.hpp"

Test::Test() {}

Test::Test(const Test &t) {}

Test::~Test() {}

Test& Test::operator=(const Test &t)
{
    return *this;
}

int Test::getInteger()
{
    return 123;
}
Heraus kommt nun test.a als Library (hier unter GCC kompiliert, meinetwegen kann es auch test.lib sein, ist ja auch egal). Und nun ein Programm, welches test.a nutzt als Library.

Code:
// main.cpp

#include "test.hpp"

int main()
{
    Test test; // Geht nicht, da Konstruktor privat ist

    int i = Test::getInteger(); // Geht

    return 0;
}
So, jetzt kommt irgendso ein Heinz auf die Idee, in der Header "test.hpp" die Zugriffsrechte der gesamte Konstruktion/Destruktion usw. auf public zu setzen, was ich ja nicht verhindern kann:

Code:
// test.hpp

class Test
{
public:
    Test();
    Test(const Test&);
    ~Test();

    Test& operator=(const Test&);

public:
    static int getInteger();
};
Er läßt das Programm nochmals neukompilieren und siehe da... Er kann direkt von Test eine Instanz erzeugen. Die Objektcodes in der Library werden anstandslos von private in public transformiert, da C++ die Zugriffsrechte nicht in die Sources mitnimmt:

Code:
// main.cpp

#include "test.hpp"

int main()
{
    Test test; // Geht jetzt, da Konstruktor nun public ist

    int i = Test::getInteger(); // Geht

    return 0;
}
So sieht's aus. Und würde die Syntax von C++ vorschreiben, daß man die Zugriffsqualifizier nochmals im Sourcecode zur Verifizierung angeben müsste, dann würde der Compiler beim Rekompilieren mit geänderter Headerdatei streiken und melden, daß Deklaration und Implementierung zugriffsmäßig nicht identisch sind. Damit wäre mein Problem gelöst. Wie gesagt, wäre ;-)
 
Hi.

Also ich glaube irgendwie verwechselst du hier was. Bei deinem Beispiel hat der C++ Compiler ja überhaupt keine Quelldateien der Bibliothek mehr und kann eben nur anhand der Header-Datei die Zugriffsrechte validieren.

Wenn es solch einen Schutzmechanismus geben soll, müßte die Information ob ein Mitglied der Klasse public oder private ist in der Bibliothek selbst, also im Objektcode, hinterlegt sein.

Die Bibliothek wird aber erst in der Bindungs-Phase durch den Linker verarbeitet. Man könnte also höchstens eine neue Art von Name-Mangling erstellen die durch einen C++ Compiler implementiert wird. Dann würde es vom Linker bei unsachgemäßen Gebrauch /Mißbrauch des Headers eine unschöne Fehlermeldung a la "Cannot resolve external symbol blablabla" geben.

Meiner Meinung nach ist ein solcher Schutzmechanismus allerdings rein hypothetischer Natur und von geringem Nutzen. Man kann (übrigens auch in Java sehr leicht mittels Dekompiler oder per Reflection API) diese privaten Methoden wieder mit etwas mehr Aufwand als nur durch Ändern des Headers zugreifbar machen indem man dann die Symbole umbenennt.

Es sollte ausreichend sein wenn man dem Kunden untersagt Änderungen an den Headern vorzunehmen. Sollten dennoch Änderungen vorgenommen werden liegt ein Vertragsbruch vor und der Kunde ist sowieso auf sich allein gestellt.

Gruß
 

Neue Beiträge

Zurück