Last active
March 24, 2023 11:23
-
-
Save ocornut/06c6b109e820e82c7780 to your computer and use it in GitHub Desktop.
C/C++ tips for using printf-style functions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.