Frage:
C - Globale in eine Struktur einwickeln?
Dirk Bruere
2019-11-19 20:17:29 UTC
view on stackexchange narkive permalink

Eine Frage im Firmware-Stil zu C.

Ich habe einen alten Code, den ich aufräumte.Eine der unangenehmen Eigenschaften sind globale Variablen, die über die Quelldateien verteilt sind.Ich kann ein paar von ihnen zu Einheimischen machen, was in Ordnung ist.Wie man jedoch mit dem Rest umgeht.Ich bevorzuge es, eine Struktur zu erstellen und sie darin zu platzieren.

Ich weiß, dass es theoretisch eine andere Indirektionsebene gibt, wenn auf sie zugegriffen wird, aber von einem POV-Stil aus ist dies besser oder schlechter als das Einfügen in die Dateien globals.c und / oder globals.h?

Dupe?https://stackoverflow.com/questions/2868651/including-c-header-file-with-lots-of-global-variables "Nur Deklarationen in .h-Dateien und die Definitionen in .c-Dateien."
@TonyStewartSunnyskyguyEE75 Art von.Die akzeptierte Antwort dort ist eine Struktur verwenden.Dennoch enthält der Code noch mehr böse Dinge, einschließlich Code in #define und impliziter Operatorpriorität, z. B. x = y / 1/2/3/4;
Keine Antwort, sondern eine Meinung: Ich bevorzuge es, "Globals" in irgendeiner Weise zu gruppieren, wie "typedef struct {static const int baudrate = 115200;statische Konstante int TX_Pin = 11;statische Konstante int RX_Pin = 12;} UART;` Auf diese Weise kann ich "UART :: TX_Pin" oder ähnliches aufrufen (Pseudocode) Auf diese Weise können die verschiedenen Gruppen auch dort platziert werden, wo sie am besten passen (keine Notwendigkeit mehr für "globals.h").
@FMashiro: Das ist C ++, nicht C.
@LaurentLARIZZA, ein Pseudocode, der von "c ++" inspiriert ist, kann in "C" sowie in so ziemlich jeder anderen Programmiersprache implementiert werden.
Wofür werden die globalen Variablen verwendet?Ich bin mir sicher, dass es bei genügend Zeit / Willen möglich ist, sie vollständig loszuwerden.
@JohnGo-Soco Leider habe ich nur ein paar Wochen Zeit, um 6000 Zeilen undokumentierten Codes zu dokumentieren und aufzuräumen
@DirkBruere dachte so viel.Der beste Weg, um wirklich voranzukommen, besteht darin, so viel wie möglich loszuwerden, aber reichlich zu dokumentieren, wo Sie nicht können.
Dirk, hast du vergessen, eine Antwort anzunehmen?
Sieben antworten:
#1
+24
Lundin
2019-11-19 20:49:17 UTC
view on stackexchange narkive permalink

Es ist bekannt, dass schlechte Praxis eine "Super-Header" -Datei wie "globals.h" oder "include.h" usw. hat, weil zusätzlich zu Globals in der Erstens schafft dies auch eine enge Kopplungsabhängigkeit zwischen jeder einzelnen, nicht verwandten Datei in Ihrem Projekt.

Nehmen wir an, Sie haben einen PWM-Treiber und ein RS-232-Debug-Druckmodul. Sie möchten das RS-232-Debug-Druckmodul herunterladen und in einem anderen Projekt wiederverwenden. Plötzlich brauchen Sie ein PWM.h, von dem Sie keine Ahnung haben, was es ist oder woher das Bedürfnis kommt. Sie werden sich fragen, warum um alles in der Welt Sie einen PWM-Treiber benötigen, um RS-232 auszuführen.

Und dann haben wir noch nicht einmal daran gedacht, diese chaotische globale Struktur wieder aufzunehmen. Lass uns das nicht einmal tun.


Der richtige Weg, um globale Spaghetti zu entwirren, wäre eher so:

  • Wird die Variable überhaupt verwendet? Wenn nicht, entfernen Sie. (Dies ist ziemlich häufig)
  • Kann die Variable innerhalb einer Funktion in den lokalen Bereich verschoben werden?
  • Kann die Variable in den lokalen Dateibereich .c verschoben werden, indem sie statisch gemacht wird? Können Sie den Zugriff von außerhalb der Datei .c reduzieren, indem Sie Setter / Getter-Funktionen implementieren?

Wenn alle oben genannten Fehler aufgetreten sind, sollte die Variable, die Sie sich ansehen, entweder ein speicherabgebildeter Hardwareregisterteil einer Registerzuordnung sein oder es handelt sich um einen kritischen Dirty Fix in Echtzeit, der während der Wartung hinzugefügt wurde.

