Forum: Compiler & IDEs GCC ARM: Problem mit "\n" am Stringende


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 Peter S. (psavr)


Lesenswert?

Ich habe festgestellt, dass der aktuelle GCC-ARM ein "\n" am Stringende 
wegfrisst! (Ich benutze die CooCox CoIDE, für STM32F4)

Zum Beispiel bei printf("Hallo\n");

Mit einem Hex-Editor kann man im generierten *.bin-File sehen, dass das 
\n (=0x10) tatsächlich fehlt.

- Wenn des \n am Stringanfang oder in der Mitte des String steht,
  ist es vorhanden.

- Wenn man zwei \n ans Stringende hängt, ist eines vorhanden.

- Ein \r wird nicht weggefressen.

- Optimierungslevel hat keinen Einfluss.

Ich vermute es liegt am Preprozessor: Link => Kapitel 1.1
http://sunsite.ualberta.ca/Documentation/Gnu/gcc-2.95.2/html_chapter/cpp_1.html

Kann es sein, dass der Preprocessor das \n" als Trigraph interpretiert 
und aus dem String wegfrisst? Falls "Ja": wie verbiete ich dem 
Preprozessor diese Unsitte?

von (prx) A. K. (prx)


Lesenswert?

Peter S. schrieb:
> \n (=0x10)

Soso...

von (prx) A. K. (prx)


Lesenswert?

Der Präprozessor interessiert sich nicht für den Inhalt von Strings. Die 
reicht er unverändert durch.

Trigraphs sehen anders aus.

von Stefan E. (sternst)


Lesenswert?

Schau nach, welche Funktion überhaupt aufgerufen wird. Es ist nämlich 
gut möglich, dass das 'printf("Hallo\n");' zu einem 'puts("Hallo");' 
optimiert wurde.

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


Lesenswert?

Peter S. schrieb:
> Ich habe festgestellt, dass der aktuelle GCC-ARM ein "\n" am Stringende
> wegfrisst!

Ja, nicht nur der für den ARM.

Wenn du statt der Glaskugel einfach den generierten Assemblercode
ansehen würdest, würdest du sofort feststellen, dass er nämlich
das printf() durch ein puts() ersetzt, und dann muss er das
abschließende Newline auch entfernen, denn puts() fügt selbst eins
an.

[edit: Stefan war einen Tick schneller]

von ♪Geist (Gast)


Lesenswert?

Hast du in der CooCox CoIDE die stdio.c ins Projekt eingebunden?
Ich vermute, wie Stefan schon gesagt hat, dass dein \n schon in der 
stdio wegoptimiert wird. Bzw. probier es mit \r\n.

von Peter S. (psavr)


Lesenswert?

>> \n (=0x10)
>Soso...
Korrigiere: \n (=(dez)10 = 0x0A)

>Hast du in der CooCox CoIDE die stdio.c ins Projekt eingebunden?
Ja

>Schau nach, welche Funktion überhaupt aufgerufen wird. Es ist nämlich
>gut möglich, dass das 'printf("Hallo\n");' zu einem 'puts("Hallo");'
>optimiert wurde.
Okey ich gucke mir mal des Assemblercode an.

von Peter S. (psavr)


Lesenswert?

>> Ich habe festgestellt, dass der aktuelle GCC-ARM ein "\n" am Stringende
>> wegfrisst!
>Ja, nicht nur der für den ARM.

Und was muss ich tun, damit ich bei printf("Hallo\n"); auch ein \n auf 
die UART geschrieben wird wird?

von (prx) A. K. (prx)


Lesenswert?

Liegt das nicht auf der Hand?
  -fno-builtin-printf

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


Lesenswert?

Peter S. schrieb:
> Und was muss ich tun, damit ich bei printf("Hallo\n"); auch ein \n auf
> die UART geschrieben wird wird?

Ein standardkonformes puts() bereitstellen.

von Peter S. (psavr)


Lesenswert?

When ich mit dem Debugger durch den Code gehe (-O0) sehe ich die 
folgende Verschachtelung der Aufrufe

main.c: printf("\nHallo\n") => puts(const char *pStr)
=> pStr @ 0x8002bb4 => "\nHallo\0" (ohne \n)

