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 setzt die primären Parameter, danach wird ein Handler (Zeiger auf eine beschreibende Struktur) an die aufgerufene Funktion innerhalb des Moduls übergeben.
Im Moment frage ich mich, was der beste Ansatz für den Zuweisungsspeicher für die Struktur ist, die ein Modul beschreibt. Wenn möglich, möchte ich Folgendes:
Ich sehe folgende Möglichkeiten, die alle einem meiner Ziele widersprechen:
globale Erklärung
mehrere Instanzen, allcoted by linker, aber struct ist nicht undurchsichtig
(#includes)
module_struct module;
void main(){
module_init(&module);
}
malloc
Undurchsichtige Struktur, mehrere Instanzen, aber Allcotion auf Heap
in module.h:
typedef module_struct Module;
in module.c Init-Funktion, malloc und Rückgabezeiger auf 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();
}
Deklaration im Modul
undurchsichtige Struktur, vom Linker zugewiesen, nur eine vordefinierte Anzahl von Instanzen
Behalten Sie die gesamte Struktur und den gesamten Speicher im Modul und legen Sie niemals einen Handler oder eine Struktur offen.
(#includes)
void main(){
module_init(_no_param_or_index_if_multiple_instances_possible_);
}
Gibt es eine Möglichkeit, diese irgendwie für undurchsichtige Strukturen, Linker statt Heap-Zuweisung und mehrere/beliebige Anzahl von Instanzen zu kombinieren?
Lösung
Wie in einigen Antworten unten vorgeschlagen, denke ich, dass der beste Weg ist:
Gibt es eine Möglichkeit, diese irgendwie für anonyme Strukturen, Linker statt Heap-Zuweisung und mehrere/beliebig viele Instanzen zu kombinieren?
Sicher gibt es das. Beachten Sie jedoch zunächst, dass die "beliebige Anzahl" von Instanzen zur Kompilierzeit festgelegt oder zumindest eine Obergrenze festgelegt werden muss. Dies ist eine Voraussetzung dafür, dass die Instanzen statisch zugewiesen werden (was Sie als "Linker-Zuweisung" bezeichnen). Sie können die Zahl ohne Änderung der Quelle anpassen, indem Sie ein Makro deklarieren, das sie angibt.
Dann deklariert die Quelldatei, die die eigentliche Struct-Deklaration und alle zugehörigen Funktionen enthält, auch ein Array von Instanzen mit interner Verknüpfung. Sie stellt entweder ein Array mit externer Verknüpfung von Zeigern auf die Instanzen bereit oder aber eine Funktion zum Zugreifen auf die verschiedenen Zeiger per Index. Die Funktionsvariante ist etwas modularer:
Modul.c
#include <module.h>
// 4 instances by default; can be overridden at compile time
#ifndef NUM_MODULE_INSTANCES
#define NUM_MODULE_INSTANCES 4
#endif
struct module {
int demo;
};
// has internal linkage, so is not directly visible from other files:
static struct module instances[NUM_MODULE_INSTANCES];
// module functions
struct module *module_init(unsigned index) {
instances[index].demo = 42;
return &instances[index];
}
Ich denke, Sie sind bereits damit vertraut, wie der Header die Struktur dann als unvollständigen Typ deklarieren und alle Funktionen deklarieren würde (in Form von Zeigern auf diesen Typ geschrieben). Zum Beispiel:
Modul.h
#ifndef MODULE_H
#define MODULE_H
struct module;
struct module *module_init(unsigned index);
// other functions ...
#endif
Now ist in anderen Übersetzungseinheiten als , *struct module
undurchsichtig und Sie können ohne dynamische Zuordnung auf bis zu der Anzahl von Instanzen zugreifen und diese verwenden, die zur Kompilierzeit definiert wurden.module.c
* Es sei denn, Sie kopieren natürlich seine Definition. Der Punkt ist, dass module.h
das nicht geht.
Ich programmiere kleine Mikrocontroller in C++, was genau das erreicht, was Sie wollen.
Was Sie ein Modul nennen, ist eine C++-Klasse, die Daten (entweder extern zugänglich oder nicht) und Funktionen (ebenfalls) enthalten kann. Der Konstruktor (eine dedizierte Funktion) initialisiert es. Der Konstruktor kann Laufzeitparameter oder (meine Lieblings-) Kompilierungsparameter (Vorlagen) annehmen. Die Funktionen innerhalb der Klasse erhalten implizit die Klassenvariable als ersten Parameter. (Oder, oft meine Präferenz, die Klasse kann als verstecktes Singleton fungieren, sodass auf alle Daten ohne diesen Overhead zugegriffen wird).
Das Klassenobjekt kann global sein (damit Sie zur Verbindungszeit wissen, dass alles passt) oder Stack-lokal, vermutlich hauptsächlich. (Ich mag C++ Globals wegen der undefinierten globalen Initialisierungsreihenfolge nicht, also bevorzuge ich Stack-Local).
Mein bevorzugter Programmierstil ist, dass Module statische Klassen sind und ihre (statische) Konfiguration durch Template-Parameter erfolgt. Dies vermeidet fast alle Überarbeitungen und ermöglicht eine Optimierung. Kombinieren Sie dies mit einem Tool, das die Stapelgröße berechnet, und Sie können unbesorgt schlafen :)
Mein Vortrag über diese Art der Codierung in C++: Objekte? Nein danke!
Viele Embedded-/Mikrocontroller-Programmierer scheinen C++ nicht zu mögen, weil sie denken, dass es sie dazu zwingen würde, C++ vollständig zu verwenden . Das ist absolut nicht notwendig und wäre eine sehr schlechte Idee. (Sie verwenden wahrscheinlich auch nicht das gesamte 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 konstruiert, bevor Ihr Hauptstart beginnt, sodass sie möglicherweise mit ungültigen Annahmen arbeiten (wie einer Haupttaktgeschwindigkeit, die später geändert wird). Das ist ein weiterer Grund, keine globalen Code-initialisierten Objekte zu verwenden. (Ich verwende ein Linker-Skript, das fehlschlägt, wenn ich globale Code-initialisierte Objekte habe.) IMO sollten solche "Objekte" explizit erstellt und weitergegeben werden. Dies schließt ein „Warte“-Einrichtungs-„Objekt“ ein, das eine wait()-Funktion bereitstellt. In meinem Setup ist dies ein 'Objekt', das die Taktrate des Chips einstellt.
Apropos RAII: Das ist eine weitere C++-Funktion, die in kleinen eingebetteten Systemen sehr nützlich ist, wenn auch nicht aus dem Grund (Speicherfreigabe), für den sie am häufigsten in größeren Systemen verwendet wird (kleine eingebettete Systeme verwenden meistens keine dynamische Speicherfreigabe). Denken Sie an das Sperren einer Ressource: Sie können die gesperrte Ressource zu einem Wrapper-Objekt machen und den Zugriff auf die Ressource so einschränken, dass er nur über den Sperr-Wrapper 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 (Template-)Magie kann es ohne Overhead sein.
In der ursprünglichen Frage wurde C nicht erwähnt, daher meine C++-zentrierte Antwort. Wenn es wirklich C sein muss....
Sie könnten Makrotricks anwenden: Deklarieren Sie Ihre Stucts öffentlich, damit sie einen Typ haben und global zugewiesen werden können, aber verstümmeln Sie die Namen ihrer Komponenten über die Verwendbarkeit hinaus, es sei denn, ein Makro ist anders definiert, was in der .c-Datei Ihres Moduls der Fall ist. Für zusätzliche Sicherheit können Sie die Kompilierzeit beim Mangeln 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 bisschen Make-File-Trick 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 (vorhanden in C und C++) sind ein offensichtlicher Weg.
Werkzeuge können bis zu einem gewissen Grad helfen. Für meine Studenten gebe ich ein gebautes Skript vor, das keine Ausnahmen und kein RTI 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 Metaprogrammierung immer weniger erforderlich.
"verwirrte Arduino-Programmierer" Ich würde sehr gerne den Arduino-Programmierstil (Verdrahtung, Replikation von Code in Bibliotheken) durch einen modernen C++-Ansatz ersetzen, der einfacher und sicherer sein und schnelleren und kleineren Code erzeugen kann. Wenn ich nur Zeit und Kraft hätte....
Ich glaube, FreeRTOS (vielleicht ein anderes Betriebssystem?) tut so etwas wie das, wonach Sie suchen, indem es zwei verschiedene Versionen der Struktur definiert.
Das 'echte', das intern von den Betriebssystemfunktionen verwendet wird, und ein 'gefälschtes', das die gleiche Größe wie das 'echte' hat, aber keine nützlichen Elemente enthält (nur ein paar int dummy1
und ähnliche).
Nur die „gefälschte“ Struktur wird außerhalb des Betriebssystemcodes offengelegt, und dies wird verwendet, um statischen Instanzen der Struktur Speicher zuzuweisen.
Wenn Funktionen im Betriebssystem aufgerufen werden, wird ihnen intern die Adresse der externen „falschen“ Struktur als Handle übergeben, und diese wird dann als Zeiger auf eine „echte“ Struktur typisiert, damit die Betriebssystemfunktionen das tun können, was sie tun müssen tun.
Anonyme Struktur, dh die Struktur darf nur durch die Verwendung bereitgestellter Schnittstellenfunktionen geändert werden
Meiner Meinung nach ist dies sinnlos. Sie können dort einen Kommentar einfügen, aber es hat keinen Sinn, ihn weiter zu verstecken.
C wird niemals eine so hohe Isolation bieten, selbst wenn es keine Deklaration für die Struktur gibt, wird es leicht sein, sie versehentlich mit zB einem irrtümlichen memcpy() oder einem Pufferüberlauf zu überschreiben.
Geben Sie der Struktur stattdessen einfach einen Namen und vertrauen Sie darauf, dass andere Leute auch guten Code schreiben. Es erleichtert auch das Debuggen, wenn die Struktur einen Namen hat, mit dem Sie darauf verweisen können.
Reine SW-Fragen werden besser unter https://stackoverflow.com gestellt .
Das Konzept, dem Aufrufer eine Struktur mit unvollständigem Typ zur Verfügung zu stellen, wie Sie es beschreiben, wird oft als "undurchsichtiger Typ" oder "undurchsichtige Zeiger" bezeichnet - anonyme Struktur bedeutet etwas ganz anderes.
Das Problem dabei ist, dass der Aufrufer keine Instanzen des Objekts zuweisen kann, sondern nur Zeiger darauf. Auf einem PC würden Sie malloc
innerhalb der Objekte "constructor" verwenden, aber malloc ist in eingebetteten Systemen ein No-Go.
Was Sie also in Embedded tun, ist, einen Speicherpool bereitzustellen. Sie haben nur eine begrenzte Menge an RAM, daher ist es normalerweise kein Problem, die Anzahl der Objekte zu beschränken, die erstellt werden können.
Siehe Statische Zuordnung von undurchsichtigen Datentypen drüben bei SO.
Eugen Sch.
L. Heinrichs
jkaron
Lee Daniel Crocker
L. Heinrichs
jkaron
L. Heinrichs
jkaron
wrtlprnft
enter code here
?L. Heinrichs