Modulare Ansätze sind im Allgemeinen ziemlich praktisch (portabel und sauber), daher versuche ich, Module so unabhängig wie möglich von anderen Modulen zu programmieren. Die meisten meiner Ansätze basieren auf einer Struktur, die das Modul selbst beschreibt. Eine Initialisierungsfunktion legt die primären Parameter fest. Anschließend wird ein Handler (Zeiger auf die beschreibende Struktur) an die aufgerufene Funktion innerhalb des Moduls übergeben.
Im Moment frage ich mich, was der beste Ansatz für die Zuweisung von Speicher für die Struktur sein kann, die ein Modul beschreibt. Wenn möglich, würde ich mir Folgendes wünschen:
- Undurchsichtige Struktur, daher kann die Struktur nur durch die Verwendung der bereitgestellten Schnittstellenfunktionen geändert werden
- Mehrere Instanzen
- Vom Linker zugewiesener Speicher
Ich sehe folgende Möglichkeiten, die alle mit einem meiner Ziele in Konflikt stehen:
globale Erklärung
mehrere Instanzen, die vom Linker zugeordnet werden, aber struct ist nicht undurchsichtig
(#includes)
module_struct module;
void main(){
module_init(&module);
}
Malloc
undurchsichtige Struktur, mehrere Instanzen, aber Zuteilung auf Haufen
in module.h:
typedef module_struct Module;
in der Init-Funktion von module.c malloc und Rückgabezeiger auf den zugewiesenen Speicher
module_mem = malloc(sizeof(module_struct ));
/* initialize values here */
return module_mem;
in main.c
(#includes)
Module *module;
void main(){
module = module_init();
}
Erklärung im Modul
opake Struktur, vom Linker zugewiesen, nur eine vordefinierte Anzahl von Instanzen
Behalten Sie die gesamte Struktur und den gesamten Speicher innerhalb des Moduls und machen Sie niemals einen Handler oder eine Struktur verfügbar.
(#includes)
void main(){
module_init(_no_param_or_index_if_multiple_instances_possible_);
}
Gibt es eine Möglichkeit, diese für eine undurchsichtige Struktur, einen Linker anstelle einer Heap-Zuordnung und mehrere / beliebig viele Instanzen zu kombinieren?
Lösung
Wie in den folgenden Antworten vorgeschlagen, ist meiner Meinung nach der beste Weg:
- Reservieren Sie Speicherplatz für MODULE_MAX_INSTANCE_COUNT Module in der Modulquelldatei
- Definieren Sie MODULE_MAX_INSTANCE_COUNT nicht im Modul selbst
- Fügen Sie der Modulheaderdatei den Fehler #ifndef MODULE_MAX_INSTANCE_COUNT # hinzu, um sicherzustellen, dass der Modulbenutzer diese Einschränkung kennt und die maximale Anzahl der für die Anwendung gewünschten Instanzen definiert
- Bei der Initialisierung einer Instanz wird entweder die Speicheradresse (* void) der beschreibenden Struktur oder der Modulindex (was auch immer Sie mehr wollen) zurückgegeben.
Antworten:
Sicher gibt es. Erkennen Sie jedoch zunächst, dass die "beliebige Anzahl" von Instanzen zur Kompilierungszeit festgelegt oder zumindest eine Obergrenze festgelegt werden muss. Dies ist eine Voraussetzung für die statische Zuordnung der Instanzen (was Sie als "Linker-Zuordnung" bezeichnen). Sie können die Zahl ohne Quellenänderung anpassen, indem Sie ein Makro deklarieren, das sie angibt.
Dann deklariert die Quelldatei, die die eigentliche Strukturdeklaration und alle zugehörigen Funktionen enthält, auch ein Array von Instanzen mit interner Verknüpfung. Es bietet entweder ein Array mit externen Verknüpfungen von Zeigern auf die Instanzen oder eine Funktion für den Zugriff auf die verschiedenen Zeiger über den Index. Die Funktionsvariante ist etwas modularer:
module.c
Vermutlich kennen Sie sich bereits damit aus, wie der Header die Struktur als unvollständigen Typ deklariert und alle Funktionen (in Form von Zeigern auf diesen Typ) deklariert . Beispielsweise:
module.h
Jetzt
struct module
ist es in anderen Übersetzungseinheiten als * undurchsichtigmodule.c
, und Sie können auf die Anzahl der Instanzen zugreifen und sie verwenden, die zum Zeitpunkt der Kompilierung definiert wurden, ohne dass eine dynamische Zuordnung erforderlich ist.* Natürlich, es sei denn, Sie kopieren die Definition. Der Punkt ist, dass
module.h
das nicht geht.quelle
Ich programmiere kleine Mikrocontroller in C ++, die genau das erreichen, was Sie wollen.
Was Sie ein Modul nennen, ist eine C ++ - Klasse, die Daten (entweder von außen zugänglich oder nicht) und Funktionen (ebenfalls) enthalten kann. Der Konstruktor (eine dedizierte Funktion) initialisiert sie. Der Konstruktor kann Laufzeitparameter oder (meine bevorzugten) Kompilierungszeitparameter (Vorlagenparameter) verwenden. Die Funktionen innerhalb der Klasse erhalten implizit die Klassenvariable als ersten Parameter. (Oder, oftmals nach meinem Geschmack, kann die Klasse als versteckter Singleton fungieren, sodass auf alle Daten ohne diesen Overhead zugegriffen wird.)
Das Klassenobjekt kann global (damit Sie zum Zeitpunkt der Verknüpfung wissen, dass alles passt) oder stapellokal sein, vermutlich im Hauptbereich. (Ich mag C ++ - Globals wegen der undefinierten globalen Initialisierungsreihenfolge nicht, deshalb bevorzuge ich Stack-Local).
Mein bevorzugter Programmierstil ist, dass Module statische Klassen sind und ihre (statische) Konfiguration durch Vorlagenparameter erfolgt. Dies vermeidet fast alles Überflüssige und ermöglicht eine Optimierung. Kombiniere dies mit einem Tool, das die Stapelgröße berechnet und du kannst ohne Sorgen schlafen :)
Mein Vortrag über diese Art der Codierung in C ++: Objekte? Nein Danke!
Viele Embedded / Mikrocontroller Programmierer scheinen C zu mögen ++ , weil sie denken , dass es sie zwingen würde , verwenden alle von C ++. Das ist absolut nicht nötig und wäre eine sehr schlechte Idee. (Sie verwenden wahrscheinlich auch nicht alle C! Denken Sie an Heap, Fließkomma, Setjmp / Longjmp, Printf, ...)
In einem Kommentar erwähnt Adam Haun RAII und Initialisierung. IMO RAII hat mehr mit Dekonstruktion zu tun, aber sein Punkt ist gültig: Globale Objekte werden vor Ihrem Hauptstart konstruiert, sodass sie möglicherweise auf ungültigen Annahmen basieren (wie eine Haupttaktrate, die später geändert wird). Dies ist ein weiterer Grund, KEINE mit globalem Code initialisierten Objekte zu verwenden. (Ich verwende ein Linker-Skript, das fehlschlägt, wenn ich mit globalem Code initialisierte Objekte habe.) IMO sollten solche "Objekte" explizit erstellt und weitergegeben werden. Dies beinhaltet ein 'Warten'-Objekt, das eine wait () -Funktion bietet. In meinem Setup ist dies "Objekt", das die Taktrate des Chips festlegt.
Apropos RAII: Dies ist eine weitere C ++ - Funktion, die in kleinen eingebetteten Systemen sehr nützlich ist, jedoch nicht aus dem Grund (Speicherfreigabe), für den sie in größeren Systemen am häufigsten verwendet wird (kleine eingebettete Systeme verwenden meist keine dynamische Speicherfreigabe). Denken Sie an das Sperren einer Ressource: Sie können die gesperrte Ressource zu einem Wrapperobjekt machen und den Zugriff auf die Ressource so beschränken, dass er nur über den Sperrwrapper möglich ist. Wenn der Wrapper den Gültigkeitsbereich verlässt, wird die Ressource entsperrt. Dies verhindert den Zugriff ohne Verriegelung und macht es viel unwahrscheinlicher, dass die Entriegelung vergessen wird. mit etwas (Vorlagen-) Magie kann es ein Overhead von Null sein.
Die ursprüngliche Frage erwähnte kein C, daher meine C ++ - zentrierte Antwort. Wenn es wirklich sein muss C ....
Sie können Makrotäuschungen anwenden: Deklarieren Sie Ihre Produkte öffentlich, damit sie einen Typ haben und global zugeordnet werden können. Machen Sie die Namen ihrer Komponenten jedoch unbrauchbar, es sei denn, ein Makro ist anders definiert, wie dies in der .c-Datei Ihres Moduls der Fall ist. Für zusätzliche Sicherheit können Sie die Kompilierungszeit im Mangling verwenden.
Oder haben Sie eine öffentliche Version Ihrer Struktur, die nichts Nützliches enthält, und haben Sie die private Version (mit nützlichen Daten) nur in Ihrer .c-Datei, und behaupten Sie, dass sie dieselbe Größe haben. Ein kleiner Trick beim Erstellen von Dateien könnte dies automatisieren.
@Lundins Kommentar zu schlechten (eingebetteten) Programmierern:
Die Art von Programmierer, die Sie beschreiben, würde wahrscheinlich in jeder Sprache ein Chaos anrichten. Makros (in C und C ++ vorhanden) sind ein offensichtlicher Weg.
Werkzeuge können in gewissem Maße helfen. Für meine Schüler gebe ich ein Skript an, das keine Ausnahmen, kein rtti, angibt und einen Linker-Fehler ausgibt, wenn entweder der Heap verwendet wird oder Code-initialisierte Globals vorhanden sind. Und es gibt warning = error an und aktiviert fast alle Warnungen.
Ich empfehle die Verwendung von Vorlagen, aber mit constexpr und Konzepten ist eine Metaprogrammierung immer weniger erforderlich.
"Verwirrte Arduino-Programmierer" Ich würde sehr gerne den Arduino-Programmierstil (Verkabelung, Replikation von Code in Bibliotheken) durch einen modernen C ++ - Ansatz ersetzen, der einfacher, sicherer und schneller und kleinerer Code sein kann. Wenn ich nur die Zeit und Kraft hätte ...
quelle
Ich glaube, FreeRTOS (vielleicht ein anderes Betriebssystem?) Macht so etwas wie das, wonach Sie suchen, indem Sie 2 verschiedene Versionen der Struktur definieren.
Die "echte", die intern von den Betriebssystemfunktionen verwendet wird, und eine "falsche", die die gleiche Größe wie die "echte" hat, aber keine nützlichen Mitglieder enthält (nur ein paar
int dummy1
und ähnliche).Außerhalb des Betriebssystemcodes wird nur die 'Fake'-Struktur angezeigt, und dies wird verwendet, um statischen Instanzen der Struktur Speicher zuzuweisen.
Intern wird beim Aufrufen von Funktionen im Betriebssystem die Adresse der externen 'Fake'-Struktur als Handle übergeben, und diese wird dann als Zeiger auf eine' echte 'Struktur typisiert, damit die Betriebssystemfunktionen das tun können, was sie benötigen tun.
quelle
Meiner Meinung nach ist das sinnlos. Sie können dort einen Kommentar einfügen, aber es macht keinen Sinn, ihn weiter auszublenden.
C wird niemals eine so hohe Isolation liefern, selbst wenn es keine Deklaration für die Struktur gibt, wird es leicht sein, sie versehentlich mit z. B. fehlerhaftem memcpy () oder Pufferüberlauf zu überschreiben.
Geben Sie stattdessen der Struktur einen Namen und vertrauen Sie darauf, dass andere Leute auch guten Code schreiben. Dies erleichtert auch das Debuggen, wenn die Struktur einen Namen hat, auf den Sie verweisen können.
quelle
Reine SW-Fragen werden unter /programming/ besser gestellt .
Das Konzept, eine Struktur eines unvollständigen Typs dem Aufrufer zur Verfügung zu stellen, wird, wie Sie beschreiben, häufig als "undurchsichtiger Typ" oder "undurchsichtige Zeiger" bezeichnet - eine anonyme Struktur bedeutet etwas ganz anderes.
Das Problem dabei ist, dass der Aufrufer keine Instanzen des Objekts zuordnen kann, sondern nur Zeiger darauf. Auf einem PC würden Sie
malloc
innerhalb der Objekte "Konstruktor" verwenden, aber Malloc ist ein No-Go in Embedded-Systemen.In embedded müssen Sie also einen Speicherpool bereitstellen. Sie haben eine begrenzte Menge an RAM, daher ist es normalerweise kein Problem, die Anzahl der Objekte, die erstellt werden können, zu beschränken.
Siehe Statische Zuordnung von undurchsichtigen Datentypen bei SO.
quelle