printf.c: puts() => fputs(pStr, stdout)
printf.c: fputs(const char *pStr, FILE *pStream) => fputc(*pStr, 
pStream)
printf.c: fputc(signed int c, FILE *pStream) => PrintChar(c)
printf.c: PrintChar(char c) => USART_SendData(Open_USART, (uint8_t) c);

stm32f4xx_usart.c:
1
//Diese Funktion habe ich angepasst
2
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
3
{
4
  /* Check the parameters */
5
  assert_param(IS_USART_ALL_PERIPH(USARTx));
6
  assert_param(IS_USART_DATA(Data));
7
  if ((Data=='\r')||(Data=='\n'))
8
  {
9
    USARTx->DR = ('\r');
10
    while (USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET); 
11
    Data='\n';
12
  }
13
   /* Transmit Data */
14
  USARTx->DR = (Data & (uint16_t)0x01FF);
15
  while (USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET);
16
}

Es wird nirgens ein \n ausgenommen. Ich füge aber eines ein, falls ein 
\r vorkommt.

von Peter S. (psavr)


Lesenswert?

>Ein standardkonformes puts() bereitstellen.
oher, bzw. wie sieht das aus? (Ich möchte natürlich auch printf() ohne 
\n am Stringende benutzen)

von (prx) A. K. (prx)


Lesenswert?

USART_FLAG_TC ist hier der verkehrte Ansatz. Immerhin hat die USART 
einen Puffer und den will man üblicherweise nutzen. USART_FLAG_TXE passt 
deshalb besser. Und zwar vorher getestet, nicht danach.

von (prx) A. K. (prx)


Lesenswert?

Ich würde übrigens dringend davon abraten, in dieser Form die PeriphLib 
zu hacken, weil du das in jeder Version neu machen musst und letztlich 
über die eigenen Füsse stolperst. Der korrekte Ansatz wäre, PrintChar 
auf Basis der unveränderten Lib-Funktionen zu definieren.

Apropos: Wie sieht PrintChar aus?

von Stefan E. (sternst)


Lesenswert?

Hast du denn überhaupt mal kontrolliert, ob das \n in der UART-Ausgabe 
tatsächlich fehlt? Denn wie bereits gesagt wurde, dass es am Ende des 
Strings im Speicher fehlt hat gar nichts zu sagen. Das \n sollte von 
fputs nach dem String automatisch hinzugefügt werden.

von Peter S. (psavr)


Lesenswert?

>Der korrekte Ansatz wäre, PrintChar
>auf Basis der unveränderten Lib-Funktionen zu definieren.
Wie zum Henker geht den das? Ich übe nun seit ca 16 Stunden ein ganz 
banales printf für die UART2 auf die Beine zu stellen!!!!

von Peter S. (psavr)


Lesenswert?

>Hast du denn überhaupt mal kontrolliert, ob das \n in der UART-Ausgabe
>tatsächlich fehlt?
Ja, es fehlt definitiv!

>Das \n sollte von fputs nach dem String automatisch hinzugefügt werden.
Ich will aber, dass fputs dann und nur dann ein \n am Stringende 
ausgibt, wenn ich auch ein \n am Stringende von printf verwende!

von (prx) A. K. (prx)


Lesenswert?

Peter S. schrieb:
> Wie zum Henker geht den das?

Sinngemäss:

PrintChar(int c):
  if c == LF
    PrintChar(CR);
  while (USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET);
  USART_SendChar(c);

von Karl H. (kbuchegg)


Lesenswert?

Peter S. schrieb:

>>Das \n sollte von fputs nach dem String automatisch hinzugefügt werden.
> Ich will aber, dass fputs dann und nur dann ein \n am Stringende
> ausgibt, wenn ich auch ein \n am Stringende von printf verwende!

Denkst du, die Compilerbauer sind doof, oder was?

Der Compiler ersetzt
printf( String );
natürlich nur dann durch einen Aufruf von puts, wenn der String auch mit 
einem \n aufhört.


puts      hängt an den String automatisch ein \n an
fputs     tut genau das nicht

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


Lesenswert?

fputs() darf kein \n am Stringende hinzufügen, nur puts() macht das!

Das ist eine schräge Asymmetrie in der C-Bibliothek, die man nur mit
der Erklärung "historisch gewachsen" belegen kann.

Mit anderen Worten, ein puts() kann man nicht implementieren, indem
man schreibt
1
#define puts(s) fputs(s, stdout)

