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


79

Ich habe kaum mehr als C-Kenntnisse für Anfänger und möchte 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, da ich C lernen möchte, befürchte ich, dass ich meine Anwendungen falsch strukturieren könnte. Ich weiß nicht, welche Richtlinien ich befolgen muss, um Modularität, Entkopplung und Trockenheit mit einer prozeduralen Sprache zu erreichen.

Haben Sie Lesungen vorzuschlagen? Ich konnte kein Anwendungsframework für C finden, auch wenn ich keine Frameworks verwende. Ich habe immer gute Ideen gefunden, indem ich deren Code durchsucht habe.


3
Die Unix-Philosophie ist sehr nützlich für die Organisation großer Projekte: http://www.faqs.org/docs/artu/ch01s06.html
newDelete

Antworten:


51

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, z. B. Klassen in einer OO-App.
  • Separate Schnittstelle und Implementierung für jedes Modul. Geben Sie nur die Schnittstelle ein, die von anderen Modulen benötigt wird. Denken Sie daran, dass es in C keinen Namespace gibt, daher müssen Sie alles in Ihren Schnittstellen eindeutig machen (z. B. mit einem Präfix).
  • Verstecken Sie globale Variablen in der Implementierung und verwenden Sie Accessor-Funktionen zum Lesen / Schreiben.
  • Denken Sie nicht in Vererbung, sondern in Zusammensetzung. Versuchen Sie in der Regel nicht, C ++ in C nachzuahmen. Dies ist sehr schwer zu lesen und zu warten.

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

Dies dient zur Codierung.

Zur Pflege (denken Sie daran, dass Sie einmal codieren, aber mehrmals pflegen) schlage ich vor, Ihren Code zu dokumentieren. Sauerstoff ist eine gute Wahl für mich. Ich schlage auch vor, eine starke Regressionstestsuite zu erstellen, mit der Sie umgestalten können.


30

Es ist ein weit verbreitetes Missverständnis, dass OO-Techniken in C nicht angewendet werden können. Die meisten können - es ist nur so, dass sie etwas unhandlicher sind als in Sprachen mit Syntax für den Job.

Eine der Grundlagen eines robusten Systemdesigns ist die Kapselung einer Implementierung hinter einer Schnittstelle. FILE*und die Funktionen , die mit ihm arbeiten ( fopen(), fread()etc.) ist ein gutes Beispiel dafür , wie Einkapselung kann in C angewandt werden Schnittstellen zu etablieren. (Da C keine Zugriffsspezifizierer hat, können Sie natürlich nicht erzwingen, dass niemand in ein hineinschaut struct FILE, aber nur ein Masochist würde dies tun.)

Bei Bedarf kann in C mithilfe von Tabellen mit Funktionszeigern polymorphes Verhalten festgestellt werden. Ja, die Syntax ist hässlich, aber der Effekt ist der gleiche 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 = (Cat*) malloc(sizeof (struct Cat));
    x->_base.eat = cat_eat;
    x->_base.sleep = cat_sleep;
    x->meow = cat_meow;
}

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

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

11
Ein reiner C-Codierer würde beim Lesen dieses Codes verloren gehen ...
mouviciel

15
@mouviciel: Müll! Die meisten C-Codierer verstehen Funktionszeiger (oder sollten es zumindest), und darüber hinaus ist hier nichts wirklich los. Zumindest unter Windows bieten sowohl Gerätetreiber als auch COM-Objekte ihre Funktionalität auf diese Weise.
j_random_hacker

8
Bei meinem Punkt 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 bevorzuge, dass ein C ++ - Codierer, der in C codiert, seine Zeit nutzt, um C zu lernen, als um Pseudo-C ++ - Klassen zu erstellen. In einigen Fällen kann Ihr Ansatz jedoch hilfreich sein.
Mouviciel

3
Tatsächlich können Sie sogar Zugriffsspezifizierer mit der C ++ - Pimpl-Sprache simulieren . Wenn die privaten Mitglieder eines Typs in einem Typ gekapselt sind, der nur in der Implementierung sichtbar ist (auch bekannt als "die .c-Datei"), fällt es Benutzern der Benutzeroberfläche schwer, sie zu ändern (natürlich können sie Müll in die Datei schreiben pimpl Zeiger, wenn sie Sie absichtlich schrauben wollen , aber Sie können das gleiche in C ++ tun).
Bitmaske

