FULL-C: printf

Tato stránka popisuje programovací jazyk FULL-C, který je dostupný na vybraných zařízeních SDS. 
Některá zařízení používají SDS-C, pro který máme návody jinde na této WiKi.

Standard

FULL-C plně sleduje standard jazyka C, i se všemi specifiky.

Nejčastější chyby v programu jsou právě z neúplné / nepřesné znalosti specifikace C90 / C99. Nejčastěji jsou problémy v nesprávném svázání formátovacích parametrů a jednotlivých prvků ve volání funkce, a také nesprávné zpracování výstupní hodnoty funkce.

Základní vstupní a výstupní funkce jsou tyto:

.
          int sprintf(char *str, char *format, ...);
          int snprintf(char *str, int n, char *format, ...);
          int sscanf(char *s, char *format, ...);

Práce s konzolí (echo.htm):

.
          int printf(char *format, ...);
          int putchar(int character);
          int puts(char *str);
          int printf(char *format, ...);
         void echoclear(void);

Formátovací systém

Uvedené funkce jsou velmi univerzální. Jejich funkce se konfiguruje formátovacím textem, který musíte vždy správně složit.

Vždy správně uveďte identifikátor šířky prvku - toto je nejčastější chyba. Bez uvedení přesné šířky prvku (např. 'hh' pro char), pracuje funkce s výchozí (maximální) hodnotou (pro tento příklad, by došlo k převedu char na int, a může se vytisknout něco úplně jiného - obvykle delšího, a to např. při sestavování zpráv pro komunikaci, může tuto komunikaci plně rozbít). Toto se stává častěji než by se řeklo...


Platí, že všechny formátovací prvky jsou uvedeny pomocí znaku procenta '%' s následnou informací o typu parametru.

%[flags][width][.precision][length]specifier 

Formátování lze (a je často velmi nutné) dále zpřesňovat - pomocí doplňujících parametrů v rámci každého formátovacího prvku:

[flags]   = parametry zarovnání

-        =  Left-justify within the given field width; Right justification is the default (see width sub-specifier).
+        =  Forces to preceed the result with a plus or minus sign (+ or -) even for positive numbers. By default, only negative numbers are preceded with a - sign.
(space)  =  If no sign is going to be written, a blank space is inserted before the value.
#        =  Used with o, x or X specifiers the value is preceeded with 0, 0x or 0X respectively for values different than zero. 
#        =  Used with a, A, e, E, f, F, g or G it forces the written output to contain a decimal point even if no more digits follow. 
#        =  By default, if no digits follow, no decimal point is written.
0        =  Left-pads the number with zeroes (0) instead of spaces when padding is specified (see width sub-specifier).
[width]   = určení délky

(number) =  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.
*        =  The width is not specified in the format string, but as an additional integer value argument preceding the argument that has to be formatted.
[precision] = určení délky

.number =  For integer specifiers (d, i, o, u, x, X): precision specifies the minimum number of digits to be written. If the value to be written is shorter than this number, the result is padded with leading zeros. The value is not truncated even if the result is longer. 
.number =  A precision of 0 means that no character is written for the value 0.
.number =  For a, A, e, E, f and F specifiers: this is the number of digits to be printed after the decimal point (by default, this is 6).
.number =  For g and G specifiers: This is the maximum number of significant digits to be printed.
.number =  For s: this is the maximum number of characters to be printed. By default all characters are printed until the ending null character is encountered.
.number =  If the period is specified without an explicit value for precision, 0 is assumed.
.*      =  	The precision is not specified in the format string, but as an additional integer value argument preceding the argument that has to be formatted.
[length] = určení typu parametru

           [specifier]
           d i          |  u b o x X      |  f F e E g G   |  c  |  s     |  p     |  n  
           -------------+-----------------+----------------+-----+--------+--------+-------
  (none) : int          |  unsigned int   | float          | int | char * | void * | int *
  hh     : signed char  |  unsigned char  |                |     |        |        | 
  h      : short        |  unsigned short |                |     |        |        | 
  l      : long / int   |  unsigned long  |                |     |        |        | 
  ll     : int64_t      |  uint64_t       |                |     |        |        | 
  j      : int          |  unsigned int   |                |     |        |        | 
  z      : int          |  unsigned int   |                |     |        |        | 
  t      : int          |  unsigned int   |                |     |        |        | 

