Single header string library for microcontrollers implements the following routines:
*printf()
- implementsprintf()
family of functions that can print standard specifiers (including floating point%f
and%g
) as well as non-standard%m
and%M
specifiers that allow custom formatting like JSON, hex, base64json_get()
- find element in a JSON stringjson_get_num()
- fetch numeric value from a JSON stringjson_get_bool()
- fetch boolean value from a JSON stringjson_get_str()
- fetch string value from a JSON stringxhexdump()
- print hex dump of the given memory buffer
- Ideal for implementing serial protocols, debug logging, JSON data exchange
- Source code is both ISO C and ISO C++ compliant
- Tiny size: see the Footprint section below for exact numbers
*printf()
supports all major format specifiers, width, precition and alignment*printf()
supports floating point%f
and%g
specifiers by default*printf()
Supports non-standard%M
,%m
specifiers which allows for custom formats- Can print to anything: to a memory buffer, file, socket, UART, etc
- Extensively tested using VC98, VC22, ARM GCC, AVR GCC, x86 GCC, Clang
Printing to a buffer:
Lines 174 to 177 in 873b39d
Print to the UART. Output JSON, and base64-encoded data:
Lines 1 to 14 in 42f0571
Parse JSON:
Lines 183 to 190 in 813e08a
Parse and print floating point:
Lines 155 to 158 in 23ebc5c
str.h
is divided in two parts: API declaration, and implementation.
The implementation part is wrapped into the preprocessor conditional:
// API declarations
size_t xprintf(void (*)(char, void *), void *, const char *, ...);
...
#ifndef STR_API_ONLY
// Implementation
......
#endif
If more than one file includes str.h
, then the build will result in
duplicate symbols, because the implementation will end up duplicated in
several object files.
To avoid this, only one file should include the full
str.h
, and the rest of the files should only include API declarations:
file1.c:
#include "str.h"
...
file2.c, file3.c, ...:
#define STR_API_ONLY
#include "str.h"
...
size_t vxprintf(void (*fn)(char, void *), void *arg, const char *fmt, va_list *);
size_t xprintf(void (*fn)(char, void *), void *arg, const char *fmt, ...);
Print formatted string using an output function fn()
. The output function
outputs a single byte: void fn(char ch, void *param) { ... }
. By using
different output functions, it is possible to print data to anywhere.
Parameters:
fn
- an output functionarg
- an parameter for thefn()
output functionfmt
- printf-like format string which supports the following specifiers:%hhd
,%hd
,%d
,%ld
,%lld
- forchar
,short
,int
,long
,int64_t
%hhu
,%hu
,%u
,%lu
,%llu
- same but for unsigned variants%hhx
,%hx
,%x
,%lx
,%llx
- same, for unsigned hex output%g
,%f
- fordouble
%c
- forchar
%s
- forchar *
%%
- prints%
character itself%p
- for any pointer, prints0x.....
hex value%M
- (NON-STANDARD EXTENSION): prints using a custom format function%m
- (NON-STANDARD EXTENSION): same as%M
but double-quotes the result%X.Y
- optional width and precision modifiers%.*
- optional precision modifier specified asint
argument
Return value: Number of bytes printed
The %M
specifier expects a cusom format function that can grab any number of
positional arguments. That format function should return the number of bytes
it has printed. Here its signature:
size_t (*ff)(void (*fn)(char, void *), void *arg, va_list *ap);
Parameters:
fn
- an output functionarg
- an parameter for thefn()
output functionap
- a pointer for fetching positional arguments
This library ships with several pre-defined format functions described below.
For example, a fmt_b64()
format function grabs two positional arguments:
int
and void *
, and base64-encodes that memory location:
char buf[100]; // Base64-encode "abc"
xsnprintf(buf, sizeof(buf), "%M", fmt_b64, 3, "abc"); // buf contains: YWJj
size_t xvsnprintf(char *buf, size_t len, const char *fmt, va_list *ap);
size_t xsnprintf(char *buf, size_t len, const char *fmt, ...);
Print formatted string into a fixed-size buffer. Parameters:
buf
- a buffer to print to. Can be NULL, in this caselen
must be 0len
- a size of thebuf
fmt
- a format string. Supports all specifiers mentioned above
Return value: number of bytes printed. The result is guaranteed to be NUL terminated.
int json_get(const char *buf, int len, const char *path, int *size);
Parse JSON string buf
, len
and return the offset of the element
specified by the JSON path
. The length of the element is stored in size
.
Parameters:
buf
- a pointer to a JSON stringlen
- a length of a JSON stringpath
- a JSON path. Must start with$
, e.g.$.user
,$[12]
,$
, etcsize
- a pointer that receives element's length. Can be NULL
Return value: offset of the element, or negative value on error.
Usage example:
// JSON string buf, len contains { "a": 1, "b": [2, 3] }
int size, ofs;
// Lookup "$", which is the whole JSON. Can be used for validation
ofs = json_get(buf, len, "$", &size); // ofs == 0, size == 23
// Lookup attribute "a". Point to value "1"
ofs = json_get(buf, len, "$.a", &zize); // ofs = 7, size = 1
// Lookup attribute "b". Point to array [2, 3]
ofs = json_get(buf, len, "$.b", &size); // ofs = 15, size = 6
// Lookup attribute "b[1]". Point to value "3"
ofs = json_get(buf, len, "$.b[1]", &size); // ofs = 19, size = 1
int mg_json_get_num(const char *buf, int len, const char *path, double *val);
Fetch numeric (double) value from the json string buf
, len
at JSON path
path
into a placeholder val
. Return true if successful.
Parameters:
buf
- a pointer to a JSON stringlen
- a length of a JSON stringpath
- a JSON path. Must start with$
val
- a placeholder for value
Return value: 1 on success, 0 on error
Usage example:
double d = 0.0;
json_get_num("[1,2,3]", 7, "$[1]", &d)); // d == 2
json_get_num("{\"a\":1.23}", 10, "$.a", &d)); // d == 1.23
int json_get_bool(const char *buf, int len, const char *path, int *val);
Fetch boolean (bool) value from the json string buf
at JSON path
path
into a placeholder v
. Return true if successful.
Parameters:
buf
- a pointer to a JSON stringlen
- a length of a JSON stringpath
- a JSON path. Must start with$
val
- a placeholder for value
Return value: 1 on success, 0 on error
Usage example:
int b = 0;
json_get_bool("[123]", 5, "$[0]", &b)); // Error. b == 0
json_get_bool("[true]", 6, "$[0]", &b)); // b == 1
long json_get_long(const char *buf, int len, const char *path, long default_val);
Fetch integer numeric (long) value from the json string buf
, len
at JSON path
path
. Return it if found, or default_val
if not found.
Parameters:
buf
- a pointer to a JSON stringlen
- a length of a JSON stringpath
- a JSON path. Must start with$
default_val
- a default value for the failure case
Return value: found value, or default_val
value
Usage example:
long a = json_get_long("[123]", 5, "$a", -1)); // a == -1
long b = json_get_long("[123]", 5, "$[0]", -1)); // b == 123
int json_get_str(const char *buf, int len, const char *path, char *dst, size_t dstlen);
Fetch string value from the json string json
at JSON path
path
. If found, a string is allocated using calloc()
,
un-escaped, and returned to the caller. It is the caller's responsibility to
free()
the returned string.
Parameters:
buf
- a pointer to a JSON stringlen
- a length of a JSON stringpath
- a JSON path. Must start with$
dst
- a pointer to a buffer that holds the resultdstlen
- a length of a result buffer
Return value: length of a decoded string. >= 0 on success, < 0 on error
Usage example:
char dst[100];
json_get_str("[1,2,\"hi\"]", "$[2]", dst, sizeof(dst)); // dst contains "hi"
void xhexdump(void (*fn)(char, void *), void *arg, const void *buf, size_t len);
Print hex dump of the given memory buffer.
Parameters:
fn
- an output functionarg
- an parameter for thefn()
output functionbuf
- a pointer to a buffer to printlen
- a length of a buffer
Usage example:
xhexdump(xputchar, NULL, "hi", 2);
size_t fmt_*(void (*fn)(char, void *), void *arg, va_list *ap);
Pre-defined helper functions for %M
specifier:
fmt_ip4
- print IPv4 address. Expect a pointer to 4-byte IPv4 addressfmt_ip6
- print IPv6 address. Expect a pointer to 16-byte IPv6 addressfmt_mac
- print MAC address. Expect a pointer to 6-byte MAC addressfmt_b64
- print base64 encoded data. Expectint
,void *
fmt_esc
- print a string, escaping\n
,\t
,\r
,"
. Espectsint
,char *
Examples:
uint32_t ip4 = 0x0100007f; // Print IPv4 address:
xsnprintf(buf, sizeof(buf), "%M", fmt_ip4, &ip4); // 127.0.0.1
uint8_t ip6[16] = {1, 100, 33}; // Print IPv6 address:
xsnprintf(buf, sizeof(buf), "%M", fmt_ip4, &ip6); // [164:2100:0:0:0:0:0:0]
uint8_t mac[6] = {1, 2, 3, 4, 5, 6}; // Print MAC address:
xsnprintf(buf, sizeof(buf), "%M", fmt_mac, &mac); // 01:02:03:04:05:06
const char str[] = {'a', \n, '"', 0}; // Print escaped string:
xsnprintf(buf, sizeof(buf), "%M", fmt_esc, 0, str); // a\n\"
const char *data = "xyz"; // Print base64 data:
xsnprintf(buf, sizeof(buf), "%M", fmt_b64, 3, data); // eHl6
It is easy to create your own format functions to format data that is specific to your application. For example, if you want to print your data structure as JSON string, just create your custom formatting function:
struct foo { int a; double b; const char *c; };
size_t fmt_foo(void (*fn)(char, void *), void *arg, va_list *ap) {
struct foo *foo = va_arg(*ap, struct foo *);
return xxprintf(fn, arg, "{\"a\":%d, \"b\":%g, \"c\":%m}",
foo->a, foo->b, ESC(c));
}
And now, you can use that function:
struct foo foo = {1, 2.34, "hi"};
xsnprintf(buf, sizeof(buf), "%M", fmt_foo, &foo);
The x*printf()
functions always return the total number of bytes that the
result string takes. Therefore it is possible to print to a malloc()-ed
buffer in two passes: in the first pass we calculate the length, and in the
second pass we print:
size_t len = xsnprintf(NULL, 0, "Hello, %s", "world");
char *buf = malloc(len + 1); // +1 is for the NUL terminator
xsnprintf(buf, len + 1, "Hello, %s", "world");
...
free(buf);
The following table contains footprint measurements for the ARM Cortex-M0 and
ARM Cortex-M7 builds of xsnprintf()
compared to the standard snprintf()
.
The compilation is done with -Os
flag using ARM GCC 10.3.1. See
test/footprint.c and the corresponding
Makefile snippet
used for measurements.
Cortex-M0 | Cortex-M7 | |
---|---|---|
xsnprintf (no float) |
1844 | 2128 |
xsnprintf |
10996 | 5592 |
Standard snprintf (no float) |
68368 | 68248 |
Standard snprintf |
87476 | 81420 |
Notes:
- by default, standard snrpintf does not support float, and
x*printf
does - to enable float for ARM GCC (newlib), use
-u _printf_float
- to disable float for
x*printf
, use-DNO_FLOAT
This library is licensed under the dual license:
-
Open source projects are covered by the GNU Affero 3.0 license
-
Commercial projects/products are covered by the commercial, permanent, royalty free license:
- Buy a single product license - covers a single company's product
- Buy a company wide license - covers all company's products
-
For licensing questions, contact us