Forum: Compiler & IDEs Wie Messwerte im Programm verfügbar machen ohne globale Variablen


von Mike Litoris (Gast)


Lesenswert?

Hi!

Ich habe folgende Situation:
Ich lese auf meinem Mikrocontroller zyklisch mehrere Sensoren ein. Diese 
müssen dann für mehrere Funktionen zur Verfügung stehen:
- Schreiben auf SD Karte
- Steuern von LEDs
- Regeln eines Motors

Bis jetzt habe ich das immer so gemacht, dass die Messwerte in globale 
Variablen geschrieben wurden, diese können dann von den einzelnen 
Funktionen abgerufen werden.

Ich muss dazu sagen, dass ich absolut kein Profi der Programmierung bin. 
Ich "habe gehört", dass globale Variablen aufgrund Speicherverbrauch und 
Sicherheit zu vermeiden sind.

Wie löst man das intelligenter? Ist die Übergabe mit Pointern hier 
richtig? Ich bin mir leider auch nicht sicher, wie man sowas am besten 
angeht...

Grüße und Danke!

von PfuschenderAutodidakt (Gast)


Lesenswert?

Mike Litoris schrieb:
> Wie löst man das intelligenter? Ist die Übergabe mit Pointern hier
> richtig? Ich bin mir leider auch nicht sicher, wie man sowas am besten
> angeht...

Ich verwende für sowas Pointer auf Strukturen.
Es gibt z.B. ein struct "Messwerte". Das ist im Header der Funktion 
deklariert (Ausschließlich als type, nicht als struct!).

Jede Funktion, die die Messwerte benötigt, benötigt halt dann den header 
und bekommt einen pointer darauf.

Beispiel (LED-Streifen):
1
//im header:
2
typedef struct
3
{
4
    uint8_t     ch_red;                 //channel setting : which is connected to what on the LPC6803: 
5
                                        //0 = OUT0, 1= OUT1, 2 = OUT2, other = invalid
6
    uint8_t     ch_green;               // ""
7
    uint8_t     ch_blue;                // ""
8
    uint16_t    NrOfLED;                //Nr. Of Pixels in series
9
    uint8_t     stripe_nr;              //number of stripe
10
    bool        active;                 //stripe active / inactive
11
    rgb         col;                    //color
12
    rgb         data[stripe_max_len];   //pixel data for stripe1
13
}LED_CFG;
14
15
//Funktion:
16
void set_Stripe(LED_CFG *cfg, uint8_t brightness);
Das Struct wird einmal im main als Variable deklariert, und nur der 
Pointer herumgereicht.

Im Beispiel hat jeder LED-Streifen ein eigenes Struct vom Type LED_CFG.

Structs lassen sich beliebig verschachteln. Man kann sich auf die Art im 
main() eine Struktur deklarieren, die alle Daten enthält, und in der 
alle Funktionsblöcke logisch gegliedert sind.
z.B. kann man der Struktur "Messung" eine Substruktur "ADC" eingliedern, 
die eine Substruktur "ADC-settings" enthält.
Die Übersicht ist leichter zu behalten, als bei einzelnen Variablen.

Den Speicherverbraucht sieht man auch sofort.

Wichtig ist: Eine Funktion bekommt ausschließlich die Substruktur, die 
sie WIRKLICH benötigt! Mehr führt zum Verlust der Übersicht, und zu 
schlechter Wiederverwendbarkeit des Codes!

Ob das so gut ist, weiß ich aber auch nicht, denn ich bin auch nur 
Autodidakt.
Ich bin damit bisher gut gefahren, vor allem bei richtig komplexen 
Projekten mit hunderten oder tausenden Variablen. Man benötigt keine 
statics mehr, das ist auch hilfreich.

von Sebastian S. (amateur)


Lesenswert?

Ich bin bisher davon ausgegangen, das eine Variable immer ihren Platz 
benötigt. Egal ob lokal oder global.

Soweit mir bekannt ist auch das alignment gleich.

