Forum: Compiler & IDEs Rückgabewerte von va_list auf einem Arduino Mega2560


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Reinhard (reinhard55)


Lesenswert?

Hallo Community,

ich arbeite an einem C-Projekt, bei dem ich die variable Argumentliste 
(va_list) in einem Arduino Mega2560 verwenden muss. In meinem Code 
verwende ich die folgende Konstruktion:
1
#include <stdarg.h>
2
3
#define jprint(...) jprint_impl(__VA_ARGS__, NULL)
4
void jprint_impl(const char* first_arg, ...) {
5
    va_list args;
6
    va_list num_args;
7
    va_start(args, first_arg);
8
9
    const char* arg=first_arg; 
10
    // Weitere Verarbeitung der Argumentenliste
11
    do
12
      {
13
14
      // >>>>>>>>> was steht in arg drin, das ist die Frage
15
16
      // Nächstes Argument aus der Argumentenliste holen
17
      arg = va_arg(args, const char*);
18
      }
19
    while (arg != NULL);   
20
  
21
    va_end(args); // Argumentenliste aufräumen
22
    }

Ich würde gerne wissen, welche Werte die va_list-Variablen args und 
num_args auf diesem spezifischen System zurückgeben.

Ich verwende das so:
1
  void Check_MOD (uint16_t ZeilenNr, uint8_t MOD_ix) 
2
    {
3
    jprint("MOD RangeErr in ",ZeilenNr,": ",MOD_ix," ",4711);
4
    }

und jprint fasst alle Argumente zusammen.

Kann mir jemand helfen, diese Rückgabewerte richtig zu verstehen für 
Argumente vom Typ char*, uint16_t und uint8_t und zu erklären, wie sie 
auf einem Arduino Mega2560 interpretiert werden können? Ich möchte dem 
Argument keine Info mitgeben, welchen Typ er hat.

Im Kern geht es um die Frage: Was steht in arg?

Vielen Dank für Eure Hilfe!

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Reinhard schrieb:
>       arg = va_arg(args, const char*);

Falsch!

> Ich möchte dem Argument keine Info mitgeben, welchen Typ er hat.

Das funktioniert nicht. va_arg() funktioniert nur richtig, wenn Du den 
Typ des Arguments korrekt mitteilst. printf() und Konsorten machen das 
auch, nämlich durch den Format-String.

> Im Kern geht es um die Frage: Was steht in arg?

So, wie Du das aufrufst: Müll. Spätestens bei der Verarbeitung des 
Arguments 4711, was kein C-String ist.

Gib mal bei Google "man va_arg" ein und lies erstmal das Manual.

: Bearbeitet durch Moderator
von Bauform B. (bauformb)


Lesenswert?

Reinhard schrieb:
> Was steht in arg?

ein paar Bits. Ob das als uint16_t oder Pointer übergeben wurde, kann 
man innerhalb der Funktion nicht mehr feststellen. Insbesondere kann man 
NULL (das Ende der Liste) nicht von MOD_ix=0 unterscheiden. Eine 
Notlösung wäre, immer abwechselnd char * und int zu übergeben. Dann wäre 
der Typ aller Parameter fest und NULL am Ende wieder eindeutig. Trotzdem 
könnte die Liste beliebig lang sein.

Nachdem die Ähnlichkeit mit printf() nicht zu übersehen ist: was ist der 
Vorteil von dieser Konstruktion?

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Reinhard schrieb:
> ich arbeite an einem C-Projekt, bei dem ich die variable Argumentliste
> (va_list) in einem Arduino Mega2560 verwenden muss.

Arduino-Code ist C++-Code, nicht C.

> jprint("MOD RangeErr in ",ZeilenNr,": ",MOD_ix," ",4711);

Warum schreibst Du nicht einfach C++:
1
  String str = (String) "MOD RangeErr in " + ZeilenNr + ": " + MOD_ix + " " + 4711;
2
  Serial.write(str); // oder irgendeine andere Ausgabefunktion

Du kannst auch auf die Variable str verzichten und das Ganze rechts vom 
Gleichheitszeichen direkt Deiner Ausgabefunktion übergeben - 
vorausgesetzt, sie kann mit einem C++-String als Argument umgehen. 
Notfalls, wenn Du wirklich noch mit "char *" arbeitest, musst Du den 
C++-String in einen C-String zurückwandeln.

: Bearbeitet durch Moderator
von Reinhard (reinhard55)


Lesenswert?

Frank M. schrieb:

> Arduino-Code ist C++-Code, nicht C.

Wenn du die Arduino-IDE benutzt, magst du recht haben. Ich verwende 
Atmel Studio und programmiere in C.

von Reinhard (reinhard55)


Lesenswert?

Bauform B. schrieb:
>
> Eine
> Notlösung wäre, immer abwechselnd char * und int zu übergeben. Dann wäre
> der Typ aller Parameter fest und NULL am Ende wieder eindeutig. Trotzdem
> könnte die Liste beliebig lang sein.

Das ist vielleicht eine Idee.

> Nachdem die Ähnlichkeit mit printf() nicht zu übersehen ist: was ist der
> Vorteil von dieser Konstruktion?

printf() hat einen ziemlich großen Overhead, und das ist auf einem 
Arduino nicht so toll. Ich programmiere auch auf dem Uno und Nano.
Kann ich printf() auch benutzen, um nur den Ausgabestring zu erzeugen, 
ohne ihn auszugeben? Also in ein char output[100] ablegen?

von Rolf M. (rmagnus)


Lesenswert?

Frank M. schrieb:
> Reinhard schrieb:
>> ich arbeite an einem C-Projekt, bei dem ich die variable Argumentliste
>> (va_list) in einem Arduino Mega2560 verwenden muss.
>
> Arduino-Code ist C++-Code, nicht C

Ein Arduino Mega2560 ist erst mal nur ein Stück Hardware, auf dem man 
auch C-Programme laufen lassen kann.

