Wie soll ich komplexe Projekte in C strukturieren? [closed]

Lesezeit: 7 Minuten

Ich habe kaum mehr als C-Kenntnisse auf Anfängerniveau und würde gerne wissen, ob es de facto “Standards” gibt, um eine etwas komplexe Anwendung in C zu strukturieren. Sogar GUI-basierte.

Ich habe immer das OO-Paradigma in Java und PHP verwendet und jetzt, wo ich C lernen möchte, habe ich Angst, dass ich meine Anwendungen falsch strukturiere. Ich bin ratlos, welchen Richtlinien ich folgen soll, um Modularität, Entkopplung und Trockenheit mit einer prozeduralen Sprache zu erreichen.

Haben Sie Leseempfehlungen? Ich konnte kein Anwendungsframework für C finden, selbst wenn ich keine Frameworks verwende, habe ich immer nette Ideen gefunden, indem ich ihren Code durchsuchte.

Benutzeravatar von mouviciel
mouviciel

Der Schlüssel ist die Modularität. Dies ist einfacher zu entwerfen, zu implementieren, zu kompilieren und zu warten.

  • Identifizieren Sie Module in Ihrer App, wie Klassen in einer OO-App.
  • Separate Schnittstelle und Implementierung für jedes Modul, fügen Sie nur das in die Schnittstelle ein, was von anderen Modulen benötigt wird. Denken Sie daran, dass es in C keinen Namensraum gibt, also müssen Sie alles in Ihren Schnittstellen eindeutig machen (z. B. mit einem Präfix).
  • Verbergen Sie globale Variablen in der Implementierung und verwenden Sie Zugriffsfunktionen zum Lesen/Schreiben.
  • Denken Sie nicht in Vererbung, sondern in Zusammensetzung. Versuchen Sie als allgemeine Regel nicht, C++ in C nachzuahmen, dies wäre sehr schwer zu lesen und zu warten.

Wenn Sie Zeit zum Lernen haben, schauen Sie sich an, wie eine Ada-App aufgebaut ist, mit ihrer obligatorischen package (Modulschnittstelle) und package body (Modulimplementierung).

Dies ist für die Codierung.

Für die Wartung (denken Sie daran, dass Sie einmal codieren, aber mehrmals warten) schlage ich vor, Ihren Code zu dokumentieren; Sauerstoff ist eine gute Wahl für mich. Ich schlage auch vor, eine starke Regressionstestsuite zu erstellen, die Ihnen eine Umgestaltung ermöglicht.

Benutzeravatar von j_random_hacker
j_random_hacker

Es ist ein weit verbreiteter Irrglaube, dass OO-Techniken nicht in C angewendet werden können. Die meisten können es — es ist nur so, dass sie etwas umständlicher sind als in Sprachen mit spezieller Syntax.

Eine der Grundlagen des robusten Systemdesigns ist die Kapselung einer Implementierung hinter einer Schnittstelle. FILE* und die Funktionen, die damit arbeiten (fopen(), fread() usw.) ist ein gutes Beispiel dafür, wie die Kapselung in C angewendet werden kann, um Schnittstellen einzurichten. (Da C keine Zugriffsbezeichner hat, können Sie natürlich nicht erzwingen, dass niemand in a hineinschaut struct FILEaber nur ein Masochist würde das tun.)

Bei Bedarf kann in C polymorphes Verhalten mit Tabellen von Funktionszeigern erreicht werden. Ja, die Syntax ist hässlich, aber der Effekt ist derselbe wie bei virtuellen Funktionen:

struct IAnimal {
    int (*eat)(int food);
    int (*sleep)(int secs);
};

/* "Subclass"/"implement" IAnimal, relying on C's guaranteed equivalence
 * of memory layouts */
struct Cat {
    struct IAnimal _base;
    int (*meow)(void);
};

int cat_eat(int food) { ... }
int cat_sleep(int secs) { ... }
int cat_meow(void) { ... }