Bei der unnötigen Verwendung von Zeigern gilt:
Mehr Programmspeicher (Zeiger holen + Daten holen)
etwas langsamer, da mindestens zwei Schritte.

von Teddy (Gast)


Lesenswert?

Sebastian S. schrieb:
> Ich bin bisher davon ausgegangen, das eine Variable immer ihren
> Platz
> benötigt. Egal ob lokal oder global.
>
> Soweit mir bekannt ist auch das alignment gleich.
>
> Bei der unnötigen Verwendung von Zeigern gilt:
> Mehr Programmspeicher (Zeiger holen + Daten holen)
> etwas langsamer, da mindestens zwei Schritte.

Der Speicherbereich einer lokalen Variable außerhalb der Main-Fkt wird 
immer nach beenden der Fkt immer freigegeben, bei Globalen dagegen 
nicht.

von Carl D. (jcw2)


Lesenswert?

Sebastian S. schrieb:
> Ich bin bisher davon ausgegangen, das eine Variable immer ihren Platz
> benötigt. Egal ob lokal oder global.
>
> Soweit mir bekannt ist auch das alignment gleich.
>
> Bei der unnötigen Verwendung von Zeigern gilt:
> Mehr Programmspeicher (Zeiger holen + Daten holen)
> etwas langsamer, da mindestens zwei Schritte.

Je nach (unbekanntem) μC ist da kein Unterschied.
Und wenn wie beim AVR8 absolute Addressierung vorhanden ist, dann muß 
die Adresse im Befehl stehen, was (AVR8) 16Bit extra macht. Bei ARM gibt 
es keine absoluten Adressen, immer nur relativ zu einem Register, also 
egal ob globale Variablen oder Zeiger auf Strukturen.

von Sebastian S. (amateur)


Lesenswert?

@Teddy
Das es "Probleme" mit der Lebensdauer und der Sichtbarkeit gibt ist mir 
bekannt. Ich bezog mich auch nur auf die verwendete Größe.

von Alex D. (allu)


Lesenswert?

Mike Litoris schrieb:
> Bis jetzt habe ich das immer so gemacht, dass die Messwerte in globale
> Variablen geschrieben wurden, diese können dann von den einzelnen
> Funktionen abgerufen werden

Gute Lösung ! Und ist auch schneller.
Früher als der Ram-Speicherplatz knapp war, ok, aber heute...

von Programmiersprachentheaterintendant (Gast)


Lesenswert?

> Ich verwende für sowas Pointer auf Strukturen.

> Das Struct wird einmal im main als Variable deklariert, und nur der
> Pointer herumgereicht.

Schön und übersichtlich, leider keinen Deut anders als /globale 
Variablen/ weil immernoch... global und ungeschützt weil jeder beliebige 
Code komplett in der Struktur rumwühlen kann bis zum umfallen.

Du suchst nach eingeschränkte Sichtbarkeit (C) und Kapselung 
(allgemein).

Ein erster, einfacher Ansatz f. C ist die strukturierte Variable 
ausschliesslich in einer Implementationsdatei zu haben und nur per 
Funktionen welche via Headerdatei zugänglich gemacht werden bedient 
werden.

Bevor jemand wg. Overhead nölt: richtig geschrieben[TM] ist der nur zur 
Compiletime, bringt zusätzlichen Schutz und kostet exakt Null zur 
Laufzeit.

von Alex D. (allu)


Lesenswert?

Programmiersprachentheaterintendant schrieb:
> Bevor jemand wg. Overhead nölt: richtig geschrieben[TM] ist der nur zur
> Compiletime, bringt zusätzlichen Schutz und kostet exakt Null zur
> Laufzeit.

Aha, danke, ich dachte bisher jedesmal neu anlegen und freigeben würde 
Zeit benötigen.

: Bearbeitet durch User
von A. S. (Gast)


Lesenswert?

globale Variablen sind böse, wenn
- von verschiedenen Stellen wild darauf geschrieben wird
- von parallelen Threads davon gelesen wird, ohne Konsistenz zu 
beachten.
- in C viele davon verwendet werden, da es nur einen Namespace gibt. 
Also dutzende von Variablen wie MyValue1 oder Temp.

Wenn eine Variable (z.B. ein Init-Flag oder ein i) lokal sein kann, oder 
zumindest static in einer Datei, dann sollte man das auch gerne tun.

Und als Anfänger hilft es ungemein, globale Variablen zu meiden.

Für den Profi sind Konventionen oder const/volatile-Qualifier für 
globale Variablen wirksamer als eine Pseudokapslung mit Pointer oder 
Getter/Setter.

von Dumdi D. (dumdidum)


Lesenswert?

Programmiersprachentheaterintendant schrieb:
> Schön und übersichtlich, leider keinen Deut anders als globale Variablen
> weil immernoch... global und ungeschützt weil jeder beliebige Code
> komplett in der Struktur rumwühlen kann bis zum umfallen.

Naja, hilft da nicht einfachr 'static' vor der Struktur? Ich finde die 
Idee mit der Pointeruebergabe eingentlich ganz gut.

von PfuschenderAutodidakt (Gast)


Lesenswert?

Dumdi D. schrieb:
> Programmiersprachentheaterintendant schrieb:
>> Schön und übersichtlich, leider keinen Deut anders als globale Variablen
>> weil immernoch... global und ungeschützt weil jeder beliebige Code
>> komplett in der Struktur rumwühlen kann bis zum umfallen.
>
> Naja, hilft da nicht einfachr 'static' vor der Struktur? Ich finde die
> Idee mit der Pointeruebergabe eingentlich ganz gut.

Jeder Codeteil bekommt nur den Zeiger auf den Zweig der Struktur, den er 
benötigt. Der Codeteil KANN andere Teile der Struktur gar nicht 
bekommen, er bekommt den nötigen Type gar nicht. Sonst wäre mein Konzept 
ziemlich unsinnig.

Ich mache das hirachisch.

Nehmen wir an:
- Der ADC hat ein ADC_Config struct
- Die Messfunktion ein "MESS" struct

wenn man Messung() aufruft, bekommt die einen Pointer auf "MESS".
Messung ruft init_ADC() auf, das bekommt einen Pointer auf den Zweig 
"ADC_Config", und nicht mehr.

Das C-File, das init_ADC() enthält, kennt "MESS" gar nicht, weil der 
nötige Header gar nicht inkludiert ist.

Warum das sinvoll ist:
Will man init_ADC() in einem neuen POrojekt verwenden, will man den 
Header von "MESS" nicht mitziehen.

von Programmiersprachentheaterintendant (Gast)


Lesenswert?

Alex D. schrieb:
> Programmiersprachentheaterintendant schrieb:
>> Bevor jemand wg. Overhead nölt: richtig geschrieben[TM] ist der nur zur
>> Compiletime, bringt zusätzlichen Schutz und kostet exakt Null zur
>> Laufzeit.
>
> Aha, danke, ich dachte bisher jedesmal neu anlegen und freigeben würde
> Zeit benötigen.

[X] Du sollst nicht die Kerzen vom Kristbaum fressen.
[  ] Ich hab ausschliesslich von lokalen Variablen geschrieben.
[  ] Du hast Kapselung verstanden.

von Dumdi D. (dumdidum)


Lesenswert?

PfuschenderAutodidakt schrieb:
> Der Codeteil KANN andere Teile der Struktur gar nicht bekommen, er
> bekommt den nötigen Type gar nicht.

Ich finde Deine Idee prima. Theoretisch kann jedoch auf eine globale 
Variable ohne static natuerlich zugegriffen werden, aber das ist nur 
relevant falls die Programme in einem Team entwickelt werden.

von PfuschenderAutodidakt (Gast)


Lesenswert?

Dumdi D. schrieb:
> PfuschenderAutodidakt schrieb:
>> Der Codeteil KANN andere Teile der Struktur gar nicht bekommen, er
>> bekommt den nötigen Type gar nicht.
>
> Ich finde Deine Idee prima. Theoretisch kann jedoch auf eine globale
> Variable ohne static natuerlich zugegriffen werden, aber das ist nur
> relevant falls die Programme in einem Team entwickelt werden.

