Ich scheine immer Code in C zu schreiben, der hauptsächlich objektorientiert ist. Sagen wir also, ich hätte eine Quelldatei oder etwas, das ich erstellen würde. Dann übergebe ich den Zeiger auf diese Struktur an Funktionen (Methoden), die zu dieser Struktur gehören:
struct foo {
int x;
};
struct foo* createFoo(); // mallocs foo
void destroyFoo(struct foo* foo); // frees foo and its things
Ist das schlechte Praxis? Wie lerne ich, C "richtig" zu schreiben?
Antworten:
Nein, dies ist keine schlechte Praxis, es wird sogar empfohlen, dies zu tun, obwohl man sogar Konventionen wie
struct foo *foo_new();
und verwenden könntevoid foo_free(struct foo *foo);
Natürlich, wie es in einem Kommentar heißt, tun Sie dies nur, wo es angebracht ist. Es hat keinen Sinn, einen Konstruktor für ein zu verwenden
int
.Das Präfix
foo_
ist eine Konvention, die von vielen Bibliotheken befolgt wird, da es vor Konflikten mit der Benennung anderer Bibliotheken schützt. Andere Funktionen haben oft die Konvention zu verwendenfoo_<function>(struct foo *foo, <parameters>);
. Dies ermöglicht es Ihnenstruct foo
, ein undurchsichtiger Typ zu sein.Werfen Sie einen Blick auf die libcurl-Dokumentation der Konvention, insbesondere mit "Subnamespaces", damit der Aufruf einer Funktion
curl_multi_*
auf den ersten Blick falsch aussieht, als der erste Parameter von zurückgegeben wurdecurl_easy_init()
.Es gibt noch allgemeinere Ansätze, siehe Objektorientierte Programmierung mit ANSI-C
quelle
std::string
, konntest du nichtfoo::create
? Ich benutze kein C. Vielleicht ist das nur in C ++?Es ist nicht schlecht, es ist ausgezeichnet. Objektorientierte Programmierung ist eine gute Sache (es sei denn , Sie mitreißen, Sie können zu viel von einer guten Sache haben). C ist nicht die am besten geeignete Sprache für OOP, aber das sollte Sie nicht davon abhalten, das Beste daraus zu machen.
quelle
Es ist nicht schlecht. Es wird die Verwendung von RAII empfohlen , wodurch viele Fehler vermieden werden (Speicherverluste, Verwendung nicht initialisierter Variablen, Verwendung nach dem Freischalten usw., die Sicherheitsprobleme verursachen können).
Wenn Sie also Ihren Code nur mit GCC oder Clang kompilieren möchten (und nicht mit MS Compiler), können Sie
cleanup
Attribute verwenden, die Ihre Objekte ordnungsgemäß zerstören. Wenn Sie Ihr Objekt so deklarieren:Dann
my_str_destructor(ptr)
wird ausgeführt, wenn ptr Spielraum erlischt. Denken Sie daran, dass es nicht mit Funktionsargumenten verwendet werden kann .Denken Sie auch daran,
my_str_
in Ihren Methodennamen zu verwenden, daC
es keine Namespaces gibt und es leicht ist, mit einem anderen Funktionsnamen zu kollidieren.quelle
#define
Ihre Typnamen verwenden möchten, erhalten__attribute__((cleanup(my_str_destructor)))
Sie diese implizit im gesamten#define
Gültigkeitsbereich (sie werden allen Variablendeklarationen hinzugefügt).#define
Typ 'd oder Arrays davon). Kurzum: Es ist kein Standard-C, und Sie zahlen mit viel Inflexibilität im Einsatz.__attribute__((cleanup()))
ziemlich einen Quasi-Standard darstellt. Allerdings stehen b) und c) noch ...Ein solcher Code kann viele Vorteile haben, aber leider wurde der C-Standard nicht geschrieben, um ihn zu vereinfachen. Compiler haben in der Vergangenheit effektive Verhaltensgarantien geboten, die über die Anforderungen des Standards hinausgehen und es ermöglichten, solchen Code viel sauberer zu schreiben als dies in Standard C möglich ist. In letzter Zeit widerrufen Compiler solche Garantien jedoch im Namen der Optimierung.
Insbesondere haben viele C-Compiler in der Vergangenheit garantiert (von Entwurf bis Dokumentation), dass, wenn zwei Strukturtypen dieselbe anfängliche Sequenz enthalten, ein Zeiger auf einen der beiden Typen verwendet werden kann, um auf Mitglieder dieser gemeinsamen Sequenz zuzugreifen, auch wenn die Typen nicht in Beziehung stehen. und weiter, dass zum Zwecke des Erstellens einer gemeinsamen Anfangssequenz alle Zeiger auf Strukturen äquivalent sind. Code, der von einem solchen Verhalten Gebrauch macht, kann viel sauberer und typsicherer sein als Code, der dies nicht tut. Obwohl der Standard jedoch vorschreibt, dass Strukturen, die eine gemeinsame Anfangssequenz haben, auf die gleiche Weise angelegt werden müssen, verbietet er die tatsächliche Verwendung von Code Ein Zeiger eines Typs, um auf die ursprüngliche Sequenz eines anderen Typs zuzugreifen.
Wenn Sie objektorientierten Code in C schreiben möchten, müssen Sie sich (und sollten Sie diese Entscheidung frühzeitig treffen) entscheiden, entweder durch viele Rahmen zu springen, um die Zeigerregeln von C einzuhalten, und sich darauf vorzubereiten moderne Compiler generieren unsinnigen Code, wenn man aus dem Ruder läuft, auch wenn ältere Compiler Code generiert hätten, der wie vorgesehen funktioniert, oder dokumentieren eine Anforderung, dass der Code nur mit Compilern verwendet werden kann, die so konfiguriert sind, dass sie das Verhalten von Zeigern im alten Stil unterstützen (z. B. mit ein "-fno-strict-aliasing") Einige Leute betrachten "-fno-strict-aliasing" als böse, aber ich würde vorschlagen, dass es hilfreicher ist, sich "-fno-strict-aliasing" C als eine Sprache vorzustellen, die bietet für einige Zwecke eine größere semantische Kraft als "Standard" C,aber auf Kosten von Optimierungen, die für andere Zwecke wichtig sein könnten.
Bei herkömmlichen Compilern interpretieren historische Compiler beispielsweise den folgenden Code:
B. Ausführen der folgenden Schritte in der Reihenfolge: Inkrementieren des ersten Elements von
*p
, Ergänzen des niedrigsten Bits des ersten Elements von*t
, Dekrementieren des ersten Elements von*p
und Ergänzen des niedrigsten Bits des ersten Elements von*t
. Moderne Compiler ordnen die Abfolge der Operationen auf eine Weise neu, die effizienter ist, wennp
undt
verschiedene Objekte identifiziert werden, ändern jedoch das Verhalten, wenn dies nicht der Fall ist .Dieses Beispiel wurde natürlich absichtlich erfunden, und in der Praxis funktioniert Code, der einen Zeiger eines Typs verwendet, um auf Mitglieder zuzugreifen, die Teil der allgemeinen Anfangssequenz eines anderen Typs sind, normalerweise , aber leider gibt es keine Möglichkeit zu wissen, wann ein solcher Code fehlschlagen könnte Die sichere Verwendung ist nur durch Deaktivieren der typbasierten Aliasing-Analyse möglich.
Ein etwas weniger ausgeklügeltes Beispiel würde auftreten, wenn man eine Funktion schreiben möchte, um so etwas wie zwei Zeiger auf beliebige Typen zu tauschen. In der überwiegenden Mehrheit der "1990er C" -Compiler konnte dies erreicht werden durch:
In Standard C müsste man jedoch verwenden:
Wenn
*p2
der temporäre Zeiger im zugewiesenen Speicher verbleibt und nicht im zugewiesenen Speicher, wird der effektive Typ von*p2
zum Typ des temporären Zeigers und der Code, der versucht, ihn*p2
als einen beliebigen Typ zu verwenden, der nicht mit dem temporären Zeiger übereinstimmt type ruft Undefiniertes Verhalten auf. Es ist zwar äußerst unwahrscheinlich, dass ein Compiler so etwas bemerkt, aber da die moderne Compilerphilosophie verlangt, dass Programmierer um jeden Preis Undefiniertes Verhalten vermeiden, kann ich mir keine andere sichere Methode zum Schreiben des obigen Codes vorstellen, ohne zugewiesenen Speicher zu verwenden .quelle
foo_function(foo*, ...)
Pseudo-OO in C nur ein bestimmter API-Stil, der wie Klassen aussieht, aber es sollte besser als modulare Programmierung mit abstrakten Datentypen bezeichnet werden.Der nächste Schritt ist das Ausblenden der Strukturdeklaration. Sie fügen dies in die .h-Datei ein:
Und dann in der .c-Datei:
quelle