[specifier] = určení práce

%%         = prints %
%b         = binary print (BASE 2)
%d, %i     = signed decimal integer
%o         = unsigned octal
%u         = unsigned decimal integer
%x, %X     = unsigned hexadecimal integer (lower / upper case)
%e, %E     = float - use scientific notation (mantissa/exponent) (lower / upper case)
%g, %G     = float - use shortest text as possible (lower / upper case)
%f, %F     = decimal floating point number (lower / upper case)
%c         = single character (WARNING: SET THE CORRECT LENGTH !!!)
%s         = null-terminated string
%p         = pointer address
%n         = (nothing printed) The corresponding argument must be a pointer to a signed int. The number of characters written so far is stored in the pointed location.	
%m         = NOT SUPPORTED
%a, %A     = NOT SUPPORTED

Samozřejmě, použití formátovacích prvků musí přesně odpovídat parametrům (...) poskytnutým příslušnému volání printf/sprintf/snprintf/sscanf.

Pozor ! nejčastější chyba je použití "pouze" %d nebo %u nad parametrem, který je kratší než plný integer (32bit), tedy např. char (8bit). V takovém případě se MUSÍ použít %hhd nebo %hhu !!!


Příklad:

 int i;
 unsigned int l, z;
 char c;
 char *q;
 float f;
 int64_t i64;

 printf("%d %u %x %s \n", i, l, z, q); // defaults: int, unsigned int, unsigned int, char *

 printf("%0.2f \n", f); // print float limited to 2 decimal places

 printf("%c \n", c); // print ascii character

 printf("%hhu \n", c); // print 8bit unsigned numeric value

 printf("%lld \n", i64); // prints signed 64 bit value

 printf("Zarovnani nulami zleva na deset mist: %010d \n", 1984);

Samozřejmě, i když jsou příklady ukázány na printf(), tak to platí pro všechny ostatní funkce (sprintf, snprintf, sscanf atd.) stejně.


int printf(char *format, ...);

Provede vypsání parametrů (...) do konzole (echo.html) v závislosti na formátování (format).

Vrací: (int) počet znaků které byly vytištěny (nebo 0 = nic nevytištěno). Pozor, printf() nemá automatický délkový limit, vydává znaky dokud nenarazí na konec svého zadaného vstupu !

Příklad:

 void main(void)
 {
   int i;

   i = 1 + 1;

   printf(" 1 + 1 = %d \n", i);

   for (;;); // wait here forever

 }

Příklad:

 void main(void)
 {
   char txt[6];

   txt[0] = 'h';
   txt[1] = 'e';
   txt[2] = 'l';
   txt[3] = 'l';
   txt[4] = 'o';
   txt[5] = 0;

   printf(" %s world \n", txt);

   for (;;);  // wait here forever

 }


int sprintf ( char * s, const char * format, ... );

Provede vypsání parametrů (...) do pole "s", v závislosti na formátování (format).

Vrací: (int) počet znaků (BEZ UKONČOVACÍ NULY, ta zde už NENÍ započítána), které byly vytištěny (nebo 0 = nic nevytištěno).

Pozor, použití sprintf() není doporučeno, z hlediska rizika překročení rozsahu pole (char * s), respektive může dojít k zápisu textu mimo deklarovanou či alokovanou oblast, na kterou ukazuje 's'. Toto je velice častá programátorská chyba.

Proto je důrazně doporučeno používat jen a pouze snprintf(), pro tyto účely (tisk do bufferu).


int snprintf ( char * s, size_t n, const char * format, ... );

Provede vypsání parametrů (...) do pole "s", o maximální délce "n" v závislosti na formátování (format).