Reinhard schrieb:
> Ich möchte dem Argument keine Info mitgeben, welchen Typ er hat.

Wie schon gesagt wurde, geht das mit varargs nicht. Du musst in deiner 
Funktion beim Aufruf von va_arg() angeben, welchen Typ das Argument hat. 
Es liegt also an dir, den Typ zu kennen und korrekt anzugeben. Wie du an 
den Typ kommst, ist dir überlassen.

In C++ könnte man sowas mit einem variadischen Template machen.

Reinhard schrieb:
> Kann ich printf() auch benutzen, um nur den Ausgabestring zu erzeugen,
> ohne ihn auszugeben? Also in ein char output[100] ablegen?

Dazu gibt es sprintf, bzw. besser snprintf.
https://en.cppreference.com/w/c/io/fprintf

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Reinhard schrieb:
> printf() hat einen ziemlich großen Overhead

Den hat es vor allem deshalb, weil es eben "allen Furz und Feuerstein" 
unterstützen muss und erst zur Laufzeit weiß, was davon wirklich 
gebraucht wird.

Daher gibt es in der avr-libc auch noch Varianten ohne 
Gleitkomma-Support sowie völlig minimalistische, bei denen noch einige 
andere Features weggelassen wurden.

Gerade auf dem ATmega2560 würde ich mich aber an deiner Stelle nicht 
verrenken und erst einmal so lange printf (oder sprintf) benutzen bis 
klar ist, dass wirklich die Säge klemmt und auch diese zu viel sind. 
Davor lohnt sich der Bau eigener Ersatzfunktionen eigentlich eher nicht, 
zumal du ja immer das Risiko eingehst, statt relativ gut debuggter 
Software eigene neue Fehler einzubauen.

von Bauform B. (bauformb)


Lesenswert?

Rolf M. schrieb:
> Reinhard schrieb:
>> Kann ich printf() auch benutzen, um nur den Ausgabestring zu erzeugen,
>> ohne ihn auszugeben? Also in ein char output[100] ablegen?
>
> Dazu gibt es sprintf, bzw. besser snprintf.
> https://en.cppreference.com/w/c/io/fprintf

Für das Beispiel aus dem ersten Beitrag würde auch etwas wie itoa() 
ausreichen. Das war doch beim AVR irgendwo dabei?

von Rolf M. (rmagnus)


Lesenswert?

Bauform B. schrieb:
> Rolf M. schrieb:
>> Reinhard schrieb:
>>> Kann ich printf() auch benutzen, um nur den Ausgabestring zu erzeugen,
>>> ohne ihn auszugeben? Also in ein char output[100] ablegen?
>>
>> Dazu gibt es sprintf, bzw. besser snprintf.
>> https://en.cppreference.com/w/c/io/fprintf
>
> Für das Beispiel aus dem ersten Beitrag würde auch etwas wie itoa()
> ausreichen.

Würde es? Soweit ich verstehe, zeigt es eine Ausgabefunktion im Stile 
von printf (nur ohne Formatstring), die entsprechend auch mit mehreren 
Argumenten von unterschiedlichem Typ zurecht kommen und das dann zu 
einem Gesamtstring zusammenbauen soll.

von Reinhard (reinhard55)


Lesenswert?

Hallo zusammen,

vielen Dank für die vielen Hinweise.
Ich entnehme dem, dass es sinnvoll ist, wenn ich auf snprintf() 
umsteige.

von Niklas G. (erlkoenig) Benutzerseite


Angehängte Dateien:

Lesenswert?

In C++ wäre es ziemlich einfach mit variadischen templates. Hier gezeigt 
mit Unterstützung für signed Integers, unsigned Integers, char, char*, 
String Literale. Man kann beliebig eigene "convert" Funktionen 
hinzufügen für weitere Typen (z.B. float oder auch Klassen). Es wird 
automatisch auf Buffer Overflows geprüft. Die Ausgabe erfolgt testweise 
auf stdout, kann natürlich beliebig geändert werden. Es ist potentiell 
auch effizienter als sprintf, weil kein Parsen eines Format-Strings und 
Laufzeitunterscheidung nötig ist sowie keine komplizierte 
Stack-Akrobatik (va_arg).

Das Atmel Studio kann auch C++, du musst vermutlich nur die Datei mit 
der Endung .cpp speichern.

: Bearbeitet durch User
von Bauform B. (bauformb)


Lesenswert?

Rolf M. schrieb:
> Bauform B. schrieb:
>> Rolf M. schrieb:
>>> Reinhard schrieb:
>>>> Kann ich printf() auch benutzen, um nur den Ausgabestring zu erzeugen,
>>>> ohne ihn auszugeben? Also in ein char output[100] ablegen?
>>>
>>> Dazu gibt es sprintf, bzw. besser snprintf.
>>> https://en.cppreference.com/w/c/io/fprintf
>>
>> Für das Beispiel aus dem ersten Beitrag würde auch etwas wie itoa()
>> ausreichen.
>
> Würde es? Soweit ich verstehe, zeigt es eine Ausgabefunktion im Stile
> von printf (nur ohne Formatstring), die entsprechend auch mit mehreren
> Argumenten von unterschiedlichem Typ zurecht kommen und das dann zu
> einem Gesamtstring zusammenbauen soll.

Naja, wenn va_list für die ursprüngliche Idee funktionieren würde, 
brauchte man doch kaum mehr als itoa() und strncat(). Aber snprintf() 
ist wirklich vernünftiger, besonders, wenn es abgespeckte Varianten 
gibt.

Wenn die immer noch zu groß sind, würde ich eher einen eigenen 
snprintf()-Ersatz bauen, kleiner, aber voll kompatibel. Schon alleine, 
weil der gcc dann nette Warnungen ausgeben kann
1
jprint(char *fmt, ...) __attribute__((format (printf, 1, 2)));

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Niklas G. schrieb:
> In C++ wäre es ziemlich einfach mit variadischen templates.