Man ist nicht gezwungen, die Struktur global anzulegen.
Man kann sie einfach im main "lokal" deklarieren. Oder woanders. Nur 
muss man sicherstellen, dass der Speicher reserviert bleibt - also 
entweder als static, oder im main loop (das man ja nie verlässt).
Oder sogar nur mit malloc(sizeof(type));

Eines sollte klar sein:
Im Prinzip ist durch die Deklarierung als "typedef" nur eine Maske 
definiert, die über einen Speicherbereich gelegt werden kann.
Konkret Speicher belgt man erst dann, wenn man eine Variable des Typs 
anlegt.

von Sheeva P. (sheevaplug)


Lesenswert?

PfuschenderAutodidakt schrieb:
> Man ist nicht gezwungen, die Struktur global anzulegen.

Spätestens wenn ein Interrupt auf die Daten zugreifen muß, wirst Du wohl 
nicht darum herum kommen, die Strutur oder Teile davon global anzulegen.

von Nop (Gast)


Lesenswert?

Mike Litoris schrieb:

> Bis jetzt habe ich das immer so gemacht, dass die Messwerte in globale
> Variablen geschrieben wurden, diese können dann von den einzelnen
> Funktionen abgerufen werden.

Das ist auch sauber so. Die Variablen werden von einer einzigen Stelle 
aus geschrieben und können von überall her gelesen werden. Single 
producer, multiple consumer. Die Hauptbaustelle ist hier natürlich 
atomic access, das mußt Du schon sicherstellen.

von PfuschenderAutodidakt (Gast)


Lesenswert?

Sheeva P. schrieb:
> PfuschenderAutodidakt schrieb:
>> Man ist nicht gezwungen, die Struktur global anzulegen.
>
> Spätestens wenn ein Interrupt auf die Daten zugreifen muß, wirst Du wohl
> nicht darum herum kommen, die Strutur oder Teile davon global anzulegen.

Naja, man kann dem Interrupt einen Pointer setzen. Selbstverständlich 
nur einen auf die Substruktur, die benötigt wird. Der Pointer muss 
global sein, das stimmt, aber man braucht ihn nicht projektglobal 
überall als "extern ..." durchzuschleifen.

In solchen Fällen habe ich der Initfunktion dann den Pointer übergeben, 
diese setzt ihn dann auf den übergebenen Wert.
Man kommt aber erstaunlich oft ohne Daten in einer ISR aus.

Hier ist es wichtig, nur die "lokale" Substruktur zu verwenden. Z.B. das 
Strukturelement "ADC" in ADC.c welches in ADC.h deklariert ist, und 
nicht mehr.
Würde man die Struktur verwenden, die die substruktur ADC enthältlt, 
müsste man der Funtkion alle verwendeten Typen bekanntgeben. Das macht 
Code unübersichtlich.

von temp (Gast)


Lesenswert?

Mike Litoris schrieb:
> Ich "habe gehört", dass globale Variablen aufgrund Speicherverbrauch und
> Sicherheit zu vermeiden sind.

Viele Leute haben viele Meinungen und geben oft genug Dünnschiss von 
sich. Es gibt keine globale Regel an die man sich halten sollte oder 
muss. Für alles gibt es Gründe dafür oder dagegen. Und wenn du hier im 
Forum mitliest, wirst du oft genug merken wie die Weltanschauungen 
aufeinanderprasseln. C gegen C++ oder asm, Tasteneinlesen per GPIO 
Interrupt oder Polling, Debugger oder printf, goto u.s.w. Am besten du 
schaust dir Codeschnipsel im Netz an und bildest dir deine eigene 
Meinung ob eine bestimmte Aufgabenstellung für dich elegant oder nicht 
gelöst wurde. Am wenigsten solltest du Leuten und ihrer Meinung 
vertrauen die für alles festgeklopfte Meinungen haben und darauf wie die 
Moralapostel herumreiten. Meistens schreien die leider am lautesten...

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.