Maximální délka musí zahrnovat prostor pro zakončovací nulu (0x00), která je VŽDY touto funkcí zapsána. Tedy, příkladem, aby se zapsal alespoň jeden platný znak, musí být "n" = 2 (pro tento znak a pro 0x00).

Vrací: (int) počet znaků (BEZ UKONČOVACÍ NULY, ta zde už NENÍ započítána), které BYLY, ČI BY BYLY vytištěny (nebo 0 = nic nevytištěno). Pozor na tuto nuanci !!!

Pokud totiž chcete vytisknout delší text, než kolik je hodnota "n", tak snprintf() správně zastaví tisk jakmile narazí na tento určený konec, tak jediná možnost jak se dozvědět, jak dlouhý by tedy ten výstupní buffer měl být (kolik by tedy bylo vhodné "n" aby se vše vlezlo), je použít právě návratovou hodnotu snprintf().

Pozor ! nejčastější chyba je slepé použití návratové hodnoty snprinf() bez znalosti jejího úplného významu (např. použití pro posun v bufferu, kdy se postupně volá více snprintf() za sebou).


Příklad:

 void main(void)
 {
   int i;
   char outbuf[32];

   i = 1 + 1;

   // example: try to print a longer text, than what would fit to (for this example) 16 bytes = (15 chars + 1 trailing zero): 
   len = snprintf(outbuf, 16, " 1 + 1 = %d ... 123456789 123456789 123456789 123456789 123456789 123456789\n", i);

   if (len > 16)
   {
     pritnf("output buffer is too small, we would really need %d bytes to fit the whole snprintf output\n", len);
   } else
   {
     printf("all have fit into the output buffer, used length is %d bytes (excluding trailing zero characted)\n", len);  
   }

   printf("snprintf() output = %s\n", outbuf);

   for (;;); // wait here forever

 }


int sscanf(char *s, char *format, ...);

Standardní implementace.


Přímá low-level práce s konzolí (echo.htm)

Několik doplňujících funkcí:

  • int putchar(int character);

Zapíše do konzole jeden znak (8 bit, i když je předáván funkci jako int).


  • int puts(char *str);

Zapíše do konzole obsah pole na které ukazuje 'str'. Zapisovány jsou znaky tak dlouho, dokud se v poli nenarazí na 0x00.


  • int printf(char *format, ...);

Samostatný popis viz výše.


  • void echoclear(void);

Smaže celý obsah konzole (= prázdná stránka echo.htm).


Souvislosti

Stejný způsob formátování používá i FULL-C funkce sprinf() a snprintf(), popř. sscanf().

Výpis do konzole (echo.html) je additivní, tzn. každé volání printf() tam text přidá (k textu, který tam už předtím byl). Pokud je potřeba obsah konzole smazat, použije se FULL-C funkce echoclear().


Tisk a převody signed / unsigned

Na toto si dejte pozor, už to bylo zdrojem mnohých nečekaných problémů.

Nejčastější chybou je tisk číselné hodnoty proměnné definované jako char.

Typy "char", "int" jsou dle C standardu "signed char", "signed int" atd. (pokud jej explicitně nedefinujete jako unsigned).

To přináší "problém" při použití (s)(n)printf funkcí (atd.), kdy se použije %u nebo %x pro tisk číselné hodnoty (tedy ne např. %c), ale interně se to uvnitř (s)(n)printf převede (např. na int32, viz tabulka výše) a pak až tiskne.

Příklad - z nějakého důvodu chceme vytisknout číselnou hodnotu ze 'c1' a 'c2' proměnných:

 char c1, c2;  // = signed char !!!
 c1 = 10; c2 = 200;
 //...
 printf("%u %u", c1, c2);

Co by jste čekali že uvidíte ? Podle C standardu uvidíte toto: "10 -56" (bez uvozovek).

Správně totiž musíte program zapsat takto:

 char c1, c2;
 c1 = 10; c2 = 200;
 //...
 printf("%hhu %hhu", (unsigned char)c1, (unsigned char)c2);

Výstup je pak "10 200" dle očekávání.