Forum: Compiler & IDEs sprintf ohne '\0'


von Michl (Gast)


Lesenswert?

Ich habe einen C-String, der mit irgendeinem Text befüllt ist.
In diesen will ich formatiert hineinschreiben (an eine beliebige 
Stelle).
D.h. ich würde sprintf oder Konsorten dafür benutzen.
Leider wird mir ja von den printf Funktionen immer die '\0' angehängt. 
D.h. mein ursprünglicher String wird beschnitten.
Einzige Möglichkeit die mir einfällt ist einen temporären Puffer 
anzulegen, dort reinzuschreiben und dann mittels strncpy das Ergebnis 
kopieren.
Speicher sowie CPU-Load sind noch nicht das Problem, könnt das also 
schon so machen.
Wundere mich nur, ob es für diese Problemstellung keine elegantere 
Lösung gibt.
Oder gibts da doch was ohne Umwege?

Grüße

von Walter T. (nicolas)


Lesenswert?

Hallo Michl,
ganz verstehe ich Deine Frage nicht. Du willst mitten in einen String 
einen anderen String kopieren, ohne daß die angehängte Null mitkopiert 
wird und memcpy gefällt Dir nicht?

Oder willst Du noch irgendwie formatieren?

Oder willst Du einen String in einen anderen einfügen à la snprintf? 
Dann wird die Null aus dem Ursprungsstring ohnehin nicht übernommen:
1
char a[] = "Hallo";
2
char[100] b;
3
snprintf(b,100,"lalala%slalala",a);
wird die terminierende Null von a ja sowieso nicht übernommen.

Viele Grüße
W.T.

: Bearbeitet durch User
von Oliver (Gast)


Lesenswert?

Michl schrieb:
> Einzige Möglichkeit die mir einfällt ist einen temporären Puffer
> anzulegen, dort reinzuschreiben und dann mittels strncpy das Ergebnis
> kopieren.

Aus der strncpy-Doku:
"destination and source shall not overlap (see memmove for a safer 
alternative when overlapping)."

Ohne zusätzlichen Buffer geht es nur, wenn du die Länge des 
einzukopierenden Strings vorher kennst, dir das durch /0 überschriebene 
Zeichen vor dem kopieren merkst, und dannach zurückschreibst.

Oliver

von Walter T. (nicolas)


Lesenswert?

Ich wollte meinen obigen Post löschen, habe Deine Frage nämlich erst 
nachher verstanden. Wenn Zeit keine Rolle spielt geht auch einfach:
1
char a[] = "Hallo mein";
2
int b = 3;
3
char c[] = "Schatzi";
4
char d[100];
5
snprintf(d,100,"%s %i. %s",a,b,c);

von Michl (Gast)


Lesenswert?

Oliver schrieb:
> Ohne zusätzlichen Buffer geht es nur, wenn du die Länge des
> einzukopierenden Strings vorher kennst, dir das durch /0 überschriebene
> Zeichen vor dem kopieren merkst, und dannach zurückschreibst.

Danke dir. So hab ichs mir auch gedacht. Ist zwar auch a weng a 
Gefrickel, aber sollte tun und braucht weder Puffer noch muss was 
kopiert werden.

von Karl H. (kbuchegg)


Lesenswert?

Michl schrieb:
> Oliver schrieb:
>> Ohne zusätzlichen Buffer geht es nur, wenn du die Länge des
>> einzukopierenden Strings vorher kennst, dir das durch /0 überschriebene
>> Zeichen vor dem kopieren merkst, und dannach zurückschreibst.
>
> Danke dir. So hab ichs mir auch gedacht. Ist zwar auch a weng a
> Gefrickel, aber sollte tun und braucht weder Puffer noch muss was
> kopiert werden.

Was macht man denn normalerweise mit einem String?
Man gibt ihn aus.

Und in dem Fall erhebt sich dann auch oft die Frage, ob man denn 
überhaupt in einem Rutsch ausgeben muss. Ein
1
  sprintf( buff, "Dies ist %s einfach so\n", str );
