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?
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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
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.
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:
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.
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.
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
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.