Forum: Mikrocontroller und Digitale Elektronik asserts() in C++


von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Hallo,
mir ist bei der Nutzung von assert() in C++ mit einem arm-none-eabi-gcc 
aufgefallen, dass _PRETTY_FUNCTION_ mit jedem assert verwendet wird 
und das zu absurd großem binaries führt 
(http://robitzki.de/blog/Assert_Hash).

Von den C++ Nutzern unter euch, die C++ auch für embedded Projekte 
einsetzen: benutzt ihr asserts?

Nutzt jemand von euch asserts im production release? Wenn ja, wie?

Ich denke ja, dass es eigentlich ausreichen müsste, z.B. einfach für ein 
hard fault zu sorgen, wenn ein assert fehlschlägt. Über program counter 
und debug Informationen, müsste man dann ja wieder zu der Zeile Code 
kommen, in der das fehlschlagende assert steht. Im Fall von templates, 
müsste an der Stelle doch sogar bekannt sein, welche template parameter 
angewendet wurden.

Hat damit schon mal jemand praktische Erfahrungen gesammelt?

mfg Torsten

von Carl D. (jcw2)


Lesenswert?

PRETTY_FUNCTION ist doch ein build in Makro, dessen Wert von der 
aktuellen Position in der Source abhängt. Im Falle eines benutzten 
assert() ergibt sich daraus eine Stringkonstante im zig-Byte Bereich. 
Für die Ver-10-fachung der Binary-Größe würde das Programm aus wenig 
mehr als assert()'s bestehen.

Ich würde mir mal ein disassemble-tes Binary anschauen, um 
rauszubekommen, wo der Faktor 10 herkommt.

PS: du hast nicht zufällig die Debuginfo im elf mitgezählt?

: Bearbeitet durch User
von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Carl D. schrieb:

> Ich würde mir mal ein disassemble-tes Binary anschauen, um
> rauszubekommen, wo der Faktor 10 herkommt.

Woher der Faktor 10 kommt, ist doch klar: Im Binary sind Tonnen von 
Funktionsnamen, damit im Fall, dass zu Laufzeit ein assert failed, der 
Funktionsname bekannt ist. Wenn Du `strings` auf das binary machst, 
sieht das dann in etwa so aus: 
http://robitzki.de/images/pretty_function.png

Das ist natürlich bei template meta C++ extremer. Aber ich denke auch 
bei einer "normalen" Desktop Anwendung wird _PRETTY_FUNCTION_ das 
binary aufblähen, ohne dass es viel Mehrwert bringt.

von Walter T. (nicolas)


Lesenswert?

Torsten R. schrieb:
> Nutzt jemand von euch asserts im production release? Wenn ja, wie?

Klassisch bleiben die asserts im Release drin, werden aber durch ein 
wirkungsloses Makro ersetzt. So hat man im Debug-Code das gewünschte 
fail early. Fehlermeldungen, die drin bleiben sollen, werden 
folgerichtig nicht durch ein assert(), sondern durch error() 
implementiert.

von Carl D. (jcw2)


Lesenswert?

Torsten R. schrieb:
> Carl D. schrieb:
>
>> Ich würde mir mal ein disassemble-tes Binary anschauen, um
>> rauszubekommen, wo der Faktor 10 herkommt.
>
> Woher der Faktor 10 kommt, ist doch klar: Im Binary sind Tonnen von
> Funktionsnamen, damit im Fall, dass zu Laufzeit ein assert failed, der
> Funktionsname bekannt ist. Wenn Du `strings` auf das binary machst,
> sieht das dann in etwa so aus:
> http://robitzki.de/images/pretty_function.png
>
> Das ist natürlich bei template meta C++ extremer. Aber ich denke auch
> bei einer "normalen" Desktop Anwendung wird _PRETTY_FUNCTION_ das
> binary aufblähen, ohne dass es viel Mehrwert bringt.

259kb Funktionsnamen?
Nochmal: reden wir vom .elf mit Debuginfo drin, oder vom .bin, das in 
den Flash soll?

Ich vermute ersteres und hoffe inständig niemals wirklich von der 
Überwachung deutscher Schienen oder autopilotierten Seeschiffen abhängig 
zu sein.

: Bearbeitet durch User
von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Carl D. schrieb:
> 259kb Funktionsnamen?

Jep. Hast Du schon mal was mit C++ und im speziellen mit templates 
gemacht? Der Code zu dem Beispiel von mir ist open source, Du kannst es 
gerne ausprobieren.

> Nochmal: reden wir vom .elf mit Debuginfo drin, oder vom .bin, das in
> den Flash soll?

.bin

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Walter T. schrieb:
> Torsten R. schrieb:
>> Nutzt jemand von euch asserts im production release? Wenn ja, wie?
>
> Klassisch bleiben die asserts im Release drin, werden aber durch ein
> wirkungsloses Makro ersetzt.

Naja, das jetzt keiner durch den Code geht und die löscht war mir schon 
klar. :-)

Ich versuche es mal konkreter: Den Code mit ohne -DNDEBUG übersetzen, 
meine ich mit "nutzen", "drinnen lassen", "aktiviert haben". Mit 
-DNDEBUG übersetzen würde ich mit "raus nehmen", "abschalten" oder 
"durch ein wirkungsloses Makro ersetzten" bezeichnen.

Als bei euch werden die asserts im Release heraus genommen. Was meinst 
Du mit error()? Hat das die gleiche Semantik wie ein assert(), aus 
Kostengründen aber mit deutlich weniger Verwendung?

mfg Torsten

von Vincent H. (vinci)


Lesenswert?

Wie wärs mit der Implementierung von newlib? Damit dürfts wesentlich 
kleiner werden...
1
// This file is part of the µOS++ III distribution.
2
// Copyright (c) 2014 Liviu Ionescu.
3
4
void
5
__attribute__((noreturn))
6
__assert_func (const char *file, int line, const char *func,
7
               const char *failedexpr)
8
{
9
  trace_printf ("assertion \"%s\" failed: file \"%s\", line %d%s%s\n",
10
                failedexpr, file, line, func ? ", function: " : "",
11
                func ? func : "");
12
  abort ();
13
  /* NOTREACHED */
14
}

Den wahnsinnigen Vorteil eines PRETTY_FUNCTION seh ich in Bezug auf 
asserts sowieso nicht wirklich.

von Carl D. (jcw2)


Lesenswert?

Torsten R. schrieb:
> Carl D. schrieb:
>> 259kb Funktionsnamen?
>
> Jep. Hast Du schon mal was mit C++ und im speziellen mit templates
> gemacht? Der Code zu dem Beispiel von mir ist open source, Du kannst es
> gerne ausprobieren.
>
>> Nochmal: reden wir vom .elf mit Debuginfo drin, oder vom .bin, das in
>> den Flash soll?
>
> .bin

Templates habe ich 1997 das erste mal benutzt und das letzte mal 
vorgestern. Dank C++11..17 habe ich auch schon lange Funktionsnamen 
gesehen. Nur wüsste ich nicht, was ich mit bis zu 1500 Zeichen langen 
Namen auf einem μC (oder überhaupt) anfangen sollte. -> 250k / (167+2) 
asserts
Vielleicht ist ein assert() nicht das richtige Debug-Mittel.

Und ein 32k-Flash Controller hat vielleicht nur 20k RAM, da sollte man 
nicht allzu komplexe Speicherverwaltungsverfahren anwenden. Sorry wenn 
ich etwas direkt bin, aber ich glaube deine typische Zielplattform hat 
einige 10-Potenzen mehr RAM verfügbar.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Vincent H. schrieb:
> Wie wärs mit der Implementierung von newlib? Damit dürfts wesentlich
> kleiner werden...

Die Implementierung von newlib sieht in etwa so aus:
1
# define assert(__e) ((__e) ? (void)0 : __assert_func (__FILE__, __LINE__, \
2
                   __ASSERT_FUNC, #__e))
3
...
4
# ifndef __ASSERT_FUNC
5
  /* Use g++'s demangled names in C++.  */
6
#  if defined __cplusplus && defined __GNUC__
7
#   define __ASSERT_FUNC __PRETTY_FUNCTION__
(Quelle: 
https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob_plain;f=newlib/libc/include/assert.h;hb=HEAD)

Das binary wird durch den Aufruf von __assert_func() so groß. Mit -D 
__ASSERT_FUNC=0 könnte man den Quatsch also offensichtlich abschalten.

> Den wahnsinnigen Vorteil eines PRETTY_FUNCTION seh ich in Bezug auf
> asserts sowieso nicht wirklich.

Im embedded Bereich, finde ich _FILE__ und __LINE_ auch schon 
überflüssig, wenn man keine Möglichkeit hat, Log-Files (oder ähnliches) 
zu schreiben.

Ich habe eine embedded Anwendung, die eine Verbindung zu einem PC hat. 
Ich hatte die Idee, aus _FILE__ und __LINE_ zur Compile-Zeit einen 
Hash zu machen und den im RAM abzulegen und wenn wieder eine Verbindung 
zum PC besteht, dieses Hash zum PC zu übertragen. Dazu braucht man dann 
aber ein Tool, dass die Sourcen nach "assert" durchpflügt und auch 
diesen Hash berechnet.

Aber eigentlich müsste ja der PC an der Stelle, an der das assert steht 
reichen. Also in etwa so:
1
# define assert(__e) ((__e) ? (void)0 : asm("invalid op")