Aber ob das Ergebnis dann wirklich ressourcenschonender wird als 
snprintf()?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Jörg W. schrieb:
> Aber ob das Ergebnis dann wirklich ressourcenschonender wird als
> snprintf()?

Schon möglich. Hängt davon ab wie gut der Compiler es optimiert bekommt 
(inlining & Co). Eventuell wird bei zahlreichen Aufrufen der Code 
länger, aber die Ausführungszeit dürfte kürzer sein. Man könnte 
alternativ auch den Format-String per templates automatisch generieren 
und dann snprintf aufrufen ;-)

An den Integer-Convert-Funktionen kann man bestimmt auch noch was 
optimieren, hab da einfach die erstbeste Implementierung hin 
geschrieben. Aber das ist ein gelöstes Problem

: Bearbeitet durch User
von Reinhard (reinhard55)


Lesenswert?

Jetzt gibt es Zahlen über die Speicherbelegung.

  > ohne snprintf: Program Memory Usage   :  33284 bytes
                    Data Memory Usage     :   6690 bytes
  > mit snprintf:  Program Memory Usage   :  34796 bytes
                    Data Memory Usage     :   6714 bytes

Ich habe das Makro P() dazugenommen. P() ist ein Makro, das den String 
im PROGMEM platziert und einen const char* zurückgibt. Ich verwende es 
so:
1
  snprintf(jou,sizeof(jou),P("MOD RangeErr in %d: %d"),ZeilenNr,MOD_ix);
Dadurch reduziert sich der Verbrauch des RAM, und das ist ja der 
Knackpunkt beim Arduino.

  > mit P():       Program Memory Usage   :  34810 bytes
                    Data Memory Usage     :   6690 bytes

Der String in jou ist "MOD RangeErr in 1945: 1", so wie ich es möchte.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Dann musst du aber auch snprintf_P() benutzen. Das ist die Variante der 
Funktion, die den Format-String im Progmem erwartet.

von Ein T. (ein_typ)


Lesenswert?

Niklas G. schrieb:
> In C++ wäre es ziemlich einfach mit variadischen templates.

Najaaa, wenn ich mich recht entsinne, erzeugt der Compiler dann Code für 
jede der im Quellcode vorkommenden Parametrierungen, oder?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Ein T. schrieb:
> Najaaa, wenn ich mich recht entsinne, erzeugt der Compiler dann Code für
> jede der im Quellcode vorkommenden Parametrierungen, oder?

Ja. Aber das sind nur ein paar Funktionsaufrufe. Die eigentliche Arbeit 
passiert in den convert-Funktionen, welche nur 1x pro Typ erzeugt 
werden.

von Peter D. (peda)


Lesenswert?

Jörg W. schrieb:
> Davor lohnt sich der Bau eigener Ersatzfunktionen eigentlich eher nicht,
> zumal du ja immer das Risiko eingehst, statt relativ gut debuggter
> Software eigene neue Fehler einzubauen.

Ja, das habe ich auch nur früher gemacht, wo Atmel noch recht geizig mit 
Flash und RAM war (AT90S2313, AT90S4433, ATtiny15, ATtiny26).

von Reinhard (reinhard55)


Lesenswert?

Jörg W. schrieb:
> Dann musst du aber auch snprintf_P() benutzen. Das ist die Variante der
> Funktion, die den Format-String im Progmem erwartet.

Habe ich getestet: verbraucht mehr RAM als meine Version.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Reinhard schrieb:
> Jörg W. schrieb:
>> Dann musst du aber auch snprintf_P() benutzen. Das ist die Variante der
>> Funktion, die den Format-String im Progmem erwartet.
>
> Habe ich getestet: verbraucht mehr RAM als meine Version.

Dann solltest du, statt hier irgendwie herum zu stochern, das genau 
analysieren.

Format-String im Flash braucht die _P-Funktionen, sonst kommt Käse raus. 
Die normale s[n]printf-Funktion hat schlicht keine Idee, dass der ihr 
übergebene Zeiger in den Flash zeigen soll, sodass sie an der gleichen 
Adresse im RAM stattdessen liest.

Intern geht das eh alles auf das gleiche Backend (vfprintf), das wird 
nur mit unterschiedlichen Flags aufgerufen je nach dem benutzten 
Frontend. Kannst du gut erkennen, wenn du dir den Sourcecode ansiehst:
1
% ls -sk
2
total 367
3
 5 Files.am        5 fscanf.c       5 sprintf_p.c
4
45 Makefile        5 fscanf_p.c     5 sscanf.c
5
 5 Makefile.am     5 fwrite.c       5 sscanf_p.c
6
49 Makefile.in     5 getc.S         5 stdio_private.h
7
 5 Rules.am        5 getchar.c      9 ultoa_invert.S
8
 5 clearerr.c      5 gets.c         5 ungetc.c
9
 5 fclose.c        5 iob.c         21 vfprintf.c
10
 9 fdevopen.c      5 printf.c       5 vfprintf_p.c
11
 5 feof.c          5 printf_p.c    25 vfscanf.c
12
 5 ferror.c        5 putc.S         5 vfscanf_p.c
13
 5 fgetc.c         5 putchar.c      5 vprintf.c
14
 5 fgets.c         5 puts.c         5 vscanf.c
15
 5 fprintf.c       5 puts_p.c       5 vsnprintf.c
16
 5 fprintf_p.c     5 scanf.c        5 vsnprintf_p.c
17
 5 fputc.c         5 scanf_p.c      5 vsprintf.c
18
 5 fputs.c         5 snprintf.c     5 vsprintf_p.c
19
 5 fputs_p.c       5 snprintf_p.c   5 xtoa_fast.h
20
 5 fread.c         5 sprintf.c

Gut zu erkennen, dass der komplette Code für stdio in den beiden 
Implementierungen für vpfprintf.c und vfscanf.c steckt. Alles andere 
sind Wrapper darum.

: Bearbeitet durch Moderator
von Reinhard (reinhard55)


Lesenswert?

Jörg W. schrieb:
> Reinhard schrieb:
>> Jörg W. schrieb:
>>> Dann musst du aber auch snprintf_P() benutzen. Das ist die Variante der
>>> Funktion, die den Format-String im Progmem erwartet.
>>
>> Habe ich getestet: verbraucht mehr RAM als meine Version.
>
> Dann solltest du, statt hier irgendwie herum zu stochern, das genau
> analysieren.
>
Meine Güte, warum gleich so aggressiv? Arbeite mal dran.

Ich habe für mich die Lösung, die am wenigsten den RAM belastet. Warum 
soll ich da weitere Zeit reinstecken?

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Reinhard schrieb:

>> Dann solltest du, statt hier irgendwie herum zu stochern, das genau
>> analysieren.
>>
> Meine Güte, warum gleich so aggressiv? Arbeite mal dran.

Das war nicht aggressiv gemeint. Kommunikation über Computer ist halt 
zuweilen missverständlich.

> Ich habe für mich die Lösung, die am wenigsten den RAM belastet. Warum
> soll ich da weitere Zeit reinstecken?

Weil sie sehr wahrscheinlich Amok laufen wird (und damit keine "Lösung" 
ist). Den Grund habe ich dir versucht zu erklären.

von Reinhard (reinhard55)


Lesenswert?

Jörg W. schrieb:
> Weil sie sehr wahrscheinlich Amok laufen wird (und damit keine "Lösung"
> ist).

Spekulation, oder hast du es selbst getestet? Ich stelle meinen Code 
gerne zur Verfügung.
Bei mir funzt es bestens :-)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Reinhard schrieb:
> Spekulation, oder hast du es selbst getestet?

Ich kann nichts testen, was ich nicht habe.

Ich habe nur versucht nachzuvollziehen, was du hier erzählt hast, und 
ich weiß, wie der stdio-Code gestrickt ist, da ich insbesondere diesen 
Teil mit den vfprintf/vfscanf-Backends und den diversen Wrappern selbst 
entworfen habe (auch wenn Dmitry Xmelkov dann in den Backends vieles 
optimiert hat).

> Bei mir funzt es bestens

Dann machst du was anderes als was ich deinen Ausführungen entnommen 
habe.

von Reinhard (reinhard55)


Lesenswert?

Ich mache Folgendes:
1
// aus einem String im PROGMEM einen char* machen
2
#define P(str) ({ \
3
  char buffer[strlen_P(str)]; \
4
  strcpy_P(buffer, PSTR(str)); \
5
  buffer; \
6
})
7
8
void RangeCheck_MOD (uint16_t ZeilenNr, uint8_t MOD_ix) {
9
  char jou[30];
10
  snprintf(jou,sizeof(jou),P("MOD RangeErr in %d: %d"),ZeilenNr,MOD_ix);
11
  println(jou); 
12
  }
Verwende ich stattdessen
1
  snprintf_P(jou,sizeof(jou),"MOD RangeErr in %d: %d",ZeilenNr,MOD_ix);
wird mehr RAM verbraucht. Logisch.

Mache ich
1
  snprintf_P(jou,sizeof(jou),PSTR("MOD RangeErr in %d: %d"),ZeilenNr,MOD_ix);
dann ist die RAM-Nutzung auch wie bei meiner Lösung. Dann bleibt es 
Geschmacksache oder eine Stilfrage.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Reinhard schrieb:
> #define P(str) ({ \
>
>   char buffer[strlen_P(str)]; \
>
>   strcpy_P(buffer, PSTR(str)); \

OK, das erklärt es.

Das braucht aber trotzdem mehr RAM als die Lösung mit sprintf_P() – nur 
auf dem Stack, dadurch fällt dir das nicht direkt auf. Es gibt aber 
eigentlich keinen Grund, das erst in den RAM umzukopieren, da eben 
vfprintf() in der Lage ist, den Format-String auch gleich direkt aus dem 
Flash zu lesen.

von Foobar (asdfasd)


Lesenswert?

Diese Gehampel sehe ich sehr häufig:
1
    char jou[30];
2
    snprintf(jou,sizeof(jou), ...);
3
    println(jou);

Warum???  Es ist umständlich, gefährlich, langsam, braucht zusätzlichen 
(Stack-/)Speicher.  Einen stdio-Stream zu erstellen ist so trivial 
(Beispiel-wrapper für uart_putc/getc):
1
static int put(char c, FILE *str)
2
{
3
    uart_putc(c);
4
    return 0;
5
}
6
static int get(FILE *str)
7
{
8
    return uart_getc();
9
}
10
FILE uart[1] = { FDEV_SETUP_STREAM(put, get, _FDEV_SETUP_RW) };
11
// Fertig.
12
13
// Usage:
14
   ...
15
   fprintf(uart, "%s\n", str);
16
   fputs("Hello World!\n", uart);
17
   c = fgetc(uart);
18
   ...
19
   // oder gleich stdin/out/err umleiten:
20
   stdin = stdout = stderr = uart;
21
   ...
22
   printf("%s\n", str);
23
   puts("Hello World!");
24
   c = getchar();

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Foobar schrieb:
> inen stdio-Stream zu erstellen ist so trivial

Das war die Intention seinerzeit. ;-)

von Foobar (asdfasd)


Lesenswert?

Btw, so wie ich das sehe, ist dieses Makro
1
#define P(str) ({ \
2
  char buffer[strlen_P(str)]; \
3
  strcpy_P(buffer, PSTR(str)); \
4
  buffer; \
5
})
fehlerhaft - es liefert eine Adresse, die beim Erreichen der "}" 
ungültig wird.  Dazu kommt noch, dass str mehrmals ausgewertet wird. 
Und lokale var-arrays ohne Längenbeschränkung sind (insb auf Systemen 
ohne dynamischen Stack, wie Mikrocontrollern) eh Buggy.

PS: ne +1 fehlt auch noch.

PPS: Zu meinen, allein durch Benutzung von "n"-Funktionen (snprintf, 
strncpy, etc), "sicher" zu programmieren, ist ein Irrtum - man 
verschiebt nur den Angriffsvektor: von Buffer overflow nach String 
truncation.

: Bearbeitet durch User
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Foobar schrieb:
> es liefert eine Adresse, die beim Erreichen der "}" ungültig wird.

Das ist richtig und funktioniert deshalb nur zufällig. Lösung: das 
char-Array "buffer" static machen.

Und siehe da: Jetzt sieht man den (versteckten) zusätzlichen RAM-Bedarf, 
den Jörg prognostiziert hat, auch wieder :-)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Wie ich weiter oben schrieb: eigene Versuche produzieren meist eine 
höhere Bug-Wahrscheinlichkeit, als wenn man gleich die "gut abgehangene" 
fertige Implementierung benutzt. Allein der zusätzliche RAM-Bedarf für 
das Umkopieren des Strings frisst ja den durch den im Flash platzierten 
Format-String angestrebten Vorteil zur Hälfte schon wieder auf.

von Peter D. (peda)


Lesenswert?

Ich finde ja das Konzept der generic Pointer beim Keil C51 genial.
Der Programmierer muß keinerlei Rücksicht darauf nehmen, in welchem 
Segment eine Variable angelegt wurde. Ein zusätzliches Byte des Pointers 
merkt sich den Segmenttyp und es wird automatisch zu dem richtigen 
Befehl compiliert. Läßt sich das zur Compilezeit noch nicht auflösen, 
wird eben eine Unterfunktion aufgerufen, die dann das Typ-Byte prüft und 
entsprechend den Zugriff ausführt.
Ohne irgendwelche Sonderlocken schreiben zu müssen, wird der 
Formatstring des printf automatisch im Flash abgelegt.

Hier mal ein Beispiel:
1
320          static void RcmdError(char* err_str)
2
 321          {
3
 322   1        sprintf(reply_buf, "SY%s\r\n", err_str);
4
 323   1        RemotePutString(reply_buf);
5
 324   1      }
Listing:
1
             ; FUNCTION _RcmdError (BEGIN)
2
0000 8B00    R     MOV     err_str,R3
3
0002 8A00    R     MOV     err_str+01H,R2
4
0004 8900    R     MOV     err_str+02H,R1
5
                                           ; SOURCE LINE # 320
6
                                           ; SOURCE LINE # 321
7
                                           ; SOURCE LINE # 322
8
0006 7500FF  E     MOV     ?_sprintf?BYTE+03H,#0FFH
9
0009 750000  R     MOV     ?_sprintf?BYTE+04H,#HIGH ?SC_0
10
000C 750000  R     MOV     ?_sprintf?BYTE+05H,#LOW ?SC_0
11
000F 8B00    E     MOV     ?_sprintf?BYTE+06H,R3
12
0011 8A00    E     MOV     ?_sprintf?BYTE+07H,R2
13
0013 8900    E     MOV     ?_sprintf?BYTE+08H,R1
14
0015 7B01          MOV     R3,#01H
15
0017 7A00    R     MOV     R2,#HIGH reply_buf
16
0019 7900    R     MOV     R1,#LOW reply_buf
17
001B 120000  E     LCALL   _sprintf
0FFH dürfte der Typ Code (= Flash) bedeuten.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Peter D. schrieb:
> Ich finde ja das Konzept der generic Pointer beim Keil C51 genial.

Außer halt, dass es Overhead kostet, sowohl im Zeiger selbst als auch 
für die Auswertung zur Laufzeit. Ist aber wurscht: hat beim AVR keiner 
implementiert und wird auch keiner mehr implementieren.

von Peter D. (peda)


Lesenswert?

Jörg W. schrieb:
> Außer halt, dass es Overhead kostet, sowohl im Zeiger selbst als auch
> für die Auswertung zur Laufzeit.

Das hatte selbst bei 12MHz / 12 = 1MIPS nicht sonderlich gestört. Die 
Ableserate des Menschen ist oftmals der Flaschenhals.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Peter D. schrieb:
> Jörg W. schrieb:
>> Außer halt, dass es Overhead kostet, sowohl im Zeiger selbst als auch
>> für die Auswertung zur Laufzeit.
>
> Das hatte selbst bei 12MHz / 12 = 1MIPS nicht sonderlich gestört. Die
> Ableserate des Menschen ist oftmals der Flaschenhals.

In diesem Falle schon, aber generic pointer waren ja ein allgemeines 
Konzept, nicht nur hinsichtlich stdio, oder?

Aber wie schon geschrieben: wird bei AVR keiner mehr machen. Zu viel 
Sonderlocke, und die Domäne für größere Flashs geht eh klar in Richtung 
ARM oder RISC-V. Bei kleineren Flashs wiederum gibt es mittlerweile die 
Option, den Flash in den normalen Adressbereich einzublenden, da braucht 
man solche Verrenkungen dann auch nicht mehr.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Zumindest kennt der Compiler __memx, außer für reduced Tiny.

von Peter D. (peda)


Lesenswert?

Jörg W. schrieb:
> Ist aber wurscht: hat beim AVR keiner
> implementiert und wird auch keiner mehr implementieren.

Ja, für die ollen 8-Bitter macht keiner mehr was.
Blinklicht nicht unter 32Bit ist der neue Scheiß.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Zumindest kennt der Compiler __memx, außer für reduced Tiny.

Müsste sich aber trotzdem jemand hinsetzen, dafür die 
Bibliotheksfunktionen zu machen. Da fände ich 64-bit Gleitkomma in libm 
deutlich interessanter. ;)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Peter D. schrieb:
>> Ist aber wurscht: hat beim AVR keiner
>> implementiert und wird auch keiner mehr implementieren.
>
> Ja, für die ollen 8-Bitter macht keiner mehr was.

