Forum: Mikrocontroller und Digitale Elektronik Was macht man mit USE_FULL_ASSERT?


von Florian (Gast)


Lesenswert?

Hallo,

ich bin mal wieder über das #define USE_FULL_ASSERT gestolpert. Was 
genau kann ich damit machen? Wie wende ich es an? Was verbirgt sich 
dahinter?
1
#ifdef USE_FULL_ASSERT
2
void assert_failed(uint8_t* file, uint32_t line) 
3
{
4
    //printf("Wrong parameters value: file %s on line %d\r\n", file, line);
5
    while (1)
6
        ;
7
}
8
#endif
Gruß
Florian

von Karl H. (kbuchegg)


Lesenswert?

Florian schrieb:

> ich bin mal wieder über das #define USE_FULL_ASSERT gestolpert. Was
> genau kann ich damit machen?

Den besagten Code, der sich zwischen
1
#ifdef USE_FULL_ASSERT
2
...
3
... besagter Code
4
...
5
#endif

von der Compilierung ausschliessen oder mitcompilieren lassen.

Irgendwo vor diesem besagtem Code gibt es ein
1
#define USE_FULL_ASSERT
steht es so da, dann wird der nachfolgende besagte Code mitcompiliert, 
es gibt dann die Funktion.
Wirfst du dieses #define raus, zb in dem du es auskommentierst
1
// #define USE_FULL_ASSERT
dann wird besagter Code nicht mitcompiliert. Die Funktion existiert dann 
nicht.
Ist zb nützlich, wenn man in der Debug-version sich ein paar spezielle 
Funktkionen machen will, die man im endgültigen Code nicht mit 
ausliefern will, die man aber auch nicht löschen will, weil man sie 
irgendwann mal (beim nächsten zu fixenden Bug) brauchen könnte.

: Bearbeitet durch User
von Jonas B. (jibi)


Lesenswert?

>Wie wende ich es an?
1
#define USE_FULL_ASSERT

>Was verbirgt sich
>dahinter?

Das hast du ja rausgelöscht...... Kopfschüttel?!

Gruß Jonas

: Bearbeitet durch User
von Florian (Gast)


Lesenswert?

Also entweder habe ich mich nicht klar ausgedrückt oder Ihr mich falsch 
verstanden. Vieleicht ja auch beides :-)

Was Präprozessorbefehle sind ist mir klar!

Ich möchte wissen wann das "assert_failed" angesprungen wird. Wann wird 
es angesprochen und wie kann ich mir diese Funktion zunutze machen.

von Karl H. (kbuchegg)


Lesenswert?

Florian schrieb:
> Ich möchte wissen wann das "assert_failed" angesprungen wird.

Wie wärs, wen du dir einfach mal im Code ansiehst, wo überall Aufrufe zu 
dieser Funktion vorkommen?

Standardfunktion ist das keine. Auch kein dokumentierter 
Standardmechanismus. Wenn du also eine Antwort willst: Die Antwort 
steckt im Code der vor die auf dem Bildschirm angezeigt wird.

von Uwe B. (derexponent)


Lesenswert?

ich vermute mal es geht um eine STM32 CPU
und die "standard peripheral libs" von ST
(falls nicht meinen Beitrag ignorieren)
1
RCC_AHB2PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);

dieser Fehler müsste zu einem "assert_failed" führen
es werden die Übergabeparameter auf plausibilität geprüft
und "RCC_AHB1Periph_GPIOB" ist in dieser Funktion nicht gültig

Gruss Uwe

: Bearbeitet durch User
von Florian (Gast)


Lesenswert?

Uwe B. schrieb:
> ich vermute mal es geht um eine STM32 CPU
> und die "standard peripheral libs" von ST
> (falls nicht meinen Beitrag ignorieren)
> RCC_AHB2PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
>
> dieser Fehler müsste zu einem "assert_failed" führen
> es werden die Übergabeparameter auf plausibilität geprüft
> und "RCC_AHB1Periph_GPIOB" ist in dieser Funktion nicht gültig
>
> Gruss Uwe

Richtig. Darum geht es :-)

Weisst Du ob man auch eigene Sachen auf Plausibilität prüfen kann oder 
ist das nur fir die Std. Lib. eigenen Funktionen?

von Karl H. (kbuchegg)


Lesenswert?

Florian schrieb:

> Richtig. Darum geht es :-)

Hättest du auch mal sagen können.

> Weisst Du ob man auch eigene Sachen auf Plausibilität prüfen kann oder
> ist das nur fir die Std. Lib. eigenen Funktionen?

Ich denke, du liest da etwas zuviel heraus.
So ein assert Makro (und darauf läuft es ja letzten Endes hinaus) ist im 
Prinzip eine recht einfache Sache.