Also irgend etwas, dass z.B. den hard fault handler auslöst. Dann könnte 
man hard faults und asserts auch identisch handhaben.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Carl D. schrieb:
> Nur wüsste ich nicht, was ich mit bis zu 1500 Zeichen langen
> Namen auf einem μC (oder überhaupt) anfangen sollte. -> 250k / (167+2)
> asserts

Ja, ich auch nicht. Du bekommst sie aber, wenn Du die asserts in Deinem 
Code einschaltest.

> Vielleicht ist ein assert() nicht das richtige Debug-Mittel.

Doch, assert() ist prima. Ich finde die so toll, dass ich sie sogar im 
release Code lassen möchte. Bei meinem Beispiel unterscheidet sich die 
Codegröße zwischen assert eingeschaltet und ausgeschaltet ja nur noch um 
5%, wenn ich assert so definiere:
1
# define assert(__e) ((__e) ? (void)0 : __assert_func (0,0,0,0))

Das wäre es mir auf jeden Fall wert.

> Und ein 32k-Flash Controller hat vielleicht nur 20k RAM, da sollte man
> nicht allzu komplexe Speicherverwaltungsverfahren anwenden.

Das stimmt sicherlich, aber worauf möchtest Du hinaus?

> Sorry wenn
> ich etwas direkt bin, aber ich glaube deine typische Zielplattform hat
> einige 10-Potenzen mehr RAM verfügbar.

Oh, meine "typische" Zielplattform gibt es so garnicht. Beim jetzigen 
Projekt hat die Zielplattform (nrf52) 512kByte flash und 64k RAM. 
Allerdings geht auch schon einiges durch den Bootloader drauf und der 
Bluetooth Stack des Chip-Herstellers verschlingt auch einen sehr großen 
Teil des Speichers.

von Carl D. (jcw2)


Lesenswert?

Torsten R. schrieb:
>
> Aber eigentlich müsste ja der PC an der Stelle, an der das assert steht
> reichen. Also in etwa so:
>
>
1
> # define assert(__e) ((__e) ? (void)0 : asm("invalid op")
2
>
>
> Also irgend etwas, dass z.B. den hard fault handler auslöst. Dann könnte
> man hard faults und asserts auch identisch handhaben.

Gibt es einen Grund das nicht einfach auszuprobieren?

>> nicht allzu komplexe Speicherverwaltungsverfahren anwenden.
> Das stimmt sicherlich, aber worauf möchtest Du hinaus?

Na ich kurz in deinen Code geschaut und fand daß da für kleines RAM zu 
viel Dynamik in der Speicherverwaltung ist. Auf kleinen Kisten kann man 
mit heutigem C++ sehr vieles zur Compiletime machen und dabei mit 
static_assert vielfältige Fehlermöglichkeiten prüfen. Diese Fehler 
fallen dann zur Laufzeit weg und die Benachrichtungung ihres Auftretens 
gleich mit.

: Bearbeitet durch User
von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Carl D. schrieb:
> Torsten R. schrieb:
>> Also irgend etwas, dass z.B. den hard fault handler auslöst. Dann könnte
>> man hard faults und asserts auch identisch handhaben.
>
> Gibt es einen Grund das nicht einfach auszuprobieren?

Zeit. Wenn ich hier von jemanden den Erfahrungsbericht bekommen kann, 
der mir sagt, das das super funktioniert und sich in der Praxis bewehrt 
hat, dann werde ich das umsetzen.

Was ist so verkehrt daran, nach Erfahrungen zu fragen? Ich muss doch 
nicht alle Fehler selbst machen. Was stört Dich daran?

> Na ich kurz in deinen Code geschaut und fand daß da für kleines RAM zu
> viel Dynamik in der Speicherverwaltung ist.

Nun, wenn Du das Beispiel aus dem Artikel meinst: das ist ein 
Bootloader, der ohne heap auskommt und Puffer fester Größe verwendet. 
Ein Bootloader hat in der Regel den Luxus, mit RAM nicht sparsam sein zu 
müssen. Wenn Du mal ins Linkerscript guckst, dann siehst Du, dass der 
mit 16k RAM auskommt (inkl. Bluetooth Stack).

Aber darum geht es doch auch überhaupt nicht.

> Auf kleinen Kisten kann man
> mit heutigem C++ sehr vieles zur Compiletime machen und dabei mit
> static_assert vielfältige Fehlermöglichkeiten prüfen. Diese Fehler
> fallen dann zur Laufzeit weg und die Benachrichtungung ihres Auftretens
> gleich mit.

Ja, kann man machen. Aber man kommt halt nicht drum herum, auch zur 
Laufzeit Zustände zu haben.

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.