Du musst mir die Worte nicht im Mund rumdrehen. Microchip konstruiert 
nach wie vor neue AVRs am laufenden Band, aber sie müssen natürlich 
Dinge produzieren, die sich auch verkaufen lassen. Da haben sie wohl 
erkannt, dass es jenseits von 128 KiB Flash (die man noch ohne 
irgendwelche Verrenkungen adressieren kann) keinen Sinn hat, auf 
8-Bitter zu setzen.

von Peter D. (peda)


Lesenswert?

Jörg W. schrieb:
> Microchip konstruiert
> nach wie vor neue AVRs am laufenden Band

Ja, das habe ich auch nicht verstanden, die 0-, 1-, 2-Serie mit nur 
minimalen Unterschieden in schneller Folge rauszuhauen. Ich vermute 
darin öfteren Führungswechsel, wo jeder seine Duftmarke setzen will.

Ich werde schon schief angesehen, wenn ich noch was mit dem AVR machen 
will. Bisher eingesetzte Typen haben Bestandsschutz, weitere anzulegen 
ist verboten.
Für das aktuelle Projekt ist ein i.MX RT1176 vorgesehen.
https://www.embeddedartists.com/products/imx-rt1176-ucom/

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Peter D. schrieb:
> Ich vermute darin öfteren Führungswechsel, wo jeder seine Duftmarke
> setzen will.

Ich vermute, dass deine Vermutung reichlich daneben ist.

Die Entwicklung eines IC kostet Millionen – selbst, wenn man vielleicht 
auf eine Art „Baukastensystem“ für die IP-Blöcke zurück greifen kann. 
Diese Millionen muss der Chip dann auch erstmal einspielen, ansonsten 
wäre ziemlich schnell Schicht im Schacht.

Das Maxim-Prinzip (viele Ideen auf MPWs und dann erst gucken, was sich 
verkaufen lässt, den Rest wieder einstampfen) scheint bei Microchip nun 
eher nicht vorzuherrschen.

von Rolf M. (rmagnus)


Lesenswert?

Peter D. schrieb:
> Ja, für die ollen 8-Bitter macht keiner mehr was.
> Blinklicht nicht unter 32Bit ist der neue Scheiß.

Ob man generische Pointer jetzt für ein Blinklicht so dringend braucht, 
ist dann die andere Frage.

Peter D. schrieb:
> Für das aktuelle Projekt ist ein i.MX RT1176 vorgesehen.
> https://www.embeddedartists.com/products/imx-rt1176-ucom/

Das ist aber ein ganz anderes Kaliber als ein AVR. Ich hoffe doch, dass 
der nachher etwas mehr macht als ein Blinklicht 😉.

von Peter D. (peda)


Lesenswert?

Jörg W. schrieb:
> Die Entwicklung eines IC kostet Millionen

Da wundert es mich aber doch, warum sie in kurzer Folge die 8-Pinner 
ATtiny12, 15, 22, 13, 25-85 rausgebracht haben. Ich habe sie alle 
gekauft und mich geärgert, als die besseren verfügbar waren. Nun liegen 
sie rum, außer dem ATtiny25, der alle Vorgänger ersetzen kann.
Und mit den neuen Serien geht dieses Spielchen ja munter weiter.
Nur der ATmega328PB ist irgendwie ein Ausreißer, da ist recht viel 
hinzugekommen.

von Joachim B. (jar)


Lesenswert?

Peter D. schrieb:
> Da wundert es mich aber doch, warum sie in kurzer Folge die 8-Pinner
> ATtiny12, 15, 22, 13, 25-85 rausgebracht haben.

wundert mich auch also vermute ich

Jörg W. schrieb:
> Die Entwicklung eines IC kostet Millionen

stimmt nicht oder ist egal wenn ein Manager seine Duftmarke setzt!
Man kann nicht mal so dumm denken wie manche Führungsetage ist!

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Peter D. schrieb:
> Da wundert es mich aber doch, warum sie in kurzer Folge die 8-Pinner
> ATtiny12, 15, 22, 13, 25-85 rausgebracht haben.

Da schmeißt du aber einige Jahre, wenn nicht Jahrzehnte in einen Topf.

25 … 85 (die letztlich eine IC-Entwicklung erstmal sind) ist eine Art 
Nachfolger / Ablösung des 15 gewesen, mehr oder weniger 
aufwärtskomptabibel.

12 (und dessen Bruder 11) waren wohl die ersten Tinys überhaupt, mit dem 
13 hat man dann ein deutlich universelleres Teil nachgeliefert, das bis 
heute noch Bestand hat.

22 … keine Ahnung. Ich glaube, ich habe mal einen in den Fingern gehabt. 
Möglich, dass der eher ein Verlustgeschäft für Atmel war damals. Auch 
möglich, dass da ein einzelner großer Kunde als Auftraggeber dahinter 
stand, sowas ist nicht ungewöhnlich in der IC-Industrie.

von Joachim B. (jar)


Lesenswert?

Joachim B. schrieb:
> Man kann nicht mal so dumm denken wie manche Führungsetage ist!

Frage vom Abteilungsleiter:
"kennen sie sich aus mit Commodore mit kleiner Tastatur? (PET2001 mit 
Datasette)"

Anwort vom Bewerber "ja (und meinte den Commodore Taschenrechner)"
Vom 6502 und Innenleben hatte er NULL Ahnung, auch nicht das man beim 
Programmieren ENTER drückt und nicht mit den Cursortasten in die nächste 
Zeile geht.

Ein anderer Entscheider stolz wie Bolle, er hat seinen ersten Dr.Ing 
eingestellt, einer der Prüfautomaten und Programme baute die auch ohne 
angeschlossenen Prüfling gut testeten!