Das ist einfach nur eine Bedingung, die true ergeben muss. Ist sie das 
nicht, dann wird irgendwas gemacht, zb eine Meldung irgendwo ausgegeben. 
Da steckt im Grunde nicht viel dahinter.
1
#define assert(x)   if(!(x)) assert_failed( __FILE__, __LINE__ );


und das benutzt du dann um zb überprüfen zu lassen, ob zum Bleistift 
Funktionsargumente im erlaubten Bereich sind
1
void setValue( int x )
2
{
3
  // x darf nicht kleiner als 5 und nicht größer als 10 sein
4
5
  assert( x >= 5 && x <= 10 );
6
7
  ....
8
}

"assert" bedeutet auf englisch einfach nur "behaupten, feststellen, 
versichern".
D.h. an dieser Stelle steht im Code mehr oder weniger: Hier gilt die 
Behauptung, dass das x sich in diesem Bereich 5 bis 10 bewegen wird. 
Behaupten kann man vieles, wir überprüfen das".

Der Sinn eines assert besteht darin, dass man seinen Debug Code mit 
derartigen Prüfungen spicken kann. Da es sich um ein Makro handelt, kann 
man ganz leicht durch ein anderes #define dafür sorgen, dass im 
Auslieferungscode diese Überprüfungen vom Compiler entfernt werden und 
damit dann keine Rechenzeit mehr benötigen.

Ein Assert ist also eine Debughilfe, die während der Entwicklungszeit 
zum tragen kommt, damit man möglichst schnell auf potentielle Fehler 
hingewiesen wird. Wird die Funktion SetValue mit einem Wert von 3 
aufgerufen, obwohl das eigentlich nicht sein dürfte, dann kriegt man 
sofort eine entsprechende Meldung (wie auch immer die dann aussieht) und 
man kann dem nachgehen, wie es zu einem derartigen Aufruf gekommen ist.

Und natürlich kannst du derartige Assertions auch für deine eigenen 
Zwecke benutzen. Für alles Mögliche.
Aber automatisch (und genau deshalb denke ich, du liest da zuviel 
heraus) passiert da überhaupt nichts. Wenn du etwas zu Debug-Zwecken 
überprüft haben willst, dann musst du die entsprechende Bedingung an der 
entpsrechenden Stelle hinschreiben.

Die Funktion assert_failed hat ganz offensichtlich die Aufgabe, genau 
diese Meldung zu generieren. Wobei das natürlich auf einem µC nicht so 
einfach ist, wenn man kein Terminal oder dergleichen zur Verfügung hat.

http://www2.hs-fulda.de/~klingebiel/c-stdlib/assert.htm

: Bearbeitet durch User
von Uwe B. (derexponent)


Lesenswert?

klar kannst du deine eigene Funktionen auch absichern :
1
typedef enum
2
{ 
3
  Baud_115200 =0,
4
  Baud_38400, 
5
  Baud_19200,
6
  Baud_9600
7
}BAUDRATE_t;
8
#define IS_BAUDRATE(BAUD_TYP) (((BAUD_TYP) == Baud_115200 ) || ((BAUD_TYP) == Baud_38400) ||
9
((BAUD_TYP) == Baud_19200) ||  ((BAUD_TYP) == Baud_9600)) 
10
11
12
13
void setBaudrate(BAUDRATE_t baud_nr)
14
{
15
  uint32_t baudrate;
16
17
  // check ob "baud_nr" gueltig
18
  assert_param(IS_BAUDRATE(baud_nr));
19
20
  // falls "baud_nr" gueltig war :
21
  // setzen der baudrate
22
  switch(baud_nr) {
23
    case Baud_115200 : baudrate=115200;break;
24
    case Baud_38400  : baudrate=38400;break;
25
    case Baud_19200  : baudrate=19200;break;
26
    case Baud_9600   : baudrate=9600;break;    
27
  }
28
}


aber ob das die mühe wert ist ?
einfacher finde ich defaultwerte zu benutzen :

1
void setBaudrate(BAUDRATE_t baud_nr)
2
{
3
  uint32_t baudrate=9600; // default baudrate
4
5
  // setzen der baudrate
6
  switch(baud_nr) {
7
    case Baud_115200 : baudrate=115200;break;
8
    case Baud_38400  : baudrate=38400;break;
9
    case Baud_19200  : baudrate=19200;break;
10
    case Baud_9600   : baudrate=9600;break;
11
  }
12
}

wobei da halt ein fehler "unbemerkt" korrigiert wird
kommt halt auf die Anwendung an

Gruss Uwe

: Bearbeitet durch User
von Florian (Gast)


Lesenswert?

Danke Euch beiden für die ausführlichen Erklärungen!

@Karl Heinz
Ja Du hast recht. Ich habe da wirklich zu viel rausgelesen :-)

