Skip to the content.

Trice Compared with printf

Contents

1. Printf-like functions

…have a lot of things to do: Copy format string from FLASH memory into a RAM buffer and parse it for format specifiers. Also parse the variadic parameter list and convert each parameter according to its format specifier into a character sequences, what includes several divisions - costly function calls. Concatenate the parts to a new string and deliver it to the output, what often means copying again. A full-featured printf library consumes plenty space and processing time and several open source projects try to make it better in this or that way. Never ever call a printf-like function in time critical code, like an interrupt - it would crash your target in most cases. The trice calls are usable inside interrupts, because they only need a few MCU clocks for execution. Porting legacy code to use it with the Trice library, means mainly to replace Printf-like function calls with trice function calls. See also chapter Legacy User Code Option Print Buffer Wrapping and Framing.

2. Trice IDs

3. Trice values bit width

4. Many value parameters

5. Floating Point Values

These types are mixable with integer types but need to be covered by converter function.


// aFloat returns passed float value x as bit pattern in a uint32_t type.
static inline uint32_t aFloat( float x ){
    union {
        float f;
        uint32_t u;
    } t;
    t.f = x;
    return t.u;
}

// aDouble returns passed double value x as bit pattern in a uint64_t type.
static inline uint64_t aDouble( double x ){
    union {
        double d;
        uint64_t u;
    } t;
    t.d = x;
    return t.u;
}

6. Runtime Generated 0-terminated Strings Transfer with triceS

Trice was designed mainly for speed. An universal trice like printf would cost too much runtime and destroy this main Trice advantage.

If you need several %s, like in

char* n = "Ann";
char* f = "Fox";
uint8_t dd = 22;
uint8_t mm = 11;
uint16_t yyyy = 1988;
// ...
print( "Name: %12s, Family: %s, Birthday %2u-%02u-%4u\n",  n, f, dd, mm, yyyy );

you can do:

// ...
triceS( "Name: %12s, ",  n );
triceS( "Family: %s, ", f );
trice( "Birthday %2u-%02u-%4u\n", dd, mm, yyyy );

or also

// ...
triceS( "Name: %12s, ",  n ); triceS( "Family: %s, ", f ); trice( "Birthday %2u-%02u-%4u\n", dd, mm, yyyy );

7. Runtime Generated counted Strings Transfer with triceN

8. Runtime Generated Buffer Transfer with triceB

  s = "abcde 12345"; // assume this as runtime generated string
  triceS( "msg:Show s with triceS: %s\n", s );
  len = strlen(s);
  triceN( "sig:Show s with triceN:%s\n", s, len );
  triceB( "dbg: %02x\n", s, len ); // Show s as colored code sequence in hex code.
  triceB( "msg: %4d\n", s, len ); // Show s as colored code sequence in decimal code.

This gives output similar to: ./ref/TRICE_B.PNG

Channel specifier within the TRICE_B format string are supported in Trice versions >= v0.66.0.

If the buffer is not 8 but 16, 32 or 32 bits wide, the macros TRICE8_B, TRICE16_B, TRICE32_B and TRICE64_B, are usable in the same manner.

9. Extended format specifier possibilities

9.1. Trice format specifier

9.2. Length modifier support

9.3. Overview Table

Format Specifier Type C Go T (T =Trice) | remark
signed decimal integer d d d Supported.
unsigned decimal integer u - u The Trice tool changes %u into %d and treats value as unsigned.
signed decimal integer i d i The Trice tool changes %i into %d and treats value as signed.
signed octal integer - o o With trice log -unsigned=false value is treated as signed.
unsigned octal integer o - o With trice log value is treated as unsigned.
signed octal integer with 0o prefix - O O With trice log -unsigned=false value is treated as signed.
unsigned octal integer with 0o prefix - - O With trice log value is treated as unsigned.
signed hexadecimal integer lowercase - x x With trice log -unsigned=false value is treated as signed.
unsigned hexadecimal integer lowercase x - x With trice log value is treated as unsigned.
signed hexadecimal integer uppercase - X X With trice log -unsigned=false value is treated as signed.
unsigned hexadecimal integer uppercase X - X With trice log value is treated as unsigned.
signed binary integer - b b With trice log -unsigned=false value is treated as signed.
unsigned binary integer - - b With trice log value is treated as unsigned.
decimal floating point, lowercase f f f aFloat(value)|aDouble(value)
decimal floating point, uppercase - F F aFloat(value)|aDouble(value)
scientific notation (mantissa/exponent), lowercase e e e aFloat(value)|aDouble(value)
scientific notation (mantissa/exponent), uppercase E E E aFloat(value)|aDouble(value)
the shortest representation of %e or %f g g g aFloat(value)|aDouble(value)
the shortest representation of %E or %F G G G aFloat(value)|aDouble(value)
a character as byte c - c Value can contain ASCII character.
a character represented by the corresponding Unicode code point c c c Value can contain UTF-8 characters if the C-File is edited in UTF-8 format.
a quoted character - q q Supported.
the word true or false - t t Supported.
a string s s s Use triceS macro with one and only one runtime generated string.
pointer address p p p Supported.
a double %% prints a single % % % % Supported.
Unicode escape sequence - U - Not supported.
value in default format - v - Not supported.
Go-syntax representation of the value - #v - Not supported.
a Go-syntax representation of the type of the value - T - Not supported.
nothing printed n - - Not supported.

