Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@ocornut
Last active March 24, 2023 11:23
Show Gist options
  • Star 31 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ocornut/06c6b109e820e82c7780 to your computer and use it in GitHub Desktop.
Save ocornut/06c6b109e820e82c7780 to your computer and use it in GitHub Desktop.
C/C++ tips for using printf-style functions
// C/C++ tips for using printf-style functions
// Lots of manipulation can be expressed simply and fast with printf-style formatting
// Also helps reducing the number of temporaries, memory allocations or copies
// ( If you are looking for a simple C++ string class that is printf-friendly and not heap-abusive,
// I've been using this one: https://github.com/ocornut/Str )
// If you are interested in a FASTER implementation of sprintf functions, see stb_sprintf.h
// https://github.com/nothings/stb/blob/master/stb_sprintf.h
// How to concatenate non-zero terminated strings
// Standard printf-style operators
// . = precision operator. on a string the function will read at most XX character.
// * = scalar value passed as parameter instead of embedded in format string
const char* input = "world##BAD_DATA##"; // suppose that after the 5th character is off-limit!
printf("Hello %.5s\n", input); // output "Hello world"
printf("Hello %.*s\n", 5, input); // output "Hello world"
// Here printf() will not read more than 5 characters from 'input'
// Other usages of * to pass in variables
printf("%0*d\n", 10, 123); // output "0000000123"
printf("%*s%s\n", 32, "", "Indented line"); // output " Indented line"
// printf() outputs to stdout
// fprintf() outputs to a file
// sprintf(), snprintf(), vsnprintf() etc outputs to a char buffer.
// From there you can use that to concatenate strings without temporary copies of duplicate.
// So next time you are copying around and assembling directory names, filenames, extensions,
// you can probably do it in one statement!
// I suggest creating a wrapper to snprintf/vsnprintf
// Because zero-termination is not guaranteed on reaching buffer size limit.
size_t FormatString(char* buf, size_t buf_size, const char* format, ...); // always zero-terminate
// The printf-style functions always return number of character they output.
// Passing NULL as buffer printf-style function only return the size needed without writing anything.
// Concatenation pattern
sprintf(buf, "%s/%s", "Folder", "Filename");
// Add %.*s in the mix to combine things efficiently.
// In the real world you almost always want to avoid using sprintf(), and use snprintf() instead:
char buf[512];
snprintf(buf, 512, "%s/%s", "Folder", "Filename");
// For raw C array you can use a macro. You may already have ARRAYSIZE() in a system header.
// This version is error-prone because if you pass a pointer (not an array) you'll get a 1 back.
// A better version of this can use template to infer the array size and error when not passed an array.
#define ARRAYSIZE(_ARR) (sizeof(_ARR)/sizeof(*_ARR))
#define ARRAYEND(_ARR) (_ARR + ARRAYSIZE(_ARR))
char buf[512];
snprintf(buf, ARRAYEND(buf), "%s/%s", "Folder", "Filename");
// A convenient concatenation pattern:
// (IMPORTANT: standard snprintf() returns the expected output length not capped to buffer size,
// so this only works if you wrap snprintf() into a helper function that cap the return value to buf_size-1.)
char buf[512];
char* buf_end = buf+ARRAYSIZE(buf);
char* buf_pos = buf;
buf_pos += MySnprintf(buf_pos, buf_end-buf_pos, "Folder");
buf_pos += MySnprintf(buf_pos, buf_end-buf_pos, "/");
buf_pos += MySnprintf(buf_pos, buf_end-buf_pos, "Filename");
// That looks more verbose but is more flexible if your control flow is variable.
// Once wrapped within your custom type it translate to an elegant append() function that behave correctly.
// As opposed to strcat() which incurs unnecessary strlen() on each call.
// Note the parallel with std::vector
// buf = data
// buf_pos-buf = size
// buf_end-buf = capacity
// From there you can create functions/methods that works with your string/buffer type.
// So you can benefit from printf-style flexibility with convenient functions/methods
// e.g.
mystr.Append("{ %s = %f }", name, value);
// If your string buffer already has space you can run a first "try" pass given buffer & buffer-size,
// and only if the first-pass reach the limit then you allocate and run a second-pass.
// You may also refer to this class: https://github.com/ocornut/Str
// For ideas of how to implement/use a printf friendly string class.
// In C++ you can use a template to infer buffer-size (got this one from Paul Holden, thanks!)
template<size_t BUF_SIZE>
size_t FormatString(char (&buf)[BUF_SIZE], const char* fmt, ...);
// With Clang/GCC you can declare printf-style error-checking in the compiler.
// Wrap the __attribute__ part in a macro so it can be empty on other compilers.
// There might be a way with Visual Studio?
size_t FormatString(char* buf, size_t buf_size, const char* format, ...) __attribute__(( format(printf,3,4) ))
// That's it for now!
// Twitter @ocornut
@mmozeiko
Copy link

mmozeiko commented Aug 7, 2017

// There might be a way with Visual Studio?

With VS you can do it with SAL annotations. Here's an example: https://godbolt.org/g/aMvRGX

This requires including sal.h header and pass /analyze argument to compiler.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment