Hallo Gemeinde
Ich habe eine C-Funktion, welche lokale static Variablen benützt, um
z.B. den Zustand einer FSM zu speichern.
Nun möchte ich zu dieser Funktion eine Testfunktion schreiben. Dazu
müsste ich aber die statische Variable von aussen manipulieren können.
Gibt es eine halbwegs elegante Lösung, wie man von aussen (separates
Testmodul) an diese statische Variable gelangt?
Nein, wenn Du mit "lokal" eine lokale Variable innerhalb der Funktion
meinst.
Ja, wenn Du mit "lokal" eine lokale Variable innerhalb des C-Moduls
meinst in dem sich deine Funktion befindet. In diesem Fall mußt Du dem
Modul eine "set"-Funktion für diese Variable hinzufügen und via Header
sichtbar machen.
Neil
Ein relativ eleganter Weg wäre, die statics in ein eigenes Modul zu
packen, das während des Tests zur Laufzeit durch ein Mock-Modul ersetzt
wird. Das ist in OO-Sprachen eine gängige Vorgehensweise und leicht zu
realisieren, in C könnte das aber das ein klein wenig Hirnschmalz
erfordern.
Gruß
Markus
So wenn das jetzt völliger Schnurz ist killt mich bitte nicht ;-)
Die statische Variable bleibt konstant an einer Stelle im Speicher. Du
könntest also in eine externe Zeigervariable die Adresse deiner
statischen Variable in der Funktion schreiben. Diese Adressvariable
macht du dann mittels Header deinem Unit Test Modul sichtbar. Willst du
deine statische Variable verändern schreibt dein Testmodul auf die
Speicherstelle auf die der Zeiger zeigt. Das ganze macht du mittels
#define für den "normalen" Betrieb unsichtbar.
L.g.
Die Frage ist doch ob diese statische Variable Sinn macht. Man versucht
da ja innerhalb der Funktion eine Art Gedächtnis zu implementieren.
Das ist wie bei strtok().
Die Funktion ist nicht reentrant und führt in jeglichen
Multithreadinganwendungen zu nicht reproduzierbaren Fehlern.
Im Allgemeinen ist es oft besser statt einer static Variablen innerhalb
einer Funktion dieser Funktion eine Adresse auf eine ausserhalb
definierten Variable zu übergeben.
So können unterschiedliche Aufrufer (rekursiv / multithreading) auch
ggf. unterschiedliche Versionen der Variable halten.
So hab das jetzt schnell probiert. Problem hatte ich nur dass die
Adresse ja irgendwie initialisiert werden muss. Das hab ich mir einer
zusätzlichen init Variable gelöst. Aber das Prinzip mit der Adresse
funktioniert.
Hier mein Minimalcode:
1
#define UNIT_TEST
2
3
#ifdef UNIT_TEST
4
staticcharinit;
5
#endif
6
7
staticunsignedint*a_step;
8
9
voidfsm(void);
10
11
voidfsm(void){
12
staticunsignedintstep=0;
13
#ifdef UNIT_TEST
14
if(init){
15
a_step=&step;
16
init=0;
17
return;
18
}
19
#endif
20
21
switch(step){
22
case0:
23
printf("Case 1\n");
24
step++;
25
break;
26
case1:
27
printf("Case 2\n");
28
step++;
29
break;
30
case2:
31
printf("Case 3\n");
32
step=0;
33
break;
34
defualt:
35
break;
36
}
37
}
38
39
voidnormal(void){
40
printf("Normal Operation\n");
41
printf("Call 1\n");
42
fsm();
43
printf("Call 2\n");
44
fsm();
45
printf("Call 3\n");
46
fsm();
47
}
48
49
voidunitTest(void){
50
#ifdef UNIT_TEST
51
// call fsm first for init address
52
init=1;
53
fsm();
54
printf("Test case 3\n");
55
*a_step=2;
56
// test
57
fsm();
58
#else
59
fprintf(stderr,"Unit test not defined but function called\n");
@Michael
Danke für das Beispiel. Ja das funktioniert so. Jetzt müsste man das nur
noch in ein Makro packen können, damit es wenn möglich so ähnlich
aussieht:
1
voidfsm(void){
2
MY_SPECIAL_STATIC(unsignedint,step,0);
3
4
switch(step){
5
case0:
6
printf("Case 1\n");
7
step++;
8
break;
9
...
Das wäre wahrscheinlich sogar machbar.
@Markus
Das mit dem Auslagern von funktions-lokalen statischen Variablen ist mir
nicht klar, wie das gehen soll.
@Udo
Full ACK. Bei diesem Ziel-Projekt handelt es sich um ein Embedded
System, ohne Multi-Threading. Getestet wird allerdings auf dem PC, mit
GCC.
Natürlich macht es wenig Sinn, überall #ifdef UNIT_TEST einzustreuen.
Aber machnchmal geht's halt nicht anders. Und wegen in paar solcher
Situationen auf Unit-Tests zu verzichten, kann nicht die Lösung sein.
@Tom
Ich sehe da zwei Probleme:
- Teilweise habe ich Funktionen, die mehrere statische Variable haben.
- Der Aufrufer muss die Variable ja auch statisch halten. Am Schluss
sind dann alle statischen als Modul-global zu definieren. Das will ich
genau nicht.
Ich versuche es mal mit dem Ansatz von Michael.
Danke für Eure Meinungen.
... du solltest dir aber im klaren darüber sein, dass bereits ein #ifdef
UNIT_TEST eine Validierung deiner Funktion durch den Unit Test über den
Haufen wirft. Aber vielleicht kommt es hier darauf ja gar nicht an...
Offensichtlich geht es hier sowieso nicht darum, mehr Qualitaet zu
erreichen, sondern nur darum den Punkt "Unit-Tests" auf der
Anforderungsliste abzuhaken. Da ist es dann nicht wichtig, ob man mit
Ausnahmen fuer den Unit-Test denselbigen ad absurdum fuehrt.
Trenn halt einfach deine FSM in ein eigenes Modul ab, pack es in sein
eigenes C-File. Dann machst du deine State Variable anstatt
funktions-static file-static und schon ist das Problem keines mehr.
1
staticunsignedintstep;
2
3
voidfsm(void)
4
{
5
switch(step){
6
case0:
7
printf("Case 1\n");
8
step++;
9
break;
10
case1:
11
printf("Case 2\n");
12
step++;
13
break;
14
case2:
15
printf("Case 3\n");
16
step=0;
17
break;
18
defualt:
19
break;
20
}
21
}
22
23
voidunit_test()
24
{
25
step=0;
26
// sonstige Vorbedingungen herstellen
27
fsm();
28
if(step!=1)
29
printf("ERR001: Fehler in FSM. Zustandsübergang 0->1 nicht durchgeführt\n");
30
31
...
32
}
Die Funktion unit_test könnte man als Ganzes noch mit einem #ifdef aus
dem Compilevorgang ausschliessen, wenn man dem Linker nicht traut. Aber
mehr muss es nicht sein.
Die Organisation von C-Programmen fängt auf File-Ebene an. Ein C-File
ist ein Modul. Und das kann in sich geschlossen werden, so dass zwar
alle Funktionen innerhalb des Moduls sich Daten(strukturen) teilen
können, aber trotzdem ausserhalb keiner ran kann.
Udo Schmitt schrieb:> Die Frage ist doch ob diese statische Variable Sinn macht. Man versucht> da ja innerhalb der Funktion eine Art Gedächtnis zu implementieren.> Das ist wie bei strtok().> Die Funktion ist nicht reentrant und führt in jeglichen> Multithreadinganwendungen zu nicht reproduzierbaren Fehlern.Tom K. schrieb:> Wie Udo schon schrieb, sind static variablen für eine vernünftige> Testbarkeit des Codes fast so tödlich wie die bösen Singletons.
Hmmmm. Also fassen wir zusammen:
1.) Globale Variablen gelten bekanntlich als "böse" und man soll sie so
wenig verwenden wie eben möglich.
2.) static-Variablen innerhalb einer Funktion sind Euch zufolge
ebenfalls sehr pöse.
Was aber nun, wenn ich ein Gedächtnis brauche? Da gibt es doch etliche
Anwendungsfälle dafür, wo eben das Ergebnis einer Berechnung im
aktuellen Durchlauf abhängig ist von früheren Berechnungen. Beispiel:
Digitales Filter.
Wie setzt Ihr sowas um, dass es Euren hehren Ansprüchen genügt? Wilde
Verpointerung quer über alle Module? ;-)
Mark Brandis schrieb:> Hmmmm. Also fassen wir zusammen:> 1.) Globale Variablen gelten bekanntlich als "böse" und man soll sie so> wenig verwenden wie eben möglich.
richtig.
Man soll Funktionen mit Argumenten aufrufen und nicht Argumentpassing
per Zuweisung an globle Variablen lösen.
Darum geht es nämlich in Wirklichkeit bei der Argumentation "Globale
Variablen sind böse".
Darum und um gordnete Progammstrukturen mittels Datenkapselung, die
durch globale Variablen komplett ausgehebelt wird.
> 2.) static-Variablen innerhalb einer Funktion sind Euch zufolge> ebenfalls sehr pöse.
sie sind nicht böse. Nur in manchen Fällen unpraktisch. Weil es eben
eher selten ist, dass sich eine Funktion, und nur diese eine Funktion,
etwas von einem Aufruf zum nächsten merken muss. Spätestens wenn es für
den Mechanismus dann einen Init() oder Reset() geben muss, fällt man
damit auf die Schnauze oder landet bei wild zusammengewürfelten
Funktionsargumenten.
> Was aber nun, wenn ich ein Gedächtnis brauche?
Da gibt es dann immer noch die 3. Variante:
File-Static im Sinne von 'lokal zu einem ganzen Modul'. Und das ist eine
der sinnvolle Varianten, wie sich Module etwas funktionsübergreifend
merken können. Neben noch anderen Mechanismen, wie zb opaque Datentypen
(Ich gebe dem Aufrufer einen Pointer auf ein Datenpaket raus, welches
ich zur Speicherung meiner Daten benutze, gebe ihm aber weiter keine
Information darüber, wie dieses Datenpaket aufgebaut ist, so dass er mit
dem Speicher nichts anfangen kann. Wann immer er etwas von mir will,
gibt er mir den Pointer wieder und mit meiner Kentniss des Datenaufbaus
kann ich damit arbeiten. Vulgo: Der Aufrufer kriegt von mir einen void
Pointer auf die Daten und in der bearbeitenden Funktion wird er wieder
zurückgecastet.)
StinkyWinky schrieb:> Gibt es eine halbwegs elegante Lösung, wie man von aussen (separates> Testmodul) an diese statische Variable gelangt?
Hallo,
ich schlage folgende simple Lösung vor:
#ifdef UNIT_TEST
#define STATIC
#else
#define STATIC static
#endif
STATIC uint32 myGlobalVar_u32;
...
Viele Grüße !
Karl Heinz Buchegger schrieb:> Neben noch anderen Mechanismen, wie zb opaque Datentypen> (Ich gebe dem Aufrufer einen Pointer auf ein Datenpaket raus, welches> ich zur Speicherung meiner Daten benutze, gebe ihm aber weiter keine> Information darüber, wie dieses Datenpaket aufgebaut ist, so dass er mit> dem Speicher nichts anfangen kann. Wann immer er etwas von mir will,> gibt er mir den Pointer wieder und mit meiner Kentniss des Datenaufbaus> kann ich damit arbeiten. Vulgo: Der Aufrufer kriegt von mir einen void> Pointer auf die Daten und in der bearbeitenden Funktion wird er wieder> zurückgecastet.)
Klingt gut. Danke für die Erläuterung :-)