Beim Lesen des Artikels über das Filtern habe ich eine seltsame Verwendung von .h
Dateien festgestellt - verwenden Sie diese zum Füllen von Koeffizientenarrays:
#define N 100 // filter order
float h[N] = { #include "f1.h" }; //insert coefficients of filter
float x[N];
float y[N];
short my_FIR(short sample_data)
{
float result = 0;
for ( int i = N - 2 ; i >= 0 ; i-- )
{
x[i + 1] = x[i];
y[i + 1] = y[i];
}
x[0] = (float)sample_data;
for (int k = 0; k < N; k++)
{
result = result + x[k]*h[k];
}
y[0] = result;
return ((short)result);
}
Ist es also üblich, float h[N] = { #include "f1.h" };
diesen Weg zu verwenden?
f1.h
Wird möglicherweise von einem externen Tool generiert und als Eingabe für Ihr (kompiliertes) Programm verwendet. Es ist jedoch üblicher, dass die externen Tools eine vollständige Header-Datei generieren, z . B.float h[N] = { ... }
im Innerenf1.h
. Beispiel hier .# include "q-char-sequence" new-line
. Wo ist die neue Linie?error: "stray #"
unabhängig davon, welchen Standard oder welche Optionen ich übergebe.Antworten:
Präprozessor- Direktiven wie
#include
führen nur eine Textersetzung durch (siehe die Dokumentation von GNU cpp in GCC ). Es kann an jeder Stelle auftreten (außerhalb von Kommentaren und String-Literalen).A
#include
sollte jedoch#
das erste nicht leere Zeichen seiner Zeile haben. Sie werden also codierenfloat h[N] = { #include "f1.h" };
Die ursprüngliche Frage hatte keine
#include
eigene Zeile, hatte also falschen Code.Es ist keine normale Praxis, aber es ist erlaubt zu üben. In diesem Fall würde ich vorschlagen, eine andere Erweiterung als
.h
z. B. use#include "f1.def"
oder#include "f1.data"
...Bitten Sie Ihren Compiler, Ihnen das vorverarbeitete Formular anzuzeigen. Mit GCC kompilieren Sie mit
gcc -C -E -Wall yoursource.c > yoursource.i
und schauen Sie mit einem Editor oder einem Pager in die generierteyoursource.i
Eigentlich bevorzuge ich solche Daten in einer eigenen Quelldatei. Daher würde ich stattdessen vorschlagen, eine in sich geschlossene
h-data.c
Datei mit einem Tool wie GNU awk zu generieren (die Dateih-data.c
beginnt alsoconst float h[345] = {
mit};
... und endet mit ...). Wenn es sich um konstante Daten handelt, deklarieren Sieconst float h[]
sie besser (damit sie gelesen werden können) -nur Segment wie.rodata
unter Linux). Wenn die eingebetteten Daten groß sind, kann der Compiler einige Zeit benötigen, um sie (nutzlos) zu optimieren (dann können Sie Ihre Datenh-data.c
schnell und ohne Optimierungen kompilieren ).quelle
>
ein Charakter kürzer ist als-o
und weil ich lahm bin.Es ist nicht normal, aber gültig (wird vom Compiler akzeptiert).
Vorteile dieser Verwendung: Sie sparen den geringen Aufwand, der erforderlich wäre, um eine bessere Lösung zu finden.
Nachteile:
Dies ist einer der Fälle, in denen zusätzliche 20 Minuten Nachdenken vor dem Schreiben des Codes Ihnen einige zehn Stunden Zeit verfluchen können, Code und Entwickler während der Laufzeit des Projekts zu verfluchen.
quelle
#include
sollten sich in einer separaten Zeile befinden.Wie bereits in früheren Antworten erläutert, ist dies keine normale Praxis, aber eine gültige.
Hier ist eine alternative Lösung:
Datei f1.h:
#ifndef F1_H #define F1_H #define F1_ARRAY \ { \ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, \ 10,11,12,13,14,15,16,17,18,19, \ 20,21,22,23,24,25,26,27,28,29, \ 30,31,32,33,34,35,36,37,38,39, \ 40,41,42,43,44,45,46,47,48,49, \ 50,51,52,53,54,55,56,57,58,59, \ 60,61,62,63,64,65,66,67,68,69, \ 70,71,72,73,74,75,76,77,78,79, \ 80,81,82,83,84,85,86,87,88,89, \ 90,91,92,93,94,95,96,97,98,99 \ } // Values above used as an example #endif
Datei f1.c:
#include "f1.h" float h[] = F1_ARRAY; #define N (sizeof(h)/sizeof(*h)) ...
quelle
F1_ARRAY
undF1_H
. Wenn Sie eine generierte Datei haben, kann es hilfreich sein, die Verwendung solcher Präprozessorsymbole zu vermeiden. Für vom Menschen erstellte Header-Dateien ist Ihre Lösung besser, nicht jedoch für eine Reihe generierter Datendateien.cat
et al. Aufrufen und sicherstellen, dass Ihrecat
und Skripte tatsächlich ausgeführt werden.Nein, das ist keine normale Praxis.
Es ist wenig bis gar kein Vorteil, ein solches Format direkt zu verwenden , stattdessen könnten die Daten in einer separaten Quelldatei generiert werden, oder in diesem Fall könnte zumindest eine vollständige Definition gebildet werden.
Es gibt jedoch ein "Muster", bei dem eine Datei an solchen zufälligen Stellen eingefügt wird : X-Makros wie diese .
Die Verwendung von X-Makro besteht darin, eine Sammlung einmal zu definieren und an verschiedenen Stellen zu verwenden. Die einheitliche Definition gewährleistet die Kohärenz des Ganzen. Betrachten Sie als triviales Beispiel:
// def.inc MYPROJECT_DEF_MACRO(Error, Red, 0xff0000) MYPROJECT_DEF_MACRO(Warning, Orange, 0xffa500) MYPROJECT_DEF_MACRO(Correct, Green, 0x7fff00)
die jetzt auf verschiedene Arten verwendet werden kann:
// MessageCategory.hpp #ifndef MYPROJECT_MESSAGE_CATEGORY_HPP_INCLUDED #define MYPROJECT_MESSAGE_CATEGORY_HPP_INCLUDED namespace myproject { enum class MessageCategory { # define MYPROJECT_DEF_MACRO(Name_, dummy0_, dummy1_) Name_, # include "def.inc" # undef MYPROJECT_DEF_MACRO NumberOfMessageCategories }; // enum class MessageCategory enum class MessageColor { # define MYPROJECT_DEF_MACRO(dumm0_, Color_, dummy1_) Color_, # include "def.inc" # undef MYPROJECT_DEF_MACRO NumberOfMessageColors }; // enum class MessageColor MessageColor getAssociatedColorName(MessageCategory category); RGBColor getAssociatedColorCode(MessageCategory category); } // namespace myproject #endif // MYPROJECT_MESSAGE_CATEGORY_HPP_INCLUDED
quelle
Vor langer Zeit wurde der Präprozessor überbeansprucht. Siehe zum Beispiel das XPM-Dateiformat, das so konzipiert wurde, dass Benutzer:
#include "myimage.xpm"
in ihrem C-Code.
Es wird nicht mehr als gut angesehen.
Der OP-Code sieht
C
so aus, also werde ich darüber sprechenC
Warum wird der Präprozessor überbeansprucht?
Die Präprozessor-
#include
Direktive soll Quellcode enthalten. In diesem Fall und im Fall des OP handelt es sich nicht um echten Quellcode, sondern um Daten .Warum wird es als schlecht angesehen?
Weil es sehr unflexibel ist . Sie können das Image nicht ändern, ohne die gesamte Anwendung neu zu kompilieren. Sie können nicht einmal zwei Bilder mit demselben Namen einfügen, da dadurch nicht kompilierbarer Code erzeugt wird. Im Fall des OP kann er die Daten nicht ändern, ohne die Anwendung neu zu kompilieren.
Ein weiteres Problem besteht darin, dass eine enge Kopplung zwischen den Daten und dem Quellcode hergestellt wird. Beispielsweise muss die Datendatei mindestens die Anzahl der Werte enthalten, die durch das
N
in der Quellcodedatei definierte Makro angegeben werden .Durch die enge Kopplung wird Ihren Daten auch ein Format zugewiesen. Wenn Sie beispielsweise 10x10-Matrixwerte speichern möchten, können Sie entweder ein eindimensionales Array oder ein zweidimensionales Array in Ihrem Quellcode verwenden. Wenn Sie von einem Format zum anderen wechseln, ändert sich Ihre Datendatei.
Dieses Problem des Ladevorganges wird leicht gelöst durch die Verwendung der Standard - I / O - Funktionen. Wenn Sie wirklich einige Standards Bilder müssen enthalten können Sie einen Standard geben Pfade zu den Bildern in Ihrem Quellcode. Auf diese Weise kann der Benutzer diesen Wert zumindest ändern (über eine
#define
oder-D
-Option beim Kompilieren) oder die Bilddatei aktualisieren, ohne sie erneut kompilieren zu müssen.Im Fall des OP wäre sein Code wiederverwendbarer, wenn die FIR-Koeffizienten und -Vektoren
x, y
als Argumente übergeben würden. Sie können ein erstellenstruct
, um diese Werte zusammen zu halten. Der Code wäre nicht ineffizient und würde auch mit anderen Koeffizienten wiederverwendbar werden . Die Koeffizienten können beim Start aus einer Standarddatei geladen werden, es sei denn, der Benutzer übergibt einen Befehlszeilenparameter, der den Dateipfad überschreibt. Dies würde die Notwendigkeit globaler Variablen beseitigen und die Absichten des Programmierers explizit machen. Sie können sogar dieselbe FIR-Funktion in zwei Threads verwenden, vorausgesetzt, jeder Thread hat seine eigenestruct
.Wann ist es akzeptabel?
Wenn Sie keine dynamischen Daten laden können. In diesem Fall müssen Sie Ihre Daten statisch laden und müssen solche Techniken anwenden.
Wir sollten beachten, dass kein Zugriff auf Dateien bedeutet, dass Sie für eine sehr eingeschränkte Plattform programmieren und daher Kompromisse eingehen müssen. Dies ist der Fall, wenn Ihr Code beispielsweise auf einem Mikrocontroller ausgeführt wird.
Aber selbst in diesem Fall würde ich es vorziehen, eine echte
C
Quelldatei zu erstellen, anstatt Gleitkommawerte aus einer halbformatierten Datei aufzunehmen.Zum Beispiel die Bereitstellung einer realen
C
Funktion, die die Koeffizienten zurückgibt, anstatt eine halbformatierte Datendatei zu haben. DieseC
Funktion kann dann in zwei verschiedenen Dateien definiert werden, von denen eine E / A für Entwicklungszwecke verwendet und eine andere statische Daten für den Release-Build zurückgibt. Sie würden die korrekte Bedingung für die Quelldatei kompilieren.quelle
#include
Datei war. Das ist das Szenario, das in der Frage diskutiert wird. Der XPM hat Zugriff auf die Build-Umgebung, da dort die Datei enthalten ist. Zugegeben, wenn Sie überhaupt schlau sind, überprüfen Sie das XPM, um sicherzustellen, dass es gültig ist, bevor#include
Sie es verwenden, aber es ist immer noch eine große Sicherheitslücke.Es gibt manchmal Situationen, in denen entweder externe Tools zum Generieren von .C-Dateien basierend auf anderen Dateien, die Quellcode enthalten, verwendet werden müssen, externe Tools C-Dateien mit einer übermäßigen Menge an Code generieren müssen, die fest mit den generierenden Tools verbunden sind, oder Code verwendet werden muss
#include
Richtlinie auf verschiedene "ungewöhnliche" Arten. Von diesen Ansätzen würde ich vorschlagen, dass letzteres - obwohl es eklig ist - oft das am wenigsten böse ist.Ich würde vorschlagen, die Verwendung des
.h
Suffixes für Dateien zu vermeiden, die nicht den normalen Konventionen für Header-Dateien entsprechen (z. B. durch Einbeziehen von Methodendefinitionen, Zuweisen von Speicherplatz, Erfordernis eines ungewöhnlichen Einschlusskontexts (z. B. in der Mitte einer Methode) und Erfordernis mehrerer Aufnahme in verschiedene definierte Makros usw. Ich vermeide im Allgemeinen auch die Verwendung von.c
oder.cpp
für Dateien, die über andere Dateien in andere Dateien integriert werden, es#include
sei denn, diese Dateien werden hauptsächlich eigenständig verwendet. [In einigen Fällen kann z. B. eine DateifooDebug.c
mit#define SPECIAL_FOO_DEBUG_VERSION
[newline] `#include" foo vorhanden sein. c "` `wenn ich möchte, dass zwei Objektdateien mit unterschiedlichen Namen aus derselben Quelle generiert werden und eine davon die" normale "Version ist.]Meine normale Praxis besteht darin,
.i
als Suffix entweder von Menschen oder Maschinen erzeugte Dateien zu verwenden, die so konzipiert sind, dass sie auf übliche Weise aus anderen C- oder C ++ - Quelldateien aufgenommen werden. Wenn Dateien maschinell generiert werden, wird das Generierungswerkzeug in der Regel in der ersten Zeile einen Kommentar enthalten, der das zum Erstellen verwendete Werkzeug angibt.Übrigens, ein Trick, bei dem ich dies verwendet habe, war, als ich zulassen wollte, dass ein Programm nur mit einer Batch-Datei ohne Tools von Drittanbietern erstellt wird, aber zählen wollte, wie oft es erstellt wurde. In meine Batch-Datei habe ich aufgenommen
echo +1 >> vercount.i
; dann in der Datei vercount.c, wenn ich mich richtig erinnere:const int build_count = 0 #include "vercount.i" ;
Der Nettoeffekt ist, dass ich einen Wert erhalte, der bei jedem Build erhöht wird, ohne dass ich mich bei der Erstellung auf Tools von Drittanbietern verlassen muss.
quelle
Wenn der Präprozessor die
#include
Direktive findet, öffnet er einfach die angegebene Datei und fügt den Inhalt ein, als wäre der Inhalt der Datei am Speicherort der Direktive geschrieben worden.quelle
Wie bereits in den Kommentaren erwähnt, ist dies keine normale Praxis. Wenn ich solchen Code sehe, versuche ich ihn umzugestalten.
Zum Beispiel
f1.h
könnte so aussehen#ifndef _f1_h_ #define _f1_h_ #ifdef N float h[N] = { // content ... } #endif // N #endif // _f1_h_
Und die .c-Datei:
#define N 100 // filter order #include “f1.h” float x[N]; float y[N]; // ...
Dies scheint mir etwas normaler zu sein - obwohl der obige Code noch weiter verbessert werden könnte (z. B. ohne Globals).
quelle
Hinzu kommt, was alle anderen gesagt haben - der Inhalt von
f1.h
muss so sein:Weil der Text in
f1.h
in das betreffende Array initialisieren wird!Ja, es kann Kommentare, andere Funktionen oder Makroverwendungen, Ausdrücke usw. enthalten.
quelle
Für mich ist das normal.
Mit dem Präprozessor können Sie eine Quelldatei in beliebig viele Blöcke aufteilen, die von # include-Direktiven zusammengestellt werden.
Es ist sehr sinnvoll, wenn Sie den Code nicht mit langen / nicht zu lesenden Abschnitten wie Dateninitialisierungen überladen möchten. Wie sich herausstellt, ist meine Datensatzdatei "Array-Initialisierung" 11000 Zeilen lang.
Ich verwende sie auch, wenn einige Teile des Codes automatisch von einem externen Tool generiert werden: Es ist sehr praktisch, wenn das Tool nur seine Chunks generiert und sie in den Rest des von Hand geschriebenen Codes einfügt.
Ich habe einige solcher Einschlüsse für einige Funktionen, die je nach Prozessor mehrere alternative Implementierungen haben, von denen einige Inline-Assembly verwenden. Die Einschlüsse machen den Code übersichtlicher.
Traditionell wurde die Direktive #include verwendet, um Header-Dateien einzuschließen, dh Sätze von Deklarationen, die eine API verfügbar machen. Aber nichts verlangt das.
quelle
Ich habe gelesen, dass Leute umgestalten wollen und sagen, dass dies böse ist. Trotzdem habe ich in einigen Fällen verwendet. Wie einige Personen sagten, handelt es sich um eine Präprozessor-Richtlinie, die auch den Inhalt der Datei enthält. Hier ist ein Fall, in dem ich Zufallszahlen erstellt habe. Ich baue Zufallszahlen und möchte dies nicht jedes Mal tun, wenn ich weder zur Laufzeit kompiliere. Ein anderes Programm (normalerweise ein Skript) füllt einfach eine Datei mit den generierten Zahlen, die enthalten sind. Dies vermeidet das Kopieren von Hand, dies ermöglicht ein einfaches Ändern der Zahlen, des Algorithmus, der sie erzeugt, und anderer Feinheiten. Sie können die Praxis nicht einfach beschuldigen, in diesem Fall ist es einfach der richtige Weg.
quelle
Ich habe die Technik des OP verwendet, eine Include-Datei für den Dateninitialisierungsteil einer Variablendeklaration für einige Zeit zu platzieren. Genau wie beim OP wurde die enthaltene Datei generiert.
Ich habe die generierten .h-Dateien in einem separaten Ordner isoliert, damit sie leicht identifiziert werden können:
#include "gensrc/myfile.h"
Dieses Schema fiel auseinander, als ich anfing, Eclipse zu verwenden. Die Überprüfung der Eclipse-Syntax war nicht ausgefeilt genug, um dies zu handhaben. Es würde reagieren, indem es Syntaxfehler meldet, bei denen es keine gibt.
Ich habe Beispiele an die Eclipse-Mailingliste gemeldet, aber es schien kein großes Interesse daran zu bestehen, die Syntaxprüfung zu "korrigieren".
Ich habe meinen Codegenerator geändert, um zusätzliche Argumente zu verwenden, damit die gesamte Variablendeklaration generiert werden kann, nicht nur die Daten. Jetzt werden syntaktisch korrekte Include-Dateien generiert.
Auch wenn ich Eclipse nicht verwendet habe, denke ich, dass es eine bessere Lösung ist.
quelle
Im Linux-Kernel habe ich ein Beispiel gefunden, IMO, schön. Wenn Sie sich die Header-Datei cgroup.h ansehen
http://lxr.free-electrons.com/source/include/linux/cgroup.h
Sie finden die Anweisung,
#include <linux/cgroup_subsys.h>
die zweimal verwendet wird, nach unterschiedlichen Definitionen des MakrosSUBSYS(_x)
. Dieses Makro wird in cgroup_subsys.h verwendet, um mehrere Namen von Linux-cgroups zu deklarieren (wenn Sie mit cgroups nicht vertraut sind, handelt es sich um benutzerfreundliche Schnittstellen, die Linux anbietet und die beim Systemstart initialisiert werden müssen).Im Code-Snippet
#define SUBSYS(_x) _x ## _cgrp_id, enum cgroup_subsys_id { #include <linux/cgroup_subsys.h> CGROUP_SUBSYS_COUNT, }; #undef SUBSYS
Jedes
SUBSYS(_x)
in cgroup_subsys.h deklarierte Element wird zu einem Element des Typsenum cgroup_subsys_id
, während es sich im Code-Snippet befindet#define SUBSYS(_x) extern struct cgroup_subsys _x ## _cgrp_subsys; #include <linux/cgroup_subsys.h> #undef SUBSYS
Jedes
SUBSYS(_x)
wird zur Deklaration einer Variablen vom Typstruct cgroup_subsys
.Auf diese Weise können Kernel-Programmierer cgroups hinzufügen, indem sie nur cgroup_subsys.h ändern, während der Vorprozessor automatisch die zugehörigen Aufzählungswerte / Deklarationen in die Initialisierungsdateien hinzufügt.
quelle