/* "Constructor" */
struct Cat* CreateACat(void) {
    struct Cat* x = (struct Cat*) malloc(sizeof (struct Cat));
    x->_base.eat = cat_eat;
    x->_base.sleep = cat_sleep;
    x->meow = cat_meow;
    return x;
}

struct IAnimal* pa = CreateACat();
pa->eat(42);                       /* Calls cat_eat() */

((struct Cat*) pa)->meow();        /* "Downcast" */

  • Ein reiner C-Codierer würde sich beim Lesen dieses Codes verirren …

    – mouviciel

    19. März 2009 um 8:06 Uhr

  • @mouviciel: Müll! Die meisten C-Programmierer verstehen Funktionszeiger (oder sollten es zumindest), und darüber hinaus passiert hier eigentlich nichts. Zumindest unter Windows stellen sowohl Gerätetreiber als auch COM-Objekte ihre Funktionalität auf diese Weise bereit.

    – j_random_hacker

    19. März 2009 um 9:29 Uhr

  • Mir geht es nicht um Inkompetenz, sondern um unnötige Komplikationen. Funktionszeiger sind für einen C-Codierer üblich (z. B. Rückrufe), Vererbung nicht. Ich ziehe es vor, dass ein C++-Programmierer, der in C programmiert, seine Zeit nutzt, um C zu lernen, als Pseudo-C++-Klassen zu bauen. Allerdings kann Ihr Ansatz in einigen Fällen nützlich sein.

    – mouviciel

    19. März 2009 um 13:03 Uhr

  • Eigentlich kannst du sogar simulieren Zugriffsbezeichner mit dem C++-Pimpl-Idiom. Wenn die Privatgelände Mitglieder eines Typs in einem Typ gekapselt sind, der nur in der Implementierung sichtbar ist (auch bekannt als “die .c-Datei”), Benutzer der Schnittstelle werden es schwer haben, sie zu ändern (natürlich können sie Müll in den Pimpl-Zeiger schreiben, wenn sie dich verarschen wollen absichtlichaber Sie können dasselbe in C++ tun).

    – Bitmaske

    14. September 2011 um 0:27 Uhr

  • Downvoter: Möchten Sie einen Kommentar abgeben?

    – j_random_hacker

    17. Oktober 2013 um 16:48 Uhr

Alles gute Antworten.

Ich würde nur “Datenstruktur minimieren” hinzufügen. Dies könnte in C sogar einfacher sein, denn wenn C++ “C mit Klassen” ist, versucht OOP Sie zu ermutigen, jedes Substantiv / Verb in Ihrem Kopf zu nehmen und es in eine Klasse / Methode umzuwandeln. Das kann sehr verschwenderisch sein.

Angenommen, Sie haben ein Array von Temperaturmesswerten zu bestimmten Zeitpunkten und möchten diese als Liniendiagramm in Windows anzeigen. Windows hat eine PAINT-Nachricht, und wenn Sie sie erhalten, können Sie das Array durchlaufen, indem Sie LineTo-Funktionen ausführen und die Daten dabei skalieren, um sie in Pixelkoordinaten umzuwandeln.

Was ich viel zu oft gesehen habe, ist, dass die Leute, da das Diagramm aus Punkten und Linien besteht, eine Datenstruktur aufbauen, die aus Punktobjekten und Linienobjekten besteht, die jeweils in der Lage sind, sich selbst zu zeichnen, und diese dann auf der Grundlage der Theorie, dass das, dauerhaft ist irgendwie “effizienter” ist, oder dass sie vielleicht, nur vielleicht, in der Lage sein müssen, mit der Maus über Teile des Diagramms zu fahren und die Daten numerisch anzuzeigen, also bauen sie Methoden in die Objekte ein, um damit umzugehen, und das natürlich beinhaltet das Erstellen und Löschen von noch mehr Objekten.