2
  lcd_puts( buff );

ist zwar nett. Aber ein
1
   lcd_puts( "Dies ist " );
2
   lcd_puts( str );
3
   lcd_puts( " einfach so\n" );

tut es ja auch. Ganz ohne Umkopiererei oder sonstigen Aufwand. Und kein 
Benutzer wird je merken, dass der Text am LCD nicht 'in einem Zug' 
geschrieben wurde.

Selbiges für Ausgaben über die UART, selbiges für Ausgaben auf ein File.

: Bearbeitet durch User
von Klaus W. (mfgkw)


Lesenswert?

Das stimmt meistens.

Wenn doch nicht, und man mit GNU arbeitet: evtl. geht es, die Datei mit 
fmemopen() (mit "b" im mode) zu öffnen.
Dnn werden nachfolgende fprintf() etc. direkt in den Speicher schreiben.
Ich bin nicht sicher, ob dann eine Nullterminierung geschrieben wird.

Falls doch: man kann sich mit fopencookie() so etwas ähnliches auch 
selbst bauen und hat dann volle Kontrolle, was beim Schreiben geschieht.
Damit und mit vfprintf() könnnte man sich das gewünschte mprintf() 
zusammenschrauben (wie sprintf, aber ohne Terminierung).
Könnte man ja vielleicht tatsächlich noch öfter brauchen ...

von Klaus W. (mfgkw)


Lesenswert?

Klaus Wachtler schrieb:
> Wenn doch nicht, und man mit GNU arbeitet: evtl. geht es, die Datei mit
> fmemopen() (mit "b" im mode) zu öffnen.
> Dann werden nachfolgende fprintf() etc. direkt in den Speicher schreiben.
> Ich bin nicht sicher, ob dann eine Nullterminierung geschrieben wird.

Ich habe es mal probiert, und es scheint zu klappen:
1
#include <stdlib.h>
2
#include <stddef.h>
3
#include <stdio.h>
4
#include <string.h>
5
#include <ctype.h>
6
7
void show( const char *p, size_t l )
8
{
9
  unsigned i;
10
  for( i=0; i<l; ++i, ++p )
11
  {
12
    printf( "[%3u] %3u = 0x%02x = %c\n",
13
            i,
14
            (unsigned)*p,
15
            (unsigned)*p,
16
            (isprint(*p) ? *p : '?')
17
            );
18
  }
19
}
20
21
int main( int nargs, char **args )
22
{
23
  char    puffer[20] = "0123456789012345678";
24
  FILE   *f = fmemopen( puffer, sizeof(puffer), "wb" );
25
26
  if( f )
27
  {
28
    fprintf( f, "name=%s", "otto" );
29
    fclose( f );
30
    show( puffer, sizeof(puffer) );
31
  }
32
  else
33
  {
34
    fprintf( stderr, "open failed!\n" );
35
  }
36
37
  return 0;
38
}
1
lwa4731@eso1188 ~ $ gcc -Wall t.c -o t && ./t
2
[  0] 110 = 0x6e = n
3
[  1]  97 = 0x61 = a
4
[  2] 109 = 0x6d = m
5
[  3] 101 = 0x65 = e
6
[  4]  61 = 0x3d = =
7
[  5] 111 = 0x6f = o
8
[  6] 116 = 0x74 = t
9
[  7] 116 = 0x74 = t
10
[  8] 111 = 0x6f = o
11
[  9]  57 = 0x39 = 9
12
[ 10]  48 = 0x30 = 0
13
[ 11]  49 = 0x31 = 1
14
[ 12]  50 = 0x32 = 2
15
[ 13]  51 = 0x33 = 3
16
[ 14]  52 = 0x34 = 4
17
[ 15]  53 = 0x35 = 5
18
[ 16]  54 = 0x36 = 6
19
[ 17]  55 = 0x37 = 7
20
[ 18]  56 = 0x38 = 8
21
[ 19]   0 = 0x00 = ?