von Peter D. (peda)


Lesenswert?

Jörg W. schrieb:
> Da schmeißt du aber einige Jahre, wenn nicht Jahrzehnte in einen Topf.

ATtiny22: 04/1999
ATtiny12: 10/1999
ATtiny15: 12/2001
ATtiny13: 06/2003
ATtiny25..85: 02/2005

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Peter D. schrieb:
> Jörg W. schrieb:
>> Da schmeißt du aber einige Jahre, wenn nicht Jahrzehnte in einen Topf.
>
> ATtiny22: 04/1999
> ATtiny12: 10/1999
> ATtiny15: 12/2001
> ATtiny13: 06/2003
> ATtiny25..85: 02/2005

Keine Jahrzehnte, aber schon einige Jahre dazwischen. Bedenke auch, dass 
die die Anfangszeit der AVRs war. Der AT90S1200 hatte das Feld 
ausgelotet (kurz danach der ATmega103), ab da konnte man dann Feedback 
von potentiellen und realen Kunden sammeln.

Dass eine Entwicklung einer kompletten MCU-Serie "from scratch" zu 
Anfang defizitär ist, dürfte normal sein.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Reinhard schrieb:
> Ich würde gerne wissen, welche Werte die va_list-Variablen args und
> num_args auf diesem spezifischen System zurückgeben.

num_args kann so nicht verwendet werden, und eine Dokumentation / 
Beispiel, die das so verwendet, kann du getrost in die Tonne kloppen. 
Der Typ von args wird durch den 2. Parameter von va_arg festgelegt.

Am einfachsten verwendet man in solchen Fehlerausgaben die printf 
Funktionen die Libc:

*diagnostic.h*
1
#include <avr/pgmspace.h>
2
3
#define error(msg, ...) \
4
    error_loc_P (PSTR(__FILE__), __LINE__, PSTR(msg), __VA_ARGS__)
5
6
extern void error_loc_P (const char *fname, int line, const char *fmt, ...);

*diagnostic.c*
1
#include <stdarg.h>
2
#include <stdio.h>
3
4
#include "diagnostic.h"
5
6
void error_loc_P (const char *fname, int line, const char *fmt, ...)
7
{
8
    va_list args;
9
    va_start (args, fmt);
10
11
    fprintf_P (stderr, PSTR ("\n%S:%d: error: "), fname, line);
12
    vfprintf_P (stderr, fmt, args);
13
    fprintf_P (stderr, PSTR ("\n\n"));
14
15
    va_end (args);
16
}

*main.c*
1
#include "diagostic.h"
2
3
int main (void)
4
{
5
    error ("Fehler %d", 123);
6
}

Der Format-String von error() muss dabei ein gültiges Argument für 
PSTR() darstellen.  Oder man lässt das PSTR für den Format-String weg, 
aber dann belegt der String i.d.R. RAM.

[v]fprintf ist ein zielmlicher Brummer, und bei Platzproblemen -- die 
man auf einem ATmega2560 eher weniger hat -- implementiert man ienfach 
eine abgespeckte Version, die nur das nötogste mitbringt wie %s, %S, %d, 
%u und %x.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Johann L. schrieb:
> implementiert man ienfach eine abgespeckte Version, die nur das nötogste
> mitbringt wie %s, %S, %d, %u und %x.

Wobei es ja schon eine abgespeckte Variante gibt, die man erstmal 
stattdessen probieren kann. ;-)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Jörg W. schrieb:
> Johann L. schrieb:
>> implementiert man einfach eine abgespeckte Version, die nur das nötigste
>> mitbringt wie %s, %S, %d, %u und %x.
>
> Wobei es ja schon eine abgespeckte Variante gibt, die man erstmal
> stattdessen probieren kann. ;-)

Ja, aber das printf aus der Libc ist immer noch ziemlich fett.  Das 
liegt nicht zuletzt auch dran, dass viele Spezialitäten wie %*, %+, %-, 
%0 etc unterstützt werden, die man meist nicht braucht; schon gar nicht 
bei Fehlerausgabe.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

vfprintf_min lässt meiner Erinnerung nach genau diese Besonderheiten 
auch noch fallen. Spart aber nicht allzu viel.

von Bauform B. (bauformb)


Lesenswert?

Johann L. schrieb:
> eine abgespeckte Version, die nur das nötogste mitbringt wie %s, %S, %d,
> %u und %x.

Das nötigste, %S, echt jetzt? Benutzt hier tatsächlich jemand wchar_t?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Bauform B. schrieb:
> Das nötigste, %S, echt jetzt? Benutzt hier tatsächlich jemand wchar_t?

RTFDoc, bevor du herum unkst.

%S macht auf dem AVR was anderes …

von Klaus H. (klummel69)


Lesenswert?

Ich bin kein Freund von Logging per  printf/fprintf/snprintf…

Sie kosten einiges an Platz und die Formatierung zur Laufzeit braucht 
Zeit und ist fehleranfällig (wobei es ja Compiler gibt, die 
Forrmierstring Fehler erkennen.)

Wenn möglich würde ich (wie oben schon empfohlen) eher auf C++ 
variadische Template umsteigen. Das ist flexibler und 
ressourcenschonender.

Unter C11 kann man so etwas auch per Makros und _Generic() nachbauen, 
geht dann aber nur mit C-Compiler die C11 unterstützen und sieht 
irgendwie gemurkst aus….

Für die schnelle Ausgabe von Messages wäre TRICE 
(Beitrag "Open Source Projekt TRICE - ein Software-Tracer & Logger für alle µC: PIC, AVR, ARM, TI, Infinion,..") von Thomas Höhenleitner 
noch empfehlenswert.

: Bearbeitet durch User
von Reinhard (reinhard55)


Lesenswert?

Klaus H. schrieb:
>
> Unter C11 kann man so etwas auch per Makros und _Generic() nachbauen,
> geht dann aber nur mit C-Compiler die C11 unterstützen und sieht
> irgendwie gemurkst aus….
>