Was ist falsch daran, * verwandte * Variablen in einer Struktur zusammenzufügen?Angenommen, Sie möchten das PWM-Treibermodul herunterladen und in einem anderen Projekt wiederverwenden, in dem Sie zwei davon instanziieren müssen.
@Bergi Kein Problem, wenn sie in der .c-Datei sind.Wenn sie in einer .h-Datei global sind, ist das Platzieren in einer Struktur keine offensichtliche Verbesserung.Wenn sie zusammengehören, können sie sich in einer Struktur befinden, unabhängig davon, wo Instanzen dieser Struktur deklariert sind.
Ich mag die dritte Option, bei der die Variable in der .c-Datei definiert ist und nur get / set-Funktionen im Header enthalten sind.
@danmcb: Ich denke, mit der Optimierung der Verbindungszeit kostet es hoffentlich keine Leistung und hindert Sie daran, die Adresse des Globalen zu übernehmen.IDK, wenn ein globaler Funktionsaufruf abhängig von den ausgewählten Namen viel besser lesbar ist als ein Variablenname, sieht er jedoch anders aus als andere Variablen.Wenn Sie es mit Getter / Setter zu einem "statischen" Dateibereich machen, werden die Dinge nicht wirklich besser, wenn Sie immer noch überall im Spaghetti-Stil darauf zugreifen.Diese Antwort legt nahe, dass dies nützlich ist, wenn * der größte Teil * der Verwendung in einer Datei enthalten sein kann, Sie jedoch noch Zugriff von außen benötigen.
Es sei darauf hingewiesen, dass "lokaler Bereich" immer noch eine statische Speicherklasse haben kann.Beachten Sie jedoch, dass ein Nicht-Konstanten-Initialisierer kein Fehler zur Kompilierungszeit mehr ist, wie er in C im globalen Bereich auftritt, sondern die Laufzeitleistung kostet, um ein bereits initialisiertes Flag zu überprüfen.(Und beim ersten Aufruf, um threadsichere Init zu machen.)
@PeterCordes Ein offensichtlicher Vorteil von Setter / Getter besteht darin, dass viele Dateibereichsvariablen in eingebetteten Systemen vorhanden sind, da sie für die Kommunikation mit einem ISR verwendet werden.Dann schützen ungefähr 90% aller geschriebenen eingebetteten Software solche Variablen nicht richtig vor Rennbedingungen.Wenn der Programmierer schließlich feststellt, dass seine seltenen intermittierenden Fehler dadurch verursacht werden, muss er eine erneute Eingabe implementieren, und dann sind die Setter / Getter der perfekte Ort, um dies zu tun.In Spaghettiware, mit der andere Übersetzungseinheiten direkt mit dem ISR kommunizieren können, ist dies nicht ordnungsgemäß möglich.
@PeterCordes richtig.Wie Lundin sagt, wenn es später notwendig wird, sicherzustellen, dass "wenn ich den Wert von X ändere, sollte ich auch überprüfen, ob ...", haben Sie einen einfachen Ort, um dies zu tun, wenn Sie get / set verwendet haben.Dies geschieht ein gutes Stück, wenn sich diese Systemtypen weiterentwickeln.Ich habe noch nie gesehen, dass der Overhead des Anrufs ein Problem darstellt. Wenn es so wäre, könnten Sie ihn inline deklarieren oder so.Auf keinen Fall muss vorzeitig optimiert werden.
#2
+14
Anders Petersson
2019-11-19 20:31:55 UTC
view on stackexchange narkive permalink

Es würde funktionieren, eine Struktur zu definieren, die Sie als einzelne globale Variable instanziieren.Zugriffe auf das Formular 'the_global.the_var' erhöhen nicht den Laufzeitaufwand und können klarstellen, dass es sich tatsächlich um einen globalen handelt. Wie https://stackoverflow.com/questions/2868651/including-c-header-file-with-lots-of-global-variables erwähnt, werden Sie vor separaten Deklarationen und Definitionen geschützt.

Persönlich würde ich mir nicht die Mühe machen, eine Struktur zu erstellen, sondern Globale lieber in die Header-Dateien zu sortieren, in denen sie meiner Meinung nach logisch hingehören, und für jede Header-Datei ein gemeinsames Präfix verwenden.Beispiel: Datei berechne.h mit der Angabe "extern int calc_result;"und berechne.c, die "int calc_result" definiert;

Andere Variablen sind nicht lokal, d. h. "static int result".in der .c-Datei.