TriceCheck output with numeric and string values

Select the preview to open the full animation.

10. Unsupported printf format features

Trice supports the common printf-style format specifiers used for embedded logging. Some less common printf features are intentionally not supported yet, because they do not fit well into the current lightweight Trice argument handling model or because they introduce side effects that are unsuitable for logging.

This mainly concerns:

10.1. Dynamic field width with *

In standard printf, a field width can either be fixed inside the format string or supplied dynamically.

Example with fixed width:

printf("%10d", value);

Example with dynamic width:

printf("%*d", width, value);

The * is not the value to be printed. It tells printf to consume an additional int argument from the argument list and to use that value as the field width. Therefore:

printf("%*d", 10, value);

behaves like:

printf("%10d", value);

A negative dynamic width has a special meaning and implies left-aligned output, similar to the - flag.

10.2. Dynamic precision with *

The same principle exists for precision.

Example with fixed precision:

printf("%.3f", value);

Example with dynamic precision:

printf("%.*f", precision, value);

Again, the * consumes an additional int argument. For strings this is often used to limit the maximum number of emitted characters:

printf("%.*s", maxLen, text);

10.3. Dynamic width and precision together

Both dynamic field width and dynamic precision can be used in the same conversion:

printf("%*.*f", width, precision, value);

This consumes three arguments:

int width;
int precision;
double value;

The important point is that each * consumes an additional int argument before the actual value argument. This means that %*.*f does not correspond to one runtime value only. It corresponds to int, int, double.

Trice is designed so that a format string usually maps to a compact and predictable sequence of transmitted values. Dynamic width and dynamic precision break that simple mapping because the * tokens are not visible output conversions, but they still consume additional arguments.

For that reason, Trice currently does not support these forms. Supporting them correctly would require the Trice format analysis to count and encode the hidden width and precision arguments in addition to the visible value arguments.

10.4. Wide character and wide string formats: %lc and %ls

The %lc and %ls conversions are valid C printf forms for wide character and wide string data.

They are not equivalent to %c and %s:

For Trice this matters because triceS and related string handling transport runtime buffers, but the Trice tool does not automatically know how a target represents wide characters internally.

Without additional target-specific metadata, the host side would not know:

Therefore, simply removing the l and treating %lc like %c or %ls like %s would not be correct. That would silently change the meaning of the original C format string and could decode the payload differently from what the target-side C code actually describes.

For that reason, Trice currently does not support %lc and %ls. Proper support would require extra target-side type or encoding information so that the host can decode the transported data in an unambiguous and portable way.

10.5. The special %n conversion specifier

The %n specifier is fundamentally different from ordinary printf conversions.

Most conversions produce output:

printf("%d", value);
printf("%s", text);
printf("%f", number);

The %n specifier prints nothing. Instead, it writes the number of characters printed so far into the object pointed to by the corresponding argument.

Example:

int count = 0;

printf("abc%nxyz", &count);

The visible output is:

abcxyz

After the call, count contains 3, because three characters were printed before %n was reached.

Depending on the length modifier, %n expects different pointer types:

printf("%n",   &i);   // int *
printf("%hn",  &s);   // short *
printf("%hhn", &c);   // signed char *
printf("%ln",  &l);   // long *
printf("%lln", &ll);  // long long *

This means %n is not a pure logging conversion. It has a side effect because it writes to memory.

10.6. Security implications of %n

The %n specifier is also relevant for format-string security.

This is unsafe when userInput is not trusted:

printf(userInput);

If userInput contains format specifiers, printf interprets them. If it contains %n, printf expects a pointer argument and writes through it. If no valid pointer was actually passed, this can cause undefined behavior, memory corruption, or a crash. In more serious cases, uncontrolled format strings can become a security vulnerability.

The correct way to print uncontrolled text is:

printf("%s", userInput);

Because %n writes to memory and is strongly associated with format-string vulnerabilities, many coding standards and safety-oriented code bases discourage or forbid it.

10.7. Why Trice does not support %n

Trice is a logging and tracing system. Its purpose is to transfer compact log information from the target to the host, where it is decoded into readable text.

The %n specifier does not fit this model for several reasons:

  1. %n produces no log output.
  2. %n writes to target memory through a pointer argument.
  3. The written value depends on the number of characters formatted so far.
  4. Trice intentionally avoids full target-side formatting in order to remain small and fast.
  5. Supporting %n would introduce a side effect into what should be a side-effect-free logging operation.
  6. %n has known security implications when format strings are not fully controlled.

For these reasons, Trice currently does not support %n.

This is intentional. Trice log statements should describe data to be logged, not modify application memory as a side effect of formatting.

11. UTF-8 Support

This is gratis, if you edit your source files containing the format strings in UTF-8:

./ref/UTF-8Example.PNG

The target does not even “know” about that, because it gets only the Trice IDs.

12. Switch the language without changing a bit inside the target code

Once the til.json list is done the user can translate it in any language and exchanging the list switches to another language. This is nowadays a simple AI agent task.

13. Format tags prototype specifier examples

This syntax is supported: %[flags][width][.precision][length]

(back to top)