C11 kennt __flash nicht.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Reinhard schrieb:
> Klaus H. schrieb:
>>
>> Unter C11 kann man so etwas auch per Makros und _Generic() nachbauen,
>> geht dann aber nur mit C-Compiler die C11 unterstützen und sieht
>> irgendwie gemurkst aus….
>>
>
> C11 kennt __flash nicht.

Na dann eben GNU-C11

von Foobar (asdfasd)


Lesenswert?

Klaus schrieb:
> Ich bin kein Freund von Logging per  printf/fprintf/snprintf…
>
> Sie kosten einiges an Platz und die Formatierung zur Laufzeit braucht
> Zeit [...]

Mit dem Platz ist relativ: bei vielen libc-stdio-Implementation zieht 
die Benutzung von sprintf gleich das gesamte stdio-Subsystem mit rein - 
bei shared-libs kein Problem, bei statischen Builds wird's eins, ja. 
Der meiste Platz dürfte aber für die Float-to-String conversion drauf 
gehen (eine qualitativ hochwertig Implementation frißt mehrere dutzend 
kB, wenns schnell sein soll auch mal deutlich über 100kB).

Die avr-libc ist eine mMn sehr gute Implementation für µC - die 
Grundroutinen sind schlank, sie ziehen keinen Rattenschwanz an 
abhängigen Funktionen rein und die FP-version ist optional.

Und meist sieht es ja so aus: anfangs nur einen String ausgeben - wozu 
printf, einfach println o.ä.  Aber je mehr das Projekt wächst, umso mehr 
steigen die Anforderungen und man schreibt sich immer mehr Hilfsroutinen 
(für signed/unsigned dezimal-Zahlen, hex-Zahlen, fix-width, etc) und 
bevor man sich versieht, hat man sprintf neu erfunden.

Und was die Geschwindigkeit angeht: printf ist nicht langsam!  Das 
Bottleneck ist üblicherweise die anschließende Ausgabe, nicht die 
Formatierung (bei Ausgabe über UART sowieso).

> Wenn möglich würde ich (wie oben schon empfohlen) eher auf C++
> variadische Template umsteigen. Das ist flexibler und
> ressourcenschonender.

Flexibler mag sein, das mit dem ressourcenschonender möchte ich 
bezweifeln.  Statt in einer zentralen Formatierungsfunktion findet bei 
den Templates die Formatierung bei jedem einzelnen Aufrufer statt. 
Sieht kurz auf, es wird aber für jedes Argument extra Code generiert. 
Das läppert sich ...  Gewinnen kann man da nur, wenn die Argumente 
statisch sind, so dass der Compiler das wegoptimieren kann.

von Reinhard (reinhard55)


Lesenswert?

Foobar schrieb:
> Und meist sieht es ja so aus: anfangs nur einen String ausgeben - wozu
> printf, einfach println o.ä.  Aber je mehr das Projekt wächst, umso mehr
> steigen die Anforderungen und man schreibt sich immer mehr Hilfsroutinen
> (für signed/unsigned dezimal-Zahlen, hex-Zahlen, fix-width, etc) und
> bevor man sich versieht, hat man sprintf neu erfunden.

Genau so siehts bei mir aus.

von Klaus H. (klummel69)


Lesenswert?

Foobar schrieb:
> Flexibler mag sein, das mit dem ressourcenschonender möchte ich
> bezweifeln.  Statt in einer zentralen Formatierungsfunktion findet bei
> den Templates die Formatierung bei jedem einzelnen Aufrufer statt.

Das kommt auf die Implementierung an. Wenn für jeden Parameter ein 
(überladener) Funktionsaufruf genutzt wird, ist der Speicherplatz nicht 
viel größer als bei ...printf Übergabe, man spart aber die Dekodierung 
des Formatstrings und ist typsicher. Klar man hat mehrere 
Funktionsaufrufe,
aber das parsen des Formatstrings kostet auch Zeit.

von Reinhard (reinhard55)


Lesenswert?

Ich mache das jetzt so:
1
const char dataloc tx_destSIZE_zu_klein[]   
2
                      = "destSIZE zu klein (jstring). Muss >= "; 
3
4
int jstring(char* dest, uint8_t destSIZE, const char* format, ...) {
5
    int num_written;
6
    char errtx[sizeof(tx_destSIZE_zu_klein)+3];
7
    
8
    // Formatargumente direkt verwenden
9
    va_list args;
10
    va_start(args, format);
11
    
12
    // Formatieren des Texts in dest
13
    num_written = vsnprintf_P(dest, destSIZE, format, args);
14
    
15
    // Überprüfen, ob der Text abgeschnitten wurde (destSIZE)
16
    if (num_written >= (destSIZE - 1))
17
        {
18
        strcpy_P(errtx, tx_destSIZE_zu_klein);
19
        char num_str[10];
20
        ltoa(num_written,num_str,10);
21
        strcat(errtx,num_str);
22
        va_end(args);
23
        emergency_stop(errtx);
24
        }
25
        
26
    // Aufräumen der Argumentenliste
27
    va_end(args);
28
    
29
    return num_written; // Rückgabe der Anzahl geschriebener Zeichen
30
}
emergency_stop(errtx); schaltet auf dem Arduino und der angeschlossenen 
Elektronik alles aus, und gibt den errtx aus.

Aufruf z.B. in
1
void RangeCheck_MOD (uint16_t ZeilenNr, uint8_t MOD_ix) {..}
1
  char jou[30];
2
  jstring(jou,sizeof(jou),PSTR("MOD RangeErr in %d: %d"),ZeilenNr,MOD_ix);
3
  journal_print(jou); // ich habe da meine eigene Ausgabeprozedur, die in einen Ringpuffer schreibt... bla bla
Jetzt kann ich das prima verwenden.

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.