von Amateur (Gast)


Lesenswert?

Früher gab's mal die Funktion memcpy, ich gehe mal davon aus, dass 
selbige nicht entfernt wurde.
Der einzige Nachteil ist, dass Du nun für ein eventuell überschriebenes 
Stringende selber zuständig bist.
Wenn die Einfügeposition sowieso bekannt ist...

von Michl (Gast)


Lesenswert?

Karl Heinz schrieb:
> Ganz ohne Umkopiererei oder sonstigen Aufwand. Und kein
> Benutzer wird je merken, dass der Text am LCD nicht 'in einem Zug'
> geschrieben wurde.

Grundsätzlich geb ich dir recht. Der Benutzer merkt nicht, dass die 
Ausgabe auf mehrere Schritte verteilt stattfindet.

Sehr wohl aber der Implementeur.
Mal ehrlich, wieso sollte man auf die Komfortfunktionen von den 
printf-Funktionen verzichten, wenn man sowohl Speicher als auch CPU-Zeit 
übrig hat? Das grenzt manchmal ja schon an Selbstkasteiung.
Ich hab auch noch eine Stelle in meiner Software, wo ich mir auf dem LCD 
Messwerte ausgeben lasse. Auf klassische Weise. Und mit klassisch mein 
ich: die LCD-API hat eine Funktion zum Cursor setzen, eine FUnktion zum 
Zeichen schreiben und eine Funktion zum String schreiben. Der "Ausgeber" 
darf sich also seine Messwerte selbst umrechnen und formatieren, muss 
dazu natürlich auch lokale temporäre Variablen halten. Von den zig 
Aufrufen der LCD-Funktionen mal ganz zu schweigen.

Natürlich klappt das. Aber es macht keinen Spaß. Und wenn du mal was 
änderst ist die Chance groß, dass die Ausgabe im ersten Schuss erstmal 
nicht so aussieht wie erwartet (und sei es nur weil ein Messwert nur 2 
statt 3 Dezimalstellen hat und die Ausgabe deswegen verschoben ist).
Meine UART-Routinen arbeiten schon lange nur noch mit formatierter 
Ausgabe. Mein LCD demnächst auch.
Erst gestern hatte ich den Fall dass ich über I²C Messwerte (8 Stück) 
bekam, die dann weiterverarbeitet werden. Das Ergebnis passte nicht. 
Also haut man sich an mehreren Stellen im Code einen printf-Aufruf zum 
Debuggen rein und schon sieht man wo was schief läuft.

Nein, auf den Komfort will ich nicht mehr verzichten. Sowas mach ich 
nicht mehr "per Hand".

Naja, aber der Thread ist eh abgedriftet. Ich hab meine Antwort, Danke 
euch dafür, und jetzt tobt euch aus :-)

von Walter T. (nicolas)


Lesenswert?

Hm...also meinem Printf (weder dem auf LCD noch dem auf UART) macht es 
aber nichts aus, ob es dreimal mit Teilstrings oder einmal mit dem 
zusammengesetzten Teil aufgerufen wird. Es ändert auch nichts an der 
Ausgabe. Für die Formatierung sind schließlich wie üblich 
Escape-Sequenzen zuständig.
1
printf("Hallo\b\b");
2
printf("\t Du\n");
3
printf("Experte!");
sieht genauso aus wie
1
printf("Hallo\b\b\t Du\nExperte!");
da letztendlich der Aufruf der putchar-Routinen völlig gleich verläuft.

: Bearbeitet durch User
von Oliver (Gast)


Lesenswert?

Michl schrieb:
> Mal ehrlich, wieso sollte man auf die Komfortfunktionen von den
> printf-Funktionen verzichten, wenn man sowohl Speicher als auch CPU-Zeit
> übrig hat?

Sollst du ja gar nicht. Deinen Teilstring kannst du ja ruhig mit sprintf 
erstellen. Was aber Blödsinn ist, ist der Ansatz, den Teilstring dann 
mit irgend einer Stringfunktion in einen anderen hineinzukopieren. 
Anfang und Ende des gesamten Strings müssen doch bekannt sein, denn 
ansonsten kommt da nur unleserlicher Unsinn raus.

Alos gib den Anfargstring aus, dann denn Mittelstring, und dann den 
Endstring.

Oliver

von Karl H. (kbuchegg)


Lesenswert?

Michl schrieb:

> Naja, aber der Thread ist eh abgedriftet.

Um ehrlich zu sein.
Ich denke, ich hab immer noch nicht verstanden, wo denn jetzt eigentlich 
der Schuh drückt.

von Klaus W. (mfgkw)


Lesenswert?

na ganz einfach: er hat einen langen String, und will dort irgendwo 
mittendrin mit etwas wie sprintf() einen Teil überschreiben.
sprintf() geht aber für ihn nicht, weil es seine Ausgabe mit \0 
terminiert und dadurch der alte lange String mittendrin terminiert ist.
1
vorher:           alt_alt_alt_alt_alt_alt_alt_alt\0
2
Wunsch:           alt_alt_neu_neu_alt_alt_alt_alt\0
3
mit sprintf():    alt_alt_neu_neu\0alt_alt_alt_alt\0

Deshalb will er etwas wie sprintf(), das aber seine Ausgabe nicht 
terminiert.

WARUM er das will, ist doch egal.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Klaus Wachtler schrieb:
> na ganz einfach: er hat einen langen String, und will dort irgendwo
> mittendrin mit etwas wie sprintf() einen Teil überschreiben.

Ah! Jetzt hab ichs.

Er will sowas
1
  char text[] = "Spg    V, Strm    A";
2
3
  sreplace( text,  4, "%3d", spg );
4
  sreplace( text, 15, "%3d", strm );

und das "spreplace" soll mit einem Format String arbeiten, aber das 
Ergebnis an eine definierte Stelle in den String einbauen.

Ja, könnte man machen. Muss man aber selber programmieren. Elegant würde 
das ganze, wenn man die Positionsangabe, dann auch noch mit in den 
Format String reinkriegt, zb.
1
  char text[] = "Spg    V, Strm    A";
2
3
  sreplace( text, "&4%3d&15%3d", spg, strm );


Für mich persönlich sehe ich die Notwendigkeit noch nicht, weil ich auf 
dem LCD Fixtext und variablen Text sowieso grundsätzlich trenne, und auf 
der UART stellt sich das Problem so nicht wirklich, denn bei einem fixen 
Layout wird wieder Fixtext und variabler Text getrennt, während bei 
gescrollter Ausgabe einfach mit sprintf ausgegeben wird.
Ja, manchmal ist das ein bischen Gefummel, bis man die Stellen dort hat, 
wo man sie haben will, aber so schlimm auch wieder nicht. Bezogen auf 
ein Gesamtprojekt fällt das meist unter ferner liefen.

Aber ok. So mach ich das, das bedeutet nicht, dass alle es so machen 
müssen.

: Bearbeitet durch User
von Klaus W. (mfgkw)


Lesenswert?

Wobei man bei einem LCD ja auch direkt jeden Teilbereich überschreiben 
kann.
Ich weiß nicht, wofür er es letztlich nehmen will: AVR oder was anderes? 
LCD oder was anderes?

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Ich könnte mir vorstellen, dass der TO ein virtuelles Display und ein 
reales verwendet. Ersteres existiert als Buffer im RAM, eine (mehr oder 
minder intelligente) Ausgaberoutine bildet dann diesen Buffer auf das 
real existierende Display ab.

Genau in diesem Falle bräuchte man die Möglichkeit, formatiert in einen 
Bereich eines Buffers schreiben zu können.

