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
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?
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.
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.
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.
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
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
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.
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:
(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:
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:
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.
>> 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.
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.