@Uwe
Danke für das Beispiel. Daran habe ich die Anwendung sehr gut verstehen 
können. Ich denke auch, das es die Mühe nicht Wert ist. Bei kritischen 
Werten (z.B. Array-Index) frage ich die Werte sowieso immer noch mal zur 
Sicherheit im Code ab.

Gruß
Florian

von Karl H. (kbuchegg)


Lesenswert?

Florian schrieb:

> können. Ich denke auch, das es die Mühe nicht Wert ist. Bei kritischen
> Werten (z.B. Array-Index) frage ich die Werte sowieso immer noch mal zur
> Sicherheit im Code ab.

Das musst du sowieso. EIn assert ist nicht mit einer sauberen 
Fehlerbehandlung zu verwechseln. Ein assert ist ein Debug-Werkzeug, 
damit Fehler nicht unentdeckt bleiben. Selbst wenn dann der Code in 
weiterer Folge damit klar kommt. Bei einem assert geht es darum, dass 
der Programmierer möglichst schnell merkt, dass irgendwas nicht stimmt 
und sich darum kümmern kann.

Was man zb oft asserted, sind Parameter die Pointer sind
1
void strComplexOperation( const char* str )
2
{
3
  assert( str );
4
5
  mach mit str eine komplexe Operation
6
}

die Funktion darf nicht mit einem NULL Pointer aufgerufen werden. Macht 
auch nicht viel Sinn. Trotzdem kann es natürlich schon mal passieren. 
Natürlich wird niemand schreiben
1
 ...
2
    strComplexOperation( NULL );
3
...

aber wenn der String aus einer Datenstruktur kommt, sagen wir mal aus 
einem Array
1
char* texte[20];
2
int  nrTexte;
3
4
...
5
  for( i = 0; i < nrTexte; i++ )
6
    strComplexOperation( texte[i] );
7
...
und du dir einen Hund reingehauen hast, so dass nrTexte um 1 zu gross 
ist, dann kann es schon passieren, dass die Funktion mit einem NULL 
Pointer gerufen wird. Der Fehler liegt dann nicht in der Funktion, er 
liegt auch nicht im Array sondern in einem Fehler ganz woanders. 
Trotzdem wirkt er sich so aus, dass die Funktion etwas illegales tun 
würde.
Ohne assert kommt man nur über Umwege drauf, dass da irgendwas abgeht. 
Mit dem assert meldet das System sofort: Sieh dir das bitte mal an - die 
Funktion wurde mit einem NULL Pointer gerufen, obwohl das eigentlich 
nicht sein sollte.
Und das ist dann schon ein Fortschritt, weil dir das Programm selber 
helfen kann, dich auf Fehlersituationen aufmerksam zu machen.
Aber ein assert ist keine Fehlerbehandlung in dem Sinne, dass damit 
alles gut wäre. Wenn diese strComplexOperation Funktion Teil einer 
Library ist, und somit allgemein verwendbar sein soll, dann bleibt es 
nicht aus, dass man da dann noch eine saubere Fehlerbehandlung einbaut
1
void strComplexOperation( const char* str )
2
{
3
  assert( str );
4
5
  if( str ) {
6
    mach mit str eine komplexe Operation
7
  }
8
}

Der assert bleibt natürlich trotzdem drinnen, denn in der Debug Version 
des Programms will man ja haben, dass das Programm aktiv an der 
Fehlersuche teilnimmt. In der Release Version des Programms werden die 
assert durch ein entsprechendes #define still gelegt und sind dann für 
deinen Anwender nicht mehr vorhanden. Dann muss die Fehlerbehandlung die 
noch vorhandenen Probleme behandeln können - aber diesmal ohne Meldung 
an dich :-)

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Wobei natürlich diese 'Meldungsgenerierung'
1
#ifdef USE_FULL_ASSERT
2
void assert_failed(uint8_t* file, uint32_t line) 
3
{
4
    //printf("Wrong parameters value: file %s on line %d\r\n", file, line);
5
    while (1)
6
        ;
7
}
8
#endif

eine Wendung hat, die der Umgebung eines µC geschuldet ist. Hier wird ja 
nichts ausgegeben, sondern der µC einfach in einer Endlosschleife 
gefangen gehalten. Die Funktion ist daher eher als eine Art Vorlage 
gedacht. So nach dem Muster: sieh dir mal den printf an. So würde das 
gehen, wenn es einen funktionierenden printf gäbe. Wenn du ein LCD hast, 
dann musst du eben den printf durch eine entsprechende LCD Ausgabe 
ersetzen. Oder durch eine Ausgabe auf die UART. Oder was du eben hast, 
so dass sich das Programm zur Laufzeit bemerkbar machen kann.
Klar, mit einer Endlosschleife, in der der µC hängt, macht er sich auch 
in einem gewissen Sinne bemerkbar. Aber der Informationsgehalt davon ist 
dann schon recht dürftig.

: Bearbeitet durch User
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.