Diese Aufgabe könnte man - auf Terminals bezogen - mit der 
Curses-Funktion printw() und deren Verwandten vergleichen.

von Michl (Gast)


Lesenswert?

Frank M. schrieb:
> Ich könnte mir vorstellen, dass der TO ein virtuelles Display und ein
> reales verwendet. Ersteres existiert als Buffer im RAM, eine (mehr oder
> minder intelligente) Ausgaberoutine bildet dann diesen Buffer auf das
> real existierende Display ab.

Das sind 100 Punkte, du hast recht :-)
Ich geb zeichenweise meinen Puffer auf dem LCD aus um das "aktive 
Warten" bei längeren Texten zu vermeiden.
Nun will ich die LCD Routinen eben um formatierte Ausgabe erweitern.

PS: mir wird ja jetzt gleich wieder angekreidet, ich hätt nicht von 
Anfang an gesagt was ich vorhab.
Problem dabei ist, ich hätt dann 100 Vorschläge gekriegt wie eure LCD 
Routinen aussehen, aber keine Antwort auf die ursprüngliche Frage. Kennt 
man ja leider.
Aber um das Thema abzuschließen: ich werd wohl in einen temporären 
Puffer schreiben müssen und diesen dann umkopieren.
Problem ist, dass die Länge der formatierten Ausgabe ja erstmal 
unbekannt ist, und damit auch die Position des Zeichens, das man 
"retten" müsste wenn man direkt in den Displaypuffer schreibt.
Alternative wäre snprintf() zweilmal laufen zu lassen, bei ersten mal 
mit NULL als pointer. Dann hätte ich die Länge. Allerdings kostet diese 
Variante wohl deutlich mehr Overhead als ein strncpy oder memcpy.

Grüße

von Michl (Gast)


Lesenswert?

Karl Heinz schrieb:
> Ja, könnte man machen. Muss man aber selber programmieren. Elegant würde
> das ganze, wenn man die Positionsangabe, dann auch noch mit in den
> Format String reinkriegt, zb.
>   char text[] = "Spg    V, Strm    A";
>
>   sreplace( text, "&4%3d&15%3d", spg, strm );

So ähnlich schauts bei mir aus.
Allerdings lege ich die Positions- und Längeninfo nicht im Formatstring 
ab. Dieser soll einfach wie von printf gewohnt funktionieren.

In meiner LCD-Konfiguration definiere ich mir "Textfelder", die z.B. so 
aussehen:
1
typedef struct
2
{
3
  uint8_t x;
4
  uint8_t y;
5
  uint8_t size;
6
}textfield_t;
7
8
textfield_t textfields[] = { {1, 0, 12}, {1, 1, 8} };
9
10
#define LCD_FIELD_VTG 0
11
12
...
13
...
14
Lcd_Print(LCD_FIELD_VTG, "Vtg: %iV", voltage);

So zumindest die Idee. Ob der Textfeld-Mechanismus in der Praxis Sinn 
macht oder zu umständlich ist wird sich zeigen, ist noch nicht ganz 
fertig.
Aber gerade die Größenbegrenzung verhindert dass man sich wieder mal 
verzählt hat und Bereiche des Displays überschreibt.

Unabhängig davon ist mir die formatierte Ausgabe aber recht wichtig.

Grüße

von Karl H. (kbuchegg)


Lesenswert?

Michl schrieb:

> Das sind 100 Punkte, du hast recht :-)
> Ich geb zeichenweise meinen Puffer auf dem LCD aus um das "aktive
> Warten" bei längeren Texten zu vermeiden.
> Nun will ich die LCD Routinen eben um formatierte Ausgabe erweitern.

OK.

Wovon reden wir?
Von Gcc, genauer AVR-gcc?

Denn da gäbe es noch eine Möglichkeit.
Du kannst ja dem I/O System eine putchar Routine unterjubeln, die von 
printf bzw. fprintf zur Ausgabe benutzt wird. Leitest du mittels putchar 
die Ausgabe in deinen LCD-Buffer um, dann müsste das meinem Verständnis 
nach ziemlich genau das sein, was du suchst.