Da Sie Legacy-Code haben, mit dem Sie vermutlich nicht viel zu tun haben, außer ihn aufzuräumen, würde ich sagen, dass die schnellste Lösung für eine klare Struktur die beste ist.

+1 Übertreibe es nicht mit Legacy-Code.Natürlich könnten Sie es mit einem Umschreiben usw. besser machen (selbst die ursprünglichen Autoren könnten es wahrscheinlich), aber ich kann Ihnen versichern, dass es in Ihrer Karriere viel mehr Code gibt, den Sie schreiben können.Konzentrieren Sie sich darauf, es so schnell wie möglich auf ein wartbareres Stadium zu bringen, und fahren Sie fort.
#3
+12
Geoxion
2019-11-19 20:29:04 UTC
view on stackexchange narkive permalink

Wenn Sie die Globals nicht loswerden können, sollten Sie sie nur dann in einer Struktur zusammenfassen, wenn sie tatsächlich verwandt sind.Wenn nicht, würde ich sie getrennt oder in kleineren Strukturen aufbewahren.

Außerdem möchte ich auch keine globals.h-Datei.Bewahren Sie sie oben in der Quelldatei auf, wo sie am meisten hingehören.Auf diese Weise bleiben Sie beim Navigieren durch den Code wahrscheinlich an dem Ort, an dem Sie sich befanden, oder gehen zu dem Ort, an den Sie wahrscheinlich wollten.

Außerdem: Wenn Sie sie ohne Notwendigkeit in eine Struktur einfügen, kann dies zukünftige Programmierer (einschließlich Sie selbst) irreführen, mehrere Instanzen solcher Strukturen zuzuweisen.und dann wird es wirklich verwirrend
@Curd Wenn der Code dagegen modular genug geschrieben ist, können solche Strukturen problemlos mehrmals instanziiert werden, ohne dass dies störend ist.
Aus dem Zusammenhang gehe ich jedoch davon aus, dass es nicht nur unwahrscheinlich ist, dass es notwendig, sondern sogar schädlich ist, wenn diese Strukturen mehrere Male existierten.
Das klingt für mich nach der pragmatischsten Herangehensweise.Das OP würde es eindeutig vorziehen, keine Globals zu haben. Wenn sie also ohne großen Umbau entfernt werden könnten, würde die Frage nicht gestellt.
Im Allgemeinen ist es nichts Falsches, bestimmte globale Variablen in einer Struktur zusammenzufügen - wenn sie miteinander in Beziehung stehen (z. B. zu derselben Schnittstelle gehören).Es ist jedoch keine gute Idee, ** irgendwelche ** Globalen in einer Struktur ** aus keinem anderen Grund als Globalen ** zusammenzufügen.
#4
+10
Michel Keijzers
2019-11-19 20:29:21 UTC
view on stackexchange narkive permalink

Eigentlich ist es nicht besser, der einzige Vorteil ist, dass Sie wissen, welche global sind. Sie haben also alle Globalen an einem Ort, aber sie sind immer noch verstreut.

C

Für jede globale:

  • Suchen Sie die ursprüngliche Datei, in der sie erstellt oder verwendet wird.
  • Machen Sie es in dieser Datei statisch
  • Wenn das Global an vielen Stellen verwendet wird, verwenden Sie es extern aus anderen Dateien (mit dem Schlüsselwort extern ).
  • Am besten übergeben Sie die Variable an die Stellen, an denen sie benötigt wird.

C ++

Für jede globale:

  • Suchen Sie den ursprünglichen Ort, an dem es erstellt oder verwendet wird.
  • Finden Sie die Klasse, zu der es am besten passt.
  • Verschieben Sie es in diese Klasse (als Feld, verbunden mit einem Objekt).
  • Erstellen Sie nun eine Get / Set-Methode (Funktionen). Vorzugsweise ist die Set-Methode geschützt oder privat.
  • Anstelle von Get and Set-Methoden können Sie diese auch über Methodenargumente übergeben.
  • Verwenden Sie Get / Set-Methoden aus anderen Klassen.
  • In einigen Fällen können Sie feststellen, dass es sich nicht um ein Feld einer Klasse handeln kann, z. B. wenn es nur ein Feld geben kann. Verwenden Sie in diesem Fall das Singleton-Entwurfsmuster. (Passen Sie auf; es ist immer noch eine Art globaler, also stellen Sie sicher, dass Sie in Zukunft nie mehr davon machen werden; andernfalls ist es ein globaler Raum in Verkleidung)