Wenn das puts() in deiner Bibliothek kein \n von sich aus anhängt,
dann ist es kaputt. => /dev/mülltonne

von Peter S. (psavr)


Lesenswert?

>Apropos: Wie sieht PrintChar aus?
1
void PrintChar(char c)
2
{
3
  USART_SendData(Open_USART, (uint8_t) c);
4
}

>Denkst du, die Compilerbauer sind doof, oder was?
Irgendwas muss falsch laufen, wenn ein banales printf so kompliziert 
ist, bzw. mir hier offenbar auch keiner ein simples Beispiel geben kann, 
wie ich das unter " CooCox CoIDE, für STM32F4" bewerkstelligen kann!

von Karl H. (kbuchegg)


Lesenswert?

Jörg Wunsch schrieb:

> Mit anderen Worten, ein puts() kann man nicht implementieren, indem
> man schreibt
>
>
1
#define puts(s) fputs(s, stdout)
>
> Wenn das puts() in deiner Bibliothek kein \n von sich aus anhängt,
> dann ist es kaputt. => /dev/mülltonne


Eben.

1
#define puts(s) do { fputs(s, stdout); fputc('\n', stdout); } while( 0 )
würde aber gehen.

von (prx) A. K. (prx)


Lesenswert?

Peter S. schrieb:
1
> void PrintChar(char c)
2
> {
3
>   USART_SendData(Open_USART, (uint8_t) c);
4
> }
5
>

Dachte ich mir. Dort muss die CRLF-Nummer und der TXE-Test rein.

> Irgendwas muss falsch laufen, wenn ein banales printf so kompliziert
> ist, bzw. mir hier offenbar auch keiner ein simples Beispiel geben kann,

Nicht jeder verwendet diese Kuh-Hähne und programmieren musst du dein 
Programm schon selber. Was du gekriegst hast ist Hilfestellung.

von Peter S. (psavr)


Lesenswert?

>Wenn das puts() in deiner Bibliothek kein \n von sich aus anhängt,
>dann ist es kaputt. => /dev/mülltonne
Ok, die Bibliothek ist nicht von mir , die hat jemand in die IDE 
eingefügt! Ich schmeisse sie gerne weg, wenn mir doch bitte jemand 
erklären könnte, wie ich eine brauchbare vom aktuellen GCC ARM benutzen 
kann!
Wie macht das der Rest von dieser Welt?

von Karl H. (kbuchegg)


Lesenswert?

Peter S. schrieb:

>>Denkst du, die Compilerbauer sind doof, oder was?
> Irgendwas muss falsch laufen, wenn ein banales printf so kompliziert
> ist,

Ein banales printf ist nicht kompliziert.
Du musst nur der I/O Library dann auch die Treiber-Schicht so zur 
Verfügung stellen, wie sie das erwartet. Nicht mehr und nicht weniger. 
Mit 'kompliziert' hat das erst mal nicht viel zu tun. Das setzen von 
Pixel auf einer VGA-Karte ist auch nicht kompliziert im eigentlichen 
Sinne. Aber das ganze mit all den Hilfsfunktionen so in einen Treiber zu 
verpacken, dass er Windows als Display-Treiber untergejubelt werden 
kann, ist nun mal Aufwand.

von (prx) A. K. (prx)


Lesenswert?

Peter S. schrieb:
> Wie macht das der Rest von dieser Welt?

Ich verwende gern ein eigenes plattformunabhängiges rprintf mit etwas 
reduzierter Funktionalität und dadurch erheblich reduzierter Grösse.

Teils weil unter AVR nichts existierte, teils weil mir die Newlib 
mancher ARM-gccs zu monströs war.

von Karl H. (kbuchegg)


Lesenswert?

Peter S. schrieb:

> Wie macht das der Rest von dieser Welt?


Ich würde mal schätzen: Auf diesen ganzen printf/scanf Müll pfeifen.
Ist auf einem µC sowieso viel Aufwand für nichts.

Eine Routine, die einen String ausgeben kann ist schnell geschrieben und 
mehr braucht man Low-Level nicht. Maximal das man sich mit sprintf an 
die Formatiermöglichkeiten anhängt, die das printf Zeugs mitbringt.
Und für alles was mit Benutzereingabe zu tun hat, ist scanf sowieso 
nicht vernünftig zu gebrauchen.

