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


von OhWeia (Gast)


Lesenswert?

Hi,

ich bekomme eine bzw mehrere gleiche Compilerwarnungen nicht weg...
1
...
2
src/main.c: In function 'vT_DISP':
3
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=]
4
             snprintf(buf, 9, "Temp%2d.%1d", (int)(temperature/10), (int)(temperature%10));
5
                                   ^~~
6
src/main.c:165:30: note: directive argument in the range [2, 8103714]
7
             snprintf(buf, 9, "Temp%2d.%1d", (int)(temperature/10), (int)(temperature%10));
8
                              ^~~~~~~~~~~~~
9
src/main.c:165:30: note: directive argument in the range [0, 9]
10
src/main.c:165:13: note: 'snprintf' output between 9 and 14 bytes into a destination of size 9
11
             snprintf(buf, 9, "Temp%2d.%1d", (int)(temperature/10), (int)(temperature%10));
12
             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
13
...

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:
1
char buf[9];
2
uint32_t temperature = ...;
3
...
4
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

von OhWeia (Gast)


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.

von Walter T. (nicolas)


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
von OhWeia (Gast)


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

von Walter T. (nicolas)


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
von Arno (Gast)


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

von OhWeia (Gast)


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! :)

von Walter T. (nicolas)


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.

von Yalu X. (yalu) (Moderator)


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?

von OhWeia (Gast)


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.

von Yalu X. (yalu) (Moderator)


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:
1
#include <stdio.h>
2
#include <assert.h>
3
#include <stdint.h>
4
5
uint32_t temp;
6
7
int main(void) {
8
  static char buf[9];
9
  assert(temp<100);
10
  snprintf(buf, 9, "Temp%2u.%1u", temp/10, temp%10);
11
}

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
von Yalu X. (yalu) (Moderator)


Lesenswert?

Folgendes geht ebenfalls ohne Warnung durch:

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

und

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

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

: Bearbeitet durch Moderator
von guest (Gast)


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.

von Oliver S. (oliverso)


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

von Jemand (Gast)


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

von Lisa-Marie (Gast)


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.

von Johann L. (gjlayde) Benutzerseite


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
1
if (temp >= 100)
2
    __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...

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.