Nun, C ++ wäre viel schöner, aber ich bleibe bei C, es sei denn, ich schreibe komplett neu (was ich gerne tun würde, aber keine Zeit usw.)
Der statische Dateibereich von @DirkBruere ist eine perfekte Methode zur privaten Kapselung in Single-Core-MCU-Systemen mit einem Prozess.Meistens benötigen Sie keine private OO-Kapselung, aber wenn Sie dies tun, schauen Sie sich das Konzept des _opaque type_ bei SO an.
@DirkBruere Ich dachte wirklich, ich hätte C ++ gesehen, aber ich habe meine Antwort aktualisiert.Hielt das C ++ nur der Vollständigkeit halber.
* "C ... in dieser Klasse statisch machen" * - sollte das sein * "C ... in dieser Datei statisch machen" *?
@DigitalTrauma OO-Konzepte wie Klassen sind sprachunabhängig.C hat kein Schlüsselwort "class", aber Sie können Klassen in jeder Programmiersprache haben.Es ist ein Programmentwurfsbegriff.
@DigitalTrauma Danke, Datei ist in diesem Fall besser, aber wie Lundin erklärt, kann eine Datei als Klasse verwendet werden (zumindest als Basis).
@DirkBruere, Ich kenne die Besonderheiten Ihrer Umgebung nicht, aber der Wechsel von C zu C ++ muss im Allgemeinen keine vollständige Neufassung bedeuten.Es ist im Allgemeinen nicht allzu schwierig, Code von C in die allgemeine Teilmenge von C und C ++ zu übersetzen. Dann können Sie den Compiler umschalten und C ++ - Funktionen in neuem Code verwenden.
Ich habe das Gefühl, dass Stackoverflow oft zu einer falschen Dichtomie führt, dass man "einfaches C" oder "modernes C ++" schreiben sollte und dass Zwischenpunkte zwischen den beiden irgendwie ungültig sind.
Eine Datei "kann als Klasse verwendet werden" sollte dies jedoch häufig nicht - es sei denn, Sie programmieren in einer Sprache wie Java, in der Programmdesignkonzepte wie Klassen und Implementierungsdetails wie die Dateinamen verwechselt werden.
@PeterGreen Zustimmen… Ich sehe bei der Arbeit, dass die Leute denken, sie schreiben in modernem C ++, aber tatsächlich schreiben sie C kaum nach OO-Prinzipien.
@alephzero Nicht sicher, was Sie meinen ... Sowohl in C als auch in C ++ haben Sie .h- und .cpp-Dateien.Wenn ein C-Programmierer seinen Code gut strukturiert, ist eine Klasse mehr oder weniger eine Datei (oder kann in eine andere konvertiert werden).
Ich muss zugeben, dass ich es vorziehe, statische flüchtige Bestandteile für Variablen zu verwenden, die * nur * in Interrupt-Handlern aktualisiert wurden, und sie als extern deklarieren, wo sie getestet / aktualisiert werden (ich * habe * eine Header-Datei für diese externen Anweisungen, wenn sie sich natürlich zusammenschließen).
#5
+6
grahamj42
2019-11-21 03:03:47 UTC
view on stackexchange narkive permalink

Die großen Vorteile beim Einfügen globaler Variablen in eine Struktur sind:

  1. Es ist klar, wenn Sie eine globale oder eine lokale Variable verwenden.
  2. Es ist unmöglich, versehentlich eine globale Variable mit einer gleichnamigen lokalen Variablen zu maskieren.
  3. ol>

    Kleinere Vorteile sind:

    1. Es ist einfacher, Überläufe in einem globalen Array oder Speicherpuffer zu debuggen, da das Speicherlayout aus der Struktur abgeleitet werden kann.
    2. Die globale Struktur kann sehr einfach in einem nichtflüchtigen Speicher gespeichert / aus diesem geladen werden.
    3. ol>

      Die sinnvolle Verwendung globaler Variablen kann die Stapelanforderungen verringern, erhöht jedoch das Fehlerrisiko, wenn ihre Verwendung nicht gut dokumentiert und die Regeln eingehalten werden.

#6
+2
Laurent LA RIZZA
2019-11-21 18:28:24 UTC
view on stackexchange narkive permalink

Warum sollte es in einer -Struktur eine andere Ebene der Indirektion geben, die auf Variablen zugreift? Wenn Sie Ihre Struktur wie folgt deklarieren:

  typedef struct tagUART {
    int field1;
    int field2;
} UART;
 

Dann deklarieren Sie ein globales:

  UART uart;
 

Anstatt diese Globalen zu deklarieren:

  int uartField1;
int uartField2;
 

Der Compiler, so beschissen er auch sein mag, hat keine Entschuldigung, eine andere Indirektionsebene einzuführen, wenn Sie über uart.field1 auf Ihre Variable zugreifen, als dies bei einem Zugriff auf uartField1 . Der Compiler kennt die Startadresse Ihrer Struktur und die Strukturdeklaration definiert den Feldversatz innerhalb der Struktur. Der Compiler weiß alles am Referenzpunkt.

Jetzt würde die zusätzliche Indirektionsebene eintreten, wenn Sie Zeiger als Funktionsargumente an uart übergeben und die Funktionsaufrufe nicht inline werden. Dort verliert der Compiler die Tatsache, dass "Hey, die Felder haben eine bekannte Adresse".

#7
+1
Peter Green
2019-12-20 01:49:47 UTC
view on stackexchange narkive permalink

Ich weiß, dass es theoretisch eine andere Indirektionsebene gibt, wenn auf sie zugegriffen wird,

Nun, es kommt darauf an, dass ein wirklich dummer Compiler zusätzliche Indirektionsebenen für eine Struktur einführt, aber ein intelligenter Compiler kann tatsächlich besseren Code erzeugen, wenn eine Struktur beteiligt ist.

Im Gegensatz zu anderen Antworten kennt der Compiler die Adresse globaler Variablen NICHT. Für globale Variablen in derselben Kompilierungseinheit ist ihre Position relativ zueinander bekannt, nicht jedoch ihre absolute Position. Für Variablen in anderen Kompilierungseinheiten weiß sie weder absolut noch relativ etwas über ihre Position.

Stattdessen enthält der vom Compiler generierte Code Platzhalter für die Positionen der globalen Variablen. Diese werden dann durch tatsächliche Positionen ersetzt, wenn die endgültige Adresse festgelegt wird (traditionell, wenn das Programm verknüpft ist, manchmal jedoch erst, wenn es ausgeführt wird ).

Obwohl es einige CPUs (wie die 6502) gibt, die ohne vorherige Einrichtung direkt auf einen globalen Speicherort zugreifen können, gibt es viele, die dies nicht können. Um beispielsweise auf arm auf eine globale Variable zuzugreifen, muss der Compiler normalerweise zuerst die Adresse der Variablen in ein Register laden, entweder unter Verwendung eines Literalpools oder in neueren Versionen von arm unter Verwendung von movw / movt. Greifen Sie dann mit einem registerrelativen Ladebefehl auf die globale Variable zu.

Dies bedeutet, dass für Variablen außerhalb der Kompilierungseinheit der Zugriff auf mehrere Elemente derselben globalen Struktur wahrscheinlich effizienter ist als der Zugriff auf einzelne globale Variablen.

Um dies zu testen, habe ich den folgenden Code mit ARM gcc 8.2 und -O3 Optimierung in godbolt eingegeben.

  extern int a;
extern int b;

struct Foo {
    int a;
    int b;
};

extern struct Foo foo;

void f1 (void) {
    a = 1;
    b = 2;
}}

void f2 (void) {
    foo.a = 1;
    foo.b = 2;
}}
 

Dies führte zu

  f1:
        mov r0, # 1
        mov r2, # 2
        ldr r1, .L3
        ldr r3, .L3 + 4
str r0, [r1]
        str r2, [r3]
        bx lr
.L3:
        .word a
        .word b
f2:
        mov r1, # 1
        mov r2, # 2
        ldr r3, .L6
        stm r3, {r1, r2}
        bx lr
.L6:
        .word foo
 

Im Fall von f1 sehen wir, dass der Compiler die Adressen von a und b getrennt von Literalpools lädt (die Adressen in den Literalpools werden später vom Linker ausgefüllt).

Im Fall von f2 muss der Compiler die Adresse der Struktur jedoch nur einmal aus dem Literalpool laden, noch besser, weil er weiß, dass die beiden Variablen im Speicher nebeneinander liegen, und sie mit einem einzigen "Speicher schreiben kannmehrere "Anweisungen anstelle von zwei separaten Speicheranweisungen.

aber von einem Stil-POV ist dies besser oder schlechter als das Einfügen in die Dateien globals.c und / oder globals.h?

IMO, das davon abhängt, ob die Variablen tatsächlich zusammenhängen oder nicht.



Diese Fragen und Antworten wurden automatisch aus der englischen Sprache übersetzt.Der ursprüngliche Inhalt ist auf stackexchange verfügbar. Wir danken ihm für die cc by-sa 4.0-Lizenz, unter der er vertrieben wird.
Loading...