Am Ende haben Sie also eine riesige Menge an Code, der ach so gut lesbar ist und lediglich 90 % seiner Zeit mit der Verwaltung von Objekten verbringt.

All dies geschieht im Namen von „guter Programmierpraxis“ und „Effizienz“.

Zumindest in C wird der einfache, effiziente Weg offensichtlicher und die Versuchung, Pyramiden zu bauen, weniger stark sein.

  • Liebe deine Antwort, und sie ist absolut wahr. OOP muss sterben!

    – Jo So

    18. Dezember 2017 um 23:34 Uhr

  • @JoSo: Ich benutze OOP, aber minimal.

    – Mike Dunlavey

    19. Dezember 2017 um 21:05 Uhr

  • Für mich zählt “minimal” (obwohl ich nicht weiß, was das für Sie bedeutet) nicht wirklich als OOP, was Objekt ist orientiert Programmierung – Objekte standardmäßig.

    – Jo So

    19. Dezember 2017 um 21:22 Uhr

  • Ich benutze auch etwas “OOP”. Hauptsächlich für meine C-Module, von denen viele init()- und exit()-Routinen haben.

    – Jo So

    19. Dezember 2017 um 21:23 Uhr

Das GNU-Codierungsstandards haben sich über ein paar Jahrzehnte entwickelt. Es wäre eine gute Idee, sie zu lesen, auch wenn Sie sie nicht buchstabengetreu befolgen. Wenn Sie über die darin angesprochenen Punkte nachdenken, erhalten Sie eine solidere Grundlage für die Strukturierung Ihres eigenen Codes.

Wenn Sie wissen, wie Sie Ihren Code in Java oder C++ strukturieren, können Sie den gleichen Prinzipien mit C-Code folgen. Der einzige Unterschied besteht darin, dass Sie den Compiler nicht an Ihrer Seite haben und alles extra sorgfältig manuell erledigen müssen.

Da es keine Pakete und Klassen gibt, müssen Sie damit beginnen, Ihre Module sorgfältig zu entwerfen. Der gebräuchlichste Ansatz besteht darin, für jedes Modul einen separaten Quellordner zu erstellen. Sie müssen sich auf Namenskonventionen verlassen, um Code zwischen verschiedenen Modulen zu unterscheiden. Stellen Sie beispielsweise allen Funktionen den Namen des Moduls voran.

Sie können keine Klassen mit C haben, aber Sie können einfach “Abstrakte Datentypen” implementieren. Sie erstellen eine .C- und .H-Datei für jeden abstrakten Datentyp. Wenn Sie es vorziehen, können Sie zwei Header-Dateien haben, eine öffentliche und eine private. Die Idee ist, dass alle Strukturen, Konstanten und Funktionen, die exportiert werden müssen, in die öffentliche Header-Datei gehen.

Ihre Werkzeuge sind auch sehr wichtig. Ein nützliches Werkzeug für C ist Fussel, was Ihnen helfen kann, schlechte Gerüche in Ihrem Code zu finden. Ein weiteres Tool, das Sie verwenden können, ist Doxygen, das Ihnen bei der Generierung helfen kann Dokumentation.

Nates Benutzeravatar
Nate

Kapselung ist immer der Schlüssel zu einer erfolgreichen Entwicklung, unabhängig von der Entwicklungssprache.

Ein Trick, den ich verwendet habe, um “private” Methoden in C zu kapseln, besteht darin, ihre Prototypen nicht in die “.h”-Datei aufzunehmen.

Benutzeravatar von Ivan Krechetov
Ivan Krechetov

Ich würde Ihnen vorschlagen, sich den Code eines beliebigen Open-Source-C-Projekts anzusehen, wie … hmm … Linux-Kernel oder Git; und sehen, wie sie es organisieren.

1418430cookie-checkWie soll ich komplexe Projekte in C strukturieren? [closed]

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy