Make putchar_() optional
Hello, great library and all, respect for effort and dedication maintaining and supporting it for all these years.
Now the fun part, I want to use formatting functionality of the library with printf()-like interface but without HW I/O, means I would only use vfctprintf() and provide my own I/O method at runtime.
But due to dependency on putchar_(), I have to create/generate extra .c file with a dummy putchar_() and then add it to printf target using target_sources(printf ...), and then build it.
It would be great if putchar_() dependency could be opted out through compile time switch defined through CMake option or cache variable to make this integration a bit easier.
Thank you.
I'm not sure I understand your request. putchar_() is the vehicle for doing what you wanted: Your putchar_() implementation will be a function which, given a character, invokes a runtime-provided I/O method (e.g. via a process-global or thread-global variable) to print that character. Without putchar_() - nothing is printed.
True, putchar_() is essential to printf() but, as I said, I'm not interested in printf(). I'm only using fctprintf() and it does not need putchar_(), it needs void (*out)(char c, void extra_arg). That is the one that I would provide at runtime and that is the one that would print what I want. There is no point for me to have putchar_() in my code that won't be used. Having putchar_() compile-time optional would allow me to build the library with the (fctprintf) subset I need.
Can't you set your toolchain and compilation flags so that this is not a problem? e.g., with GCC, compile with
-ffunction-sections -fdata-sections
and then link with
-Wl,--gc-sections
?
Well, I could, but we have a Windows customer and for Windows build this doesn't work with none of GCC (mingw64), LLVM, Cl (with /opt:ref) or ClangCL.
I also think this would be a nice thing to have :) I'm working with a few different embedded compilers and the way they handle garbage collecting unused symbols varies.
From my perspective I see two solutions, that have worked for my use-cases:
-
Separate the functions that require putchar_ into a new translation unit. All the linkers I've used ended up ignoring the extra object file in the library if they didn't reference any of its symbols.
-
Create a separate translation unit with a putchar_ definition that either forwards to stdio's putchar or is a noop. Again the linkers I've worked with have all ignored the extra object file if the user supplied their own implementation. I don't like this solution as much because the link order can influence which implementation is used (on GCC you could define it as weak, but I know not all compilers have a similar feature) and that can be nasty to debug.
Do you think either solution could be workable?
Well, two people requesting this is a 100% increase, so I will tend more toward obliging now...
but - there are actually multiple approaches to addressing this issue. I've asked about this on StackOverflow and started a bit of a discussion:
I'll specifically say that separating printf() and vprintf() into a separate translation unit will probably mean either duplicating a lot of code, or exposing a bunch of functions which are currently static.
Different translation unit (ie extra .c file) would work just fine too.
I'd guess, static functions are only static there to avoid name collisions.
You could just use longer and more unique names for that.
And no, not many functions, you would only need a couple (function_gadget() and vsnprintf_impl()).
Still it's a bit more work than just #ifdef it (this is more of a sketch):
- create an extra file (let's say printf_putchar.c)
- forward declare function_gadget() in it.
- move there four functions: putchar_wrapper, extern_putchar_gadget, vprintf_, printf_ (about 30 lines, let's say 90 with all the copyrights and includes)
- create another .h files (let's say printf_gadget.h)
- move declaration of printf_size_t and output_gadget_t there.
- include printf_gadget.h in both .c files.
- remove static from function_gadget() and vsnprintf_impl()
- add CMake option [let's say] PRINTF_BUILD_PUTCHAR to be ON by default.
- add printf_putchar.c into CMakeLists.txt conditionally into target_sources() as:
$<$<BOOL:${PRINTF_BUILD_PUTCHAR}>:src/printf/printf_putchar.c>and that is pretty much it.
Well come to think of it the simplest thing is probably just to have an extra config flag to either build them or not, and that will be necessary anyways when building a shared library.
From a usage perspective I do think it's a little more comfortable if you also separate things into two translation units because you won't have to recompile the library for when you want to provide putchar_ and when you don't. I've already implemented that locally. If you want I can clean it up a little, open a PR and you can see if you like it.
Different translation unit (ie extra .c file) would work just fine too. I'd guess, static functions are only static there to avoid name collisions. You could just use longer and more unique names for that. And no, not many functions, you would only need a couple (function_gadget() and vsnprintf_impl()). Still it's a bit more work than just
#ifdefit (this is more of a sketch):
- create an extra file (let's say printf_putchar.c)
- forward declare function_gadget() in it.
- move there four functions: putchar_wrapper, extern_putchar_gadget, vprintf_, printf_ (about 30 lines, let's say 90 with all the copyrights and includes)
- create another .h files (let's say printf_gadget.h)
- move declaration of printf_size_t and output_gadget_t there.
- include printf_gadget.h in both .c files.
- remove static from function_gadget() and vsnprintf_impl()
- add CMake option [let's say] PRINTF_BUILD_PUTCHAR to be ON by default.
- add printf_putchar.c into CMakeLists.txt conditionally into target_sources() as:
$<$<BOOL:${PRINTF_BUILD_PUTCHAR}>:src/printf/printf_putchar.c>and that is pretty much it.
Didn't catch that one when I wrote my last message, but yeah this is roughly what I did. I only forward declared vsnprintf_impl though, and put the other shared functions in the header, given that they're static inline
From a usage perspective I do think it's a little more comfortable if you also separate things into two translation units because you won't have to recompile the library for when you want to provide putchar_ and when you don't. I've already implemented that locally. If you want I can clean it up a little, open a PR and you can see if you like it.
Remember that many of the users just copy printf.h and printf.c and put it in their code, possibly with a couple of preprocessor definitions. An extra translation unit complicates their life.
If you want I can clean it up a little, open a PR and you can see if you like it.
You can do that, yes. But - I am leaning towards the simpler solution. If people have objections to that, please make them now...
@Gizzzzmo , @yzazik ?
Confirmed, with PROVIDE_PLAIN_PRINTF=OFF I can build without having to provide putchar_().