2009-04-13 9 views
4

Ich arbeite an einem Projekt auf einem 8051, wo jedes Byte zählt. Als solche verwende ich einige globale Variablen, wo ich normalerweise nicht würde. Die normale Methode, Zeiger in eine Funktion zu übergeben, fügt hier zu viel Overhead hinzu.Wie markiere ich Code mit Nebenwirkungen?

Ich habe eine Reihe von Funktionen, die einzelne Bitvariablen verwenden (eine Compiler-spezifische Erweiterung zu C), um das Ergebnis einer Funktion zusätzlich zu dem normalen Rückgabewert zu signalisieren.

bit global_error_flag = 0; 
bit global_data_ready_flag = 0; 

unsigned char A_Function (void) { 
    // Do some stuff 

    if (badness) { 
     global_error_flag = 0; 
     global_data_ready_flag = 1; 

     return 0; 
    } 

    if (data_is_ready_use) { 
     global_data_ready_flag = 1; 
    } 

    return a_value;  
} 

void Other_Function (void) { 
    unsigned char c; 

    c = A_Function(); 

    if(global_error_flag) { 
     // Do error stuff here. 
    } 
    else 
    if(global_data_ready_flag) { 
     // Do data processing here. 
    } 
    global_error_flag = 0; 
    global_data_ready_flag = 0; 

} 

Da die Technik böse ist, ist es eine Möglichkeit, ich den Code klarer machen kann?

Wie soll ich am besten angeben, welche Funktionsaufrufe den Nebeneffekt haben, diese Flags zu setzen? Sind Kommentare genug? Soll ich die Funktionen benennen, um ihre API (quasi-ungarisch-style) anzugeben? Sollte ich ein Makro verwenden, um solche Anrufe zu markieren:

#define FUNCTION_SETS_FLAGS(code) (code) 

FUNCTION_SETS_FLAGS(c = A_Function()); 

Irgendwelche anderen Ideen?

+0

Wenn Sie einen 8-Bit-Mikrocontroller mit 256 Byte RAM (sagen wir) verwenden. Dann sind die Zeiger auf den Datenbereich ebenfalls 8 Bits breit. Ich verstehe den Grund für die Verwendung der globalen Variablen nicht. Ich kann verstehen, wenn es zwischen ISR (Interrupt Service Routine) und der Hauptschleife geteilt wird. Aber ... – Alphaneo

+0

Das große Problem ist, dass Parameter jenseits des ersten entweder auf dem Stack übergeben werden können, oder indem ihnen Speicher zugewiesen wird (mit meinem speziellen Compiler). Wenn ich sage, ich habe 3 Flags für 7 Funktionen, dann verbraucht das zwischen 21 Bytes zusätzlichen Speicher. Die meisten davon benötige ich für die Pufferung von IO. Wenn ich also 12 Bytes auf 7 Bits reduzieren kann, ist das ein Nettogewinn von 20 Bytes Speicher. Das ist in diesem Zusammenhang riesig. Das Entpacken/Entpacken von Funktionsparametern benötigt auch mehr Code. – daotoad

Antwort

3

Ihre Globals sind für Klarheit markiert, das ist ein guter Anfang.

Idealerweise möchten Sie etwas, das nicht kompiliert wird, wenn Sie es falsch verstehen. Das bedeutet, dass Makros und Kommentare nicht funktionieren.

Ich bleibe bei einer Namenskonvention für die Funktionen - nicht unbedingt Ungarisch, aber so etwas wie A_Function_Returns_Flags, oder weniger wortreich, wenn Sie daran denken können. helfen Sie

6

Mit einer Konvention, ob Sie es "Ungarisch" nennen wollen oder nicht, ist der beste Weg, ich denke, dies zu markieren. Stilistisch wäre eine Art Benennungs-Präfix gegenüber dem leeren #define vorzuziehen, zumindest für mich.

Das ist eigentlich ziemlich üblich, denke ich. Ich weiß, dass die S60-Programmierumgebung viele herkömmliche Tags für Funktionen verwendet, um beispielsweise anzugeben, dass sie Ausnahmen auslösen.

2

Dies gilt nicht wirklich, aber GCC hat einen Weg, um die gegenüber von dem zu tun, was Sie wollen: Funktionen markieren, die keine Nebenwirkungen haben. Siehe die const und pure Attribute. Dies ist mehr für die Optimierung als für die Dokumentation gedacht: Wenn der Compiler weiß, dass eine bestimmte Funktion keine anderen Daten als ihre Argumente untersucht, kann sie intelligentere Optimierungen wie loop-invariant code motion durchführen.

0

Zuerst würde ich versuchen, es so zu kodieren, dass es nur einen Hersteller und nur einen Verbraucher für jedes dieser Flags gibt. Dann würde ich nur wenn nötig eine Flagge löschen/setzen. Wie zum Anzeigen des Nebeneffektes, ein Standard-Header auf der Funktion, doxygen Stils, sollte genug sein:

// Function func 
    // Does something 
    // Consumes ready_flag and sets error_flag on error. 

    int func() 
    { 
     if (ready_flag) 
     { 
      //do something then clear the flag 
      if (some_error) 
       error_flag = x; 
      ready_flag = 0; 
     } 
     //don't mess with the flags outside of their 'scope' 
     return 0; 
    } 