von Karl H. (kbuchegg)


Lesenswert?

> main.c: printf("\nHallo\n") => puts(const char *pStr)
> => pStr @ 0x8002bb4 => "\nHallo\0" (ohne \n)
>
> printf.c: puts() => fputs(pStr, stdout)

Dann lass doch mal dein puts aus printf.c ansehen!

Dort muss nach dem String noch ein \n ausgegeben werden.
Tut es das nicht, dann hat der, der das geschrieben hat, geschlampt.

Von diesen Funktionen ...
> printf.c: puts() => fputs(pStr, stdout)
> printf.c: fputs(const char *pStr, FILE *pStream) => fputc(*pStr,
>           pStream)
> printf.c: fputc(signed int c, FILE *pStream) => PrintChar(c)
> printf.c: PrintChar(char c) => USART_SendData(Open_USART, (uint8_t) c);
... sind puts, fputs und fputc Standardfunktionen, die eine genau 
vorgeschriebene Funktionalität bereit stellen müssen. Der Compiler 
verlässt sich darauf. Tun die Funktionen das nicht, dann geht natürlich 
alles weitere schief.

von Peter S. (psavr)


Lesenswert?

>Dann lass doch mal dein puts aus printf.c ansehen!
1
signed int puts(const char *pStr)
2
{
3
  return fputs(pStr, stdout);
4
}

habe ich nun ersetzt durch
1
signed int puts(const char *pStr)
2
{
3
  signed int num = 0;
4
  while (*pStr != 0)
5
  {
6
    if (fputc(*pStr, stdout) == EOF)
7
    {
8
      return EOF;
9
    }
10
    num++;
11
    pStr++;
12
  }
13
  if (fputc('\n', stdout) == EOF)
14
  {
15
    return EOF;
16
  }
17
  return num++;
18
}

Muss der Rückgabewert des \n mitzählen?

Aber nun tut es, danke für eure Hilfe!

von Karl H. (kbuchegg)


Lesenswert?

Peter S. schrieb:
>>Dann lass doch mal dein puts aus printf.c ansehen!
>
1
signed int puts(const char *pStr)
2
> {
3
>   return fputs(pStr, stdout);
4
> }
5
>

Na da haben wirs doch.

> habe ich nun ersetzt durch
Was um alles in der Welt machst du da?

1
int puts(const char *pStr)
2
{
3
  if( fputs(pStr, stdout) != EOF )
4
    return fputc( '\n', stdout );
5
  return EOF;
6
}
und gut ists.

C-Standard (OK, die Draft-Version)

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf

1
7.19.7.10 The puts function
2
Synopsis
3
1 #include <stdio.h>
4
  int puts(const char *s);
5
6
Description
7
2 The puts function writes the string pointed to by s to the stream
8
  pointed to by stdout, and appends a new-line character to the output.
9
  The terminating null character is not written.
10
11
Returns
12
3 The puts function returns EOF if a write error occurs; otherwise it
13
  returns a nonnegative value.

Es ist nicht definiert, was der Returnwert genau aussagt, ausser das er 
in bestimmten Situationen EOF sein soll und wenn die nicht vorliegen, 
dann ist der Wert nicht negativ.

von Peter (Gast)


Lesenswert?

>> habe ich nun ersetzt durch...
>Was um alles in der Welt machst du da?
Ich wollte einfach die Functioncall-Tiefe nicht weiter aufblähen, daher 
habe ich fputs() ge-inlined! ;o)

von Peter S. (psavr)


Lesenswert?

INFO: Das entsprechende Projekt habe ich in der Codesammlung abgelegt:
=> Beitrag Beitrag "STM32F4Discovery mit CooCox CoOS-RTOS und printf"

von Klaus (Gast)


Lesenswert?

Peter schrieb:
>>Was um alles in der Welt machst du da?
> Ich wollte einfach die Functioncall-Tiefe nicht weiter aufblähen, daher
> habe ich fputs() ge-inlined! ;o)

Gibts für nicht benutzte Funktionen Geld zurück? ;-)

von Peter S. (psavr)


Lesenswert?

>Gibts für nicht benutzte Funktionen Geld zurück? ;-)
Fast: Man gewinnt etwas Speicherplatz auf dem Stack und etwas Zeit...

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.