Wie man ein C-Programm effektiv strukturiert

Lesezeit: 5 Minuten

Lassen Sie mich zunächst sagen, dass ich sowohl in C als auch in C++ ziemlich viel Erfahrung habe. Ich starte jedoch ein neues Projekt in C und arbeite schon so lange mit objektorientierten Sprachen (C# und C++), dass ich Probleme habe, eine effektive Möglichkeit zu finden, Funktionalität in einer prozeduralen Sprache zu kapseln. Mein erster Gedanke war, einfach auf mein OO-Wissen zurückzugreifen und es etwa so zu strukturieren:

struct Foo 
{
    int x;
    char *y;
}; 

struct Foo *new_Foo()
{
    return (struct Foo *)malloc(sizeof(struct Foo));
} 

void Foo_member_function(struct Foo *foo, int z)
{
    foo->x = z;    
}

Aber das scheint einfach langweilig und widerspricht dem Geist von C. Ganz zu schweigen davon, dass es ein OO eines armen Mannes ist.

Dieses Programm wird letztendlich ziemlich umfangreich werden, daher ist es wichtig, von einer guten Designorganisation auszugehen. Ich kann mir vorstellen, dass sich mit den Jahren der Entwicklung in C bestimmte Entwurfsmuster entwickelt haben, wie der Code für die Wartbarkeit am besten strukturiert werden kann. Ähnlich wie bei der funktionalen Programmierung hoffe ich, dass die prozedurale Programmierung ein Paradigma hat, das sauber und einigermaßen lesbar ist.

Verweise auf relevante Artikel und Bücher sind ebenfalls akzeptabel.

  • Ich hasse es, nur eine Antwort auszuwählen, weil sie alle nützliche Leckerbissen enthalten, aber die Opaque Pointer-Referenz war wahrscheinlich das Nützlichste, was mir nicht bewusst war. Vielen Dank!

    – Hans van Slooten

    7. Juni 2009 um 13:04 Uhr

Dies ist eine ganz normale und vernünftige Praxis. Versuchen Sie jedoch, das Struct-Layout nicht in Header-Dateien offenzulegen, damit Sie eine gewisse Flexibilität bei der Implementierung haben und Ihre Abhängigkeiten besser verwalten können.

Sehen Undurchsichtiger Zeiger für mehr Details.

Was Sie vorschlagen, ist die Art und Weise, wie ich früher immer C-Programme geschrieben habe, als ich so etwas gemacht habe. Ich denke nicht, dass es “poor mans OO” ist, ich denke, es ist eine vernünftige prozedurale Programmierpraxis.

Ich würde ein paar Dinge über Ihren C-Code beobachten:

  • Verwenden Sie typedefs mit Struct-Definitionen, damit Sie das Schlüsselwort „struct“ nicht über den gesamten Code verteilen müssen
  • Verwenden Sie nur Umwandlungen, wenn sie tatsächlich benötigt werden – die Umwandlung des Rückgabewerts von malloc() ist unnötig

  • Gute Tipps. Was ich mit “OO des armen Mannes” meinte, war, dass es ein sehr begrenztes OO war (dh kein Polymorphismus, keine Kapselung usw.).

    – Hans van Slooten

    7. Juni 2009 um 12:22 Uhr

  • Beachten Sie jedoch, dass Typedefs von einigen als schlechte Praxis angesehen werden, einschließlich der Linux-Kernel-Crew

    – temp2290

    23. Oktober 2009 um 21:24 Uhr

Hmmm … Früher haben wir nur Namenskonventionen verwendet … Ergo: str* macht Sachen mit welcher gemeinsamen Datenstruktur? Nehmen Sie also vielleicht einfach die C#-Syntax und s/./_/g?

  • foo_constructor
  • foo_destructor
  • foo_someMethod
  • foo_someMethod2 // ist keine Überladung in ANSI C
  • foo_otherMethod

… und es gibt kein Erbe …

  • foo2_constructor
  • foo2_destructor
  • foo2_someMethod // und es gibt keinen Polymorphismus

Aber schauen Sie auf die positive Seite … Sie können Zeiger-zu-Zeiger-zu-Zeiger-zu-Funktion verwenden, die einen Zeiger-auf-Zeiger-int zurückgibt! O Freude!

Mein bester Rat ist, die Lektionen von Java (und daraus abgeleitet C#) zu lernen und Ihre Bibliotheken so zu strukturieren, dass sie KEINE Nebenwirkungen haben … mehr typdefs == weniger Kopfschmerzen … und wenn Sie trainieren, wie Sie diesem weisen Rat folgen lass es mich wissen, bitte 😉

Prost. Keith.

  • Ich nehme an, Sie meinen mit “Nebenwirkungen” statische Variablen, Globals usw.? Offensichtlich würden die Bibliotheken Seiteneffekte auf die “Objekte” haben, die sie modifizieren.

    – Hans van Slooten

    7. Juni 2009 um 12:59 Uhr

  • Im Allgemeinen können Sie einen neuen Status zurückgeben, anstatt einen vorhandenen zu mutieren, um einen Status zu aktualisieren. Dies entspricht der funktionalen Programmierung und macht die Abhängigkeiten des Datenflusses deutlicher und leichter nachvollziehbar.

    – keiner

    7. Juni 2009 um 13:52 Uhr

Das ist eine ziemlich vernünftige Art, ein C-Programm zu schreiben. Es gibt eine andere große Anwendung, die so ziemlich das Gleiche macht – den Linux-Kernel. Einige fast OO-Features, die darin verwendet werden:

  • Strukturen und Operationen auf Strukturen für die Kapselung wie in Ihrem Beispiel
  • Verweise auf Basisstrukturen als eine Form der Erbschaft des armen Mannes – Sie werden dort jede Menge Verweise auf struct kobject finden
  • Makros zum Generieren von Funktionen als Ersatz für Template-Programmierung

C war eine Low-Level-Sprache und in dieser Hinsicht wäre es sehr nützlich, Ihre Datenstrukturen gemäß Ihren Codefunktionen und -modulen zu organisieren.

Ich würde vorschlagen, dass Sie Typedefs und Aufzählungen überall dort verwenden, wo Sie Datenobjekte erstellen möchten. Verwenden Sie Makros oder statische Funktionen, um nach Bedarf zu initialisieren, zuzuweisen und zu „zerstören“.

Benutzer-Avatar
Larry Watanabe

Ich stimme den obigen Vorschlägen zu. Du machst es am besten .. wenn du in C programmieren willst.

Natürlich könnten Sie einen Präprozessor schreiben, um diese Deklarationen und Dinge automatisch für Sie zu generieren. Verwenden Sie möglicherweise eine “Klassen” -Deklaration

Aber was wir hier haben, ist ein einfacher C++-zu-C-Compiler. Warum nicht einfach in C++ programmieren, einen echten C++-Compiler verwenden, saubere Schnittstellen verwenden und einfach den C++-Code mit dem C-Code verknüpfen? Was ist der Grund, warum Sie sowieso in C vs. C++ codieren müssen? Oder generieren Sie bei Bedarf C-Code aus dem Compiler und kompilieren Sie den ausgegebenen C-Code zusammen mit allem, was Sie sonst noch benötigen.

Benutzer-Avatar
Johannes Bellone

Ich arbeite seit einiger Zeit an einem Projekt, bei dem die Bibliothek in C sein muss, aber ich möchte eine Form von OO-Funktionalität haben. Ich mache etwas Ähnliches mit ein wenig mehr Details.

struct klass {
  char * value;

  void (*set_value) (struct klass *, const char *);
  void (*destroy) (struct klass *);
};

static void
klass_method_set_value (struct klass * k, const char * value) {
  if (k->value == NULL) {
  }
}

static void 
klass_object_desetroy (struct klass * k) {
  free (k);
  k = NULL;
}

static void
klass_method_destroy (struct klass * k) {
  klass_object_destroy (k);
}

static struct klass *
klass_object_init (void) {
  struct klass * obj = (struct klass *) malloc (sizeof (struct klass*) );

  /* members */
  obj->value = NULL;

  /* methods */
  obj->set_value = klass_method_set_value;
  obj->destroy = klass_method_destroy;
  return obj;
}

struct klass * 
klass_new (void) {
  return klass_object_init ();
}

Vergib mir, wenn etwas nicht stimmt; etwas schnell geschrieben.

  • Ja, das wollte ich irgendwie vermeiden. Ich habe jedoch gehört, dass Stroustrup den ersten C++-Compiler genau dafür programmiert hat.

    – Hans van Slooten

    7. Juni 2009 um 21:07 Uhr

1382410cookie-checkWie man ein C-Programm effektiv strukturiert

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

Privacy policy