2
Downvoter: Möchtest du einen Kommentar abgeben?
j_random_hacker

15

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 zu bestimmten Zeitpunkten eine Reihe von Temperaturmesswerten und möchten diese in Windows als Liniendiagramm anzeigen. Windows verfügt über eine PAINT-Nachricht. Wenn Sie diese erhalten, können Sie das Array mit LineTo-Funktionen durchlaufen und die Daten skalieren, während Sie sie in Pixelkoordinaten konvertieren.

Was ich viel zu oft gesehen habe, ist, dass Menschen, 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 nach der Theorie, dass dies der Fall ist, dauerhaft machen ist irgendwie "effizienter", oder dass sie möglicherweise in der Lage sein müssen, über Teile des Diagramms zu fahren und die Daten numerisch anzuzeigen, damit sie Methoden in die Objekte einbauen, um damit umzugehen, und das natürlich. beinhaltet das Erstellen und Löschen von noch mehr Objekten.

Sie erhalten also eine riesige Menge an Code, der ach so lesbar ist und lediglich 90% der Zeit für die Verwaltung von Objekten benötigt.

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.


1
Liebe deine Antwort und es ist absolut wahr. OOP muss sterben!
Jo So

@JoSo: Ich benutze OOP, aber minimal.
Mike Dunlavey

Für mich zählt "minimal" (obwohl ich nicht weiß, was das für Sie bedeutet) nicht wirklich als OOP, was objektorientierte Programmierung ist - Objekte standardmäßig.
Jo So

Ich benutze auch etwas "OOP". Hauptsächlich für meine C-Module, von denen viele die Routinen init () und exit () haben.
Jo So

11

Die GNU-Codierungsstandards haben sich über einige Jahrzehnte weiterentwickelt. Es wäre eine gute Idee, sie zu lesen, auch wenn Sie ihnen nicht genau folgen. Wenn Sie über die darin angesprochenen Punkte nachdenken, erhalten Sie eine genauere Grundlage für die Strukturierung Ihres eigenen Codes.


4
Nicht jeder mag sie, von lxr.linux.no/linux+v2.6.29/Documentation/CodingStyle : "Zunächst würde ich vorschlagen, eine Kopie der GNU-Codierungsstandards auszudrucken und NICHT zu lesen. Brennen Sie sie, es ist eine große symbolische Geste ". Ich habe sie seit vielen Jahren nicht mehr gelesen, aber Linus hat einige berechtigte Einwände.
Hlovdal

1
@hlovdal: Natürlich mag nicht jeder einen bestimmten Codierungsstandard, deshalb gibt es mehr als einen Standard für ähnliche Anwendungsfälle. Der wichtige Teil ist, dass Sie innerhalb Ihrer eigenen Projekte konsistent sind, dass zumindest ein gewisser Standard eingehalten wird, und nicht de facto Ad-hoc-Inkonsistenzen.
JM Becker

4

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

Da es keine Pakete und Klassen gibt, müssen Sie zunächst Ihre Module sorgfältig entwerfen. Am häufigsten wird für jedes Modul ein separater Quellordner erstellt. 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 für jeden abstrakten Datentyp eine .C- und .H-Datei. Wenn Sie möchten, 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 verschoben werden.

Ihre Werkzeuge sind auch sehr wichtig. Ein nützliches Werkzeug für C sind Flusen , mit denen Sie schlechte Gerüche in Ihrem Code finden können. Ein weiteres Tool, das Sie verwenden können, ist Doxygen, mit dem Sie Dokumentation erstellen können .


4

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

Ein Trick, mit dem ich "private" Methoden in C gekapselt habe, besteht darin, ihre Prototypen nicht in die ".h" -Datei aufzunehmen.


3

Ich würde Ihnen empfehlen, den Code eines beliebten Open-Source-C-Projekts wie ... hmm ... Linux-Kernel oder Git zu überprüfen. und sehen, wie sie es organisieren.



2

Ich würde vorschlagen, als ersten Schritt ein C / C ++ - Lehrbuch zu lesen. Zum Beispiel ist C Primer Plus eine gute Referenz. Wenn Sie sich die Beispiele ansehen, erhalten Sie eine Vorstellung davon, wie Sie Ihr Java OO einer prozeduraleren Sprache wie C zuordnen können.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.