mikrocontroller.net

Forum: Compiler & IDEs snprintf "%2d" - compiler Warnung


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.
Autor: OhWeia (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi,

ich bekomme eine bzw mehrere gleiche Compilerwarnungen nicht weg...
...
src/main.c: In function 'vT_DISP':
src/main.c:165:35: warning: '%2d' directive output may be truncated writing between 2 and 7 bytes into a region of size 5 [-Wformat-truncation=]
             snprintf(buf, 9, "Temp%2d.%1d", (int)(temperature/10), (int)(temperature%10));
                                   ^~~
src/main.c:165:30: note: directive argument in the range [2, 8103714]
             snprintf(buf, 9, "Temp%2d.%1d", (int)(temperature/10), (int)(temperature%10));
                              ^~~~~~~~~~~~~
src/main.c:165:30: note: directive argument in the range [0, 9]
src/main.c:165:13: note: 'snprintf' output between 9 and 14 bytes into a destination of size 9
             snprintf(buf, 9, "Temp%2d.%1d", (int)(temperature/10), (int)(temperature%10));
             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
...

Ich möchte mit snprintf maximal 8 Zeichen + Null in ein Array schreiben 
lassen. Das funktioniert auch soweit. Im Array steht danach dann z.b. 
"Temp20.4\0" drin.

Hier ein Auszug aus dem Code, der angemeckert wird:
char buf[9];
uint32_t temperature = ...;
...
snprintf(buf, 9, "Temp%2d.%1d", (int)(temperature/10), (int)(temperature%10));
(Es sind noch mehrere andere Stellen, aber immer nach dem selben Muster. 
%2d.)

Wie bekomme ich die Warnungen weg? In temperature wird nie etwas 
grösseres also 450-500 stehen (45.0°-50.0°). Muss ich erstmal zwei 
Variablen anlegen und sicherstellen, dass die nicht grösser als 2 bzw 1 
Stelle werden?

Danke,
OhWeia

Autor: OhWeia (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Oh, ich habe Compiler vergessen:

gcc-arm-none-eabi-8-2018-q4-major
gcc version 8.2.1 20181213 (release) [gcc-8-branch revision 267074] (GNU 
Tools for Arm Embedded Processors 8-2018-q4-major)

Die libc ist die newlib.

Autor: Walter T. (nicolas)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Andere Frage: Wenn Du eh printf benutzt - was kosten Dich 5 Bytes mehr 
im globalen Puffer?

Nebenbei: Wenn snprintf die volle Länge 9 ausnutzt, ist der String nicht 
mehr Null-terminiert.

: Bearbeitet durch User
Autor: OhWeia (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> Nebenbei: Wenn snprintf die volle Länge 9 ausnutzt, ist der String nicht
> mehr Null-terminiert.

Das steht in der manpage:
>The functions snprintf() and vsnprintf() write at most size bytes (including the 
terminating null byte ('\0')) to str.
Ich interpretiere das so, dass es die 0 immer schreibt. Wenn ich als 
länge also 9 angebe, passen maximal 8 Zeichen rein.

Walter T. schrieb:
> Andere Frage: Wenn Du eh printf benutzt - was kosten Dich 5 Bytes mehr
> im globalen Puffer?

Nichts, aber ich würde gerne wissen, wie ich mit den Formatbezeichnern 
die Kommazahl so anzeigen kann. %2d.%1d scheint aber wohl zu einfach 
gedacht zu sein...

Autor: Walter T. (nicolas)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
OhWeia schrieb:
> Nichts, aber ich würde gerne wissen, wie ich mit den Formatbezeichnern
> die Kommazahl so anzeigen kann. %2d.%1d scheint aber wohl zu einfach
> gedacht zu sein...

Das sollte funktionieren. Der Fehler liegt an einer anderen Stelle. 
Außer dass man üblicherweise ein Leerzeichen zwischen Text und Zahl 
läßt.

: Bearbeitet durch User
Autor: Arno (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
OhWeia schrieb:
> Walter T. schrieb:
>> Nebenbei: Wenn snprintf die volle Länge 9 ausnutzt, ist der String nicht
>> mehr Null-terminiert.
>
> Das steht in der manpage:
>>The functions snprintf() and vsnprintf() write at most size bytes (including the
> terminating null byte ('\0')) to str.
> Ich interpretiere das so, dass es die 0 immer schreibt. Wenn ich als
> länge also 9 angebe, passen maximal 8 Zeichen rein.

Das ist - ziemlich sicher - anders gemeint: Bei der Ermittlung der Größe 
wird die terminierende \0 mitgezählt. Wenn du 9 angibst, schreibt er 
(maximal) 9 Bytes rein, nicht 9 Bytes + \0.

OhWeia schrieb:
> Walter T. schrieb:
>> Andere Frage: Wenn Du eh printf benutzt - was kosten Dich 5 Bytes mehr
>> im globalen Puffer?
>
> Nichts, aber ich würde gerne wissen, wie ich mit den Formatbezeichnern
> die Kommazahl so anzeigen kann. %2d.%1d scheint aber wohl zu einfach
> gedacht zu sein...

Nein, %2d ist nur die Mindestlänge des Strings: 
http://www.cplusplus.com/reference/cstdio/printf/

"Minimum number of characters to be printed. If the value to be printed 
is shorter than this number, the result is padded with blank spaces. The 
value is not truncated even if the result is larger."

MfG, Arno

Autor: OhWeia (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Arno schrieb:
> Das ist - ziemlich sicher - anders gemeint: Bei der Ermittlung der Größe
> wird die terminierende \0 mitgezählt. Wenn du 9 angibst, schreibt er
> (maximal) 9 Bytes rein, nicht 9 Bytes + \0.

Ja, das schrieb ich doch so. 8 Zeichen + 0.

Walter T. schrieb:
> Außer dass man üblicherweise ein Leerzeichen zwischen Text und Zahl
> läßt.
"%2d" meinst du? Das meckert er auch an und funktioniert auch nicht. Da 
steht dann einfach nichts an den Stellen im Array.
Ein Leerzeichen ist kein Bezeichner... Das hab ich auch noch nie 
gesehen.

Arno schrieb:
> "Minimum number of characters to be printed. If the value to be printed
> is shorter than this number, the result is padded with blank spaces. The
> value is not truncated even if the result is larger."

Dann bleibt mir wohl nichts anderes, als das Array grösser zu machen.

Danke euch! :)

Autor: Walter T. (nicolas)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Arno schrieb:
> Das ist - ziemlich sicher - anders gemeint: Bei der Ermittlung der Größe
> wird die terminierende \0 mitgezählt. Wenn du 9 angibst, schreibt er
> (maximal) 9 Bytes rein, nicht 9 Bytes + \0.

Sagen wir es so: In der Realität ist es leider Compilerabhängig, ob ein 
mit snprintf erzeugter String mit zu kleiner Bufferlänge Nullterminiert 
ist. Mit _snprintf des MSVC ist es das nämlich z.B. nicht.

Autor: Yalu X. (yalu) (Moderator)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Du hast dem Compiler (direkt oder indirekt) die Option

  -Wformat-truncation=2

mitgegeben. Damit prüft der Compiler auf pessimistische Weise, ob der
resultierende String einschließlich der Nullterminierung länger als 9
werden kann und deswegen abgeschnitten wird.

  https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html

Das kann hier tatsächlich passieren, nämlich dann, wenn

  temperature/10 >= 100

oder

  temperature/10 <= -10

ist.

Du kannst den Pessimismus des Compilers reduzieren mit

  -Wformat-truncation=1

oder die Warnung mit

  -Wformat-truncation=0

oder

  -Wno-format-truncation

ganz abschalten.

Du kannst die Warnung auch eingeschaltet lassen und versuchen, dem
Compiler klarzumachen, dass temperature/10 niemals negativ oder mehr als
zweistellig wird. Von welchem Datentyp ist temperature?

Autor: OhWeia (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
temperature ist uint32_t.

Warnungen abschalten finde ich nicht so schön, an anderer Stelle entgeht 
mir dann vielleicht noch etwas...

Ich habe jetzt den buffer grösser gemacht und die Ausgabe-Routine gibt 
nicht mehr von Stelle 0 bis \0 aus, sondern von Stelle 0 bis Stelle 7. 
(Das Display hat 8 Stellen).
So bin ich die Warnung los und im Fehlerfall hab ich halt Müll auf dem 
Display und keinen Pointer-Amoklauf.

Autor: Yalu X. (yalu) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
OhWeia schrieb:
> temperature ist uint32_t.


Dieser Code (nur auf dem PC getestet mit GCC 8.2.1) liefert auch mit

  -Wformat-truncation=2

keine Warnung:
#include <stdio.h>
#include <assert.h>
#include <stdint.h>

uint32_t temp;

int main(void) {
  static char buf[9];
  assert(temp<100);
  snprintf(buf, 9, "Temp%2u.%1u", temp/10, temp%10);
}

Durch die Verwendung von unsigned weiß der Compiler, dass temp/10 und
temp%10 nicht negativ werden können, und durch das assert ist für
temp/10 auch eine obere Schranke definiert. Des Weiteren erkennt der
Compiler offensichtlich auch, dass temp%10 immer <10 ist.

Das assert benötigt allerdings etwas zusätzliche Rechenzeit. Deaktiviert
man es mit #define NDEBUG, erscheint auch die Warnung wieder.

Die internen Gedankengänge des Compilers sind auch ziemlich schwer
nachvollziehbar. So bleibt auch mit assert(temp<101) die Warnung aus,
obwohl temp==100 schon ein Problem darstellt. Erst mit assert(temp<1001)
erscheint sie wieder.

PS: Für uint32_t sollte man eigentlich das Format "%"PRIu32 statt "%u"
verwenden. Bei 32-Bit-Ints tritt der Unterschied aber nicht in
Erscheinung.

: Bearbeitet durch Moderator
Autor: Yalu X. (yalu) (Moderator)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Folgendes geht ebenfalls ohne Warnung durch:

  ...
  snprintf(buf, 9, "Temp%2u.%1u", temp/10%100, temp%10);
  ...

und

  ...
  uint32_t temp10 = temp / 10;
  if(temp10 > 99)
    temp10 = 99;
  snprintf(buf, 9, "Temp%2u.%1u", temp10, temp%10);
  ...

Aber auch diese Varianten sind aber bzgl. Programmgröße und Rechenzeit
nicht kostenlos.

: Bearbeitet durch Moderator
Autor: guest (Gast)
Datum:

Bewertung
-1 lesenswert
nicht lesenswert
Yalu X. schrieb:
> Das assert benötigt allerdings etwas zusätzliche Rechenzeit.

Yalu X. schrieb:
> Aber auch diese Varianten sind aber bzgl. Programmgröße und Rechenzeit
> nicht kostenlos.

Tja, bei MS würde man einfach "__analysis_assume(temp<100);" verwenden.
GCC scheint sowas allerdings nicht zu kennen.

Autor: Oliver S. (oliverso)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Yalu X. schrieb:
> Dieser Code (nur auf dem PC getestet mit GCC 8.2.1) liefert auch mit
>
>   -Wformat-truncation=2
>
> keine Warnung:

Der liefert hier mit gcc 8.3.0 weder mit noch ohne das assert eine 
Warnung. Das scheint also sehr implementationsabhängig zu sein.

Oliver

Autor: Jemand (Gast)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
guest schrieb:
> Tja, bei MS würde man einfach "__analysis_assume(temp<100);" verwenden.
> GCC scheint sowas allerdings nicht zu kennen.

Bei GCC geht das mit __builtin_unreachable(), du Experte

Autor: Lisa-Marie (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Jemand schrieb:
> guest schrieb:
>> Tja, bei MS würde man einfach "__analysis_assume(temp<100);" verwenden.
>> GCC scheint sowas allerdings nicht zu kennen.
>
> Bei GCC geht das mit __builtin_unreachable(), du Experte

Unfug.
Das macht etwas völlig anderes.

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Lisa-Marie schrieb:
> Jemand schrieb:
>> Bei GCC geht das mit __builtin_unreachable(), du Experte
>
> Unfug.
> Das macht etwas völlig anderes.

So völlig anders nun auch wieder nicht:

__builtin_unreachable() erzeugt keinen Code, ein Statement wie
if (temp >= 100)
    __builtin_unreachable();
erzeugt also auch keinen Code.  Und den Analyse-Passes kann man so 
mitteilen, dass temp nicht > 99 werden kann so dass Warnungen 
zielgenauer werden.  In manchen Fällen kann damit auch die Codeerzeugung 
verbessert werden, aber man will kaum den Code mit solchen 
compilerabhängigen Statements verunstalten...

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.