von Michl (Gast)


Lesenswert?

avr-gcc, allgemeiner eben gcc.

Meine Standardausgabe ist bereits umgeleitet, auf eine Instanz vom UART.

Könnte natürlich mir nochmal einen Stream anlegen und ein 
fprintf-Derivat benutzen.
Aber mal schauen, irgendwann wirds halt doch Overkill. Die Streamausgabe 
bringt eben mit sich dass die zum Stream gehörende putc() unter 
Umständen sehr oft aufgerufen wird.

Momentan gefällt mir die Lösung so wie ich sie in meinem letzten Beitrag 
beschrieben habe am Besten, also der Weg über die "Textfelder". Mit dem 
Umkopieren aus dem temporären Puffer kann ich ja leben, wie im 
Eröffnungspost geschildert. Die Frage war ja, ob es denn was anderes 
gäbe.

von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz schrieb:
> Michl schrieb:
>
>> Das sind 100 Punkte, du hast recht :-)
>> Ich geb zeichenweise meinen Puffer auf dem LCD aus um das "aktive
>> Warten" bei längeren Texten zu vermeiden.
>> Nun will ich die LCD Routinen eben um formatierte Ausgabe erweitern.
>
> OK.
>
> Wovon reden wir?
> Von Gcc, genauer AVR-gcc?
>
> Denn da gäbe es noch eine Möglichkeit.
> Du kannst ja dem I/O System eine putchar Routine unterjubeln, die von
> printf bzw. fprintf zur Ausgabe benutzt wird. Leitest du mittels putchar
> die Ausgabe in deinen LCD-Buffer um, dann müsste das meinem Verständnis
> nach ziemlich genau das sein, was du suchst.

Das sähe dann (minus Tippfehler), circa so aus
1
#include <stdio.h>
2
3
static int lcd_putchar(char c, FILE *stream);
4
5
static FILE mystdout = FDEV_SETUP_STREAM(lcd_putchar, NULL, _FDEV_SETUP_WRITE);
6
7
#define NR_ROWS 2
8
#define NR_COLS 20
9
10
char lcd_screen[NR_ROWS][NR_COLS];
11
uint8_t lcd_cursorRow = 0;
12
uint8_t lcd_cursorCol = 0;
13
14
static void lcd_newLine()
15
{
16
  lcd_cursorCol = 0;
17
  lcd_cursorRow++;
18
  if( lcd_cursorRow == NR_ROWS )
19
    lcd_cursorRow = 0;
20
}
21
22
static int lcd_putchar(char c, FILE *stream)
23
{
24
  if (c == '\n')
25
    lcd_newLine();
26
27
  else {
28
    lcd_screen[lcd_cursorRow][lcd_cursorCol++] = c;
29
    if( lcd_cursorCol == NR_COLS )
30
      lcd_newLine();
31
  }
32
33
  return 0;
34
}
35
36
int main(void)
37
{
38
  int i = 5;
39
  stdout = &mystdout;
40
41
  printf( "Hello, world! %d\n", i );
42
  printf( "Once again: hello" );
43
44
  return 0;
45
}

Deinen LCD-Rausschaufelmechanismus musst du natürlich noch auf 
lcd_screen ansetzen und ein paar zusätzliche Hilfsfunktionen zum 
Cursorpositionieren bzw. Screen löschen wirst du noch brauchen, aber so 
würde der Basismechanismus aussehen, mit dem man printf überredet, in 
einen Speicherbereich zu schreiben. Ganz ohne Umweg über zusätzliche 
Strings.

(Ob du die Newline Behanldung überhaupt brauchst, bzw. was beim Zeilen 
Overflow passieren soll, musst du natürlich selbst entscheiden. Ich hab 
halt mal einfach mal Wrap-Arounds angenommen. Kann man auch alles 
weglassen)

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