Auf der anderen Seite, wenn der Fehler und bereit Fahnen gegenseitig ausschließen, könnten Sie ein Byte (oder Bits innerhalb eines Bytes/Registers), um die Bereitschaft oder einen Fehlerzustand anzuzeigen.

0 für einen Fehler, 1 für nicht-ready/fehlerfrei und 2 für ready/fehlerfrei (oder -1, 0, 1, was auch immer)

IIRC, der Standard-8051-Befehlssatz ‚doesn t arbeiten mit einzelnen Bits, so dass die Verwendung eines ganzen Bytes für (verschiedene) Flags nicht zu einem enormen Leistungseinbruch führen sollte.

+1

Der 8051 verfügt über Speicherbereiche, die Bit-adressierbar sind. Interne Speicherbytes 20-2F, die Bit adressierbarem Raum zugeordnet sind. Bits sind einstellbar mit setb und clr; während jb, jbc und jnb logische Tests durchführen. Also ist "setb 00h" dasselbe wie "orl 20h, # 0x01", außer effizienter. – daotoad

+0

Heh, Lebe und lerne. Als ich die 8051 benutzte hatte ich die absurde Menge von 64kB ROM und 2kB RAM. Damit habe ich mich nicht um so viel Effizienz gekümmert (das Projekt hat 59kB/1.5kB max verwendet) = D –

+0

Ja, ich mag diese Art von Projekt auch besser. Auf diese Weise kann ich für Lesbarkeit und Vernunft schreiben und muss nicht jedes verwendete Bit und Byte sorgfältig abwägen. – daotoad

3

Ich habe meinen Ph.D. zu einem ähnlichen Problem in Java. Ich kann Ihnen das eine sagen, was Sie nicht tun sollten: Verlassen Sie sich nicht auf die Dokumentation, denn dann sind Sie auf jemanden angewiesen, der es tatsächlich liest. Sie müssen einen Hinweis im Methodennamen hinzufügen, um anzugeben, dass der Benutzer die Dokumente lesen sollte, um sich über Nebenwirkungen zu informieren. Wenn Sie etwas auswählen und damit konsistent sind, haben Sie wahrscheinlich die meisten Chancen.

3

Wenn Sie nur erwähnen möchten, dass eine Funktion globale Variable (n) beeinflusst, kann ein einfaches (ungarisches) Präfix hilfreich sein.

Aber wenn Sie jedes einzelne Flag (s), das es betrifft, erwähnen möchten, dann ist die Verwendung der Funktion Header wahrscheinlich der Weg zu gehen. Wie zum Beispiel, könnte

/************************************************************************* 
    * FUNCTION : <function_name> 
    * DESCRIPTION : <function description> 
    * PARAMETERS : 
    * Param1 - <Parameter-1 explanation> 
    * Param2 - <Parameter-2 explanation> 
    * Param3 - <Parameter-3 explanation> 
    * RETURN  : <Return value and type> 
    * GLOBAL VARIABLES USED: 
    * Global1 - <Global-1 explanation> 
    * Global2 - <Global-2 explanation> 
    * Global3 - <Global-3 explanation> 
    *************************************************************************/ 
2

Sie einen Makro verwenden, um die Funktion zu simulieren, um weitere Parameter:


unsigned char _a_function(void); 

#define A_Function(ret_val) (*(ret_val) = _a_function(), !global_error_flag) 

... 
unsigned char var; 
/* call the function */ 
if (!A_Function(&var)) 
{ 
    /* error! */ 
} 
else 
{ 
    /* use var */ 
    var++; 
} 

ich es nicht zu kompilieren habe versucht, so kann nicht sagen, dass dies funktionieren wird, aber Ich denke es sollte.

-2

Wenn Sie wirklich mit diesen globalen Variablen zu halten haben, können Sie es auf der Hand, dass eine Funktion sich durch erwarteten Verweis auf sie als Funktionsargument ändern kann:

unsigned char A_Function (bit *p_error_flag, bit *p_data_ready_flag) 
{ 
    ... 
} 
+3

Ich arbeite an einem System mit 8 8-Bit-Register und nur 256 Bytes Speicher. Die 8 Register verbrauchen 8 der 256 Bytes des Speichers. Wenn Zeiger verwendet werden, werden Ressourcen verbraucht, die nicht verfügbar sind. Selbst unbenutzte Hinweise zu übergeben ist verschwenderisch. Sonst würde ich Zeiger verwenden. – daotoad

0

Wenn Sie dies noch nicht getan Vielleicht möchten Sie auch die sdcc project on sourceforge auschecken, es ist ein C-Compiler speziell für die Embedded-Entwicklung, die auch die 8051 zielt, zusätzlich unterstützt der Compiler eine Reihe von benutzerdefinierten, zielspezifischen und nicht-Standard-Compiler-Intrinsics für verschiedene Anwendungsfälle, ich persönlich habe das Entwicklungsteam persönlich als sehr offen für Ideen für neue Erweiterungen und andere damit verbundene Feature-Anfragen empfunden.

Verwandte Themen