7

I'd like to convert multiple numbers into some representation and then use the flags, width and precision of *printf() specifiers. Preference would be to avoid global or static buffers. The key problem appears to be is how to provide a char[] for each of the converted numbers?

fprintf(ostream, "some_format", foo(int_a, base_x), foo(int_b, base_y), ...);

How to use C11 compound literals to solve this?
How to use C99 (or later) compound literals to solve this?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256

1 Answers1

15

C99 C11 introduced compound literals which allow not only a complicated initialized structure, but also an "in-line" variable.

Code can call a conversion function and pass in a new buffer (char [UTOA_BASE_N]){0} per each function call allowing the function to return that same buffer, now written as needed that is still within its lifetime. The returned string is then printed using various flags, width and precision available to the "%s" specifier.

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

// Maximum buffer size needed
#define UTOA_BASE_N (sizeof(unsigned)*CHAR_BIT + 1)

char *utoa_base(char *s, unsigned x, unsigned base) {
  s += UTOA_BASE_N - 1;
  *s = '\0';
  if (base >= 2 && base <= 36) {
    do {
      *(--s) = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[x % base];
      x /= base;
    } while (x);
  }
  return s;
}

#define TO_BASE(x,b) utoa_base((char [UTOA_BASE_N]){0} , (x), (b))

void test(unsigned x) {
  printf("base10:%10u base2:%5s  base36:%s ", x, TO_BASE(x, 2), TO_BASE(x, 36));
  printf("%lu\n", strtoul(TO_BASE(x, 36), NULL, 36));
}

int main(void) {
  test(0);
  test(25);
  test(UINT_MAX);
}

Output

base10:         0 base2:    0  base36:0 0
base10:        25 base2:11001  base36:P 25
base10:4294967295 base2:11111111111111111111111111111111  base36:1Z141Z3 4294967295

Ref: Is there a printf converter to print in binary format? has a number of answers but none of them allow the simple memory management (no static) of the above with access to fprintf() flags width, precision and use the full range of the number.

This is an Answer your own question answer.

Community
  • 1
  • 1
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • 1
    Note: I assert the return value is still in scope due to "... its lifetime extends from entry into the block with which it is associated until execution of that block ends in any way. (Entering an enclosed block or calling a function suspends, but does not end, execution of the current block.)" C11dr §6.2.4 6 – chux - Reinstate Monica Dec 15 '15 at 15:29
  • @chux: i think you are right, but scope and lifetime are different concepts. An object has a lifetime; an identifier has a scope. Unlike lifetimes, scopes can be discontiguous. – rici Dec 15 '15 at 15:54
  • An example where scope and lifetime are radically different: `void f(void) { static int x = 42; }` – rici Dec 15 '15 at 15:59
  • @rici Agree about scope/lifetime and the [mystical 42](https://en.wikipedia.org/wiki/42_(number)#The_Hitchhiker.27s_Guide_to_the_Galaxy). Post amended. [Comment](http://stackoverflow.com/questions/34292060/how-to-use-compound-literals-to-fprintf-multiple-formatted-numbers-with-arbi/34292061?noredirect=1#comment56327557_34292061) should read "I assert the return value is within its lifetime due to " – chux - Reinstate Monica Dec 15 '15 at 16:15
  • I would have said simply "the return value is still alive because...". But looking more closely at the standard, I think the relevant paragraph in §6.2.4 is paragraph 8, not paragraph 6. Unfortunately, paragraph 8 states "...Any attempt to modify an object with temporary lifetime results in undefined behavior." – rici Dec 15 '15 at 18:46
  • @rici IMO, paragraph 8 doe not apply which begins. "A non-lvalue expression with structure or union type...", yet this answer is not using a structure nor union. Your thoughts? – chux - Reinstate Monica Dec 15 '15 at 18:55
  • 2
    Yes, yes, yes, this answer('s example) shows why we should love compound *literals*! ;-) – alk Dec 15 '15 at 19:07
  • @rici paragraph 8 further does not apply as that is about "A non-lvalue" and this answer's _compound literal_ is an _lvalue_ §6.5.2.5. Certainly the "Any attempt to modify an object with temporary lifetime results in undefined behavior." is due to "non-lvalue-ness". BTW I appreciate the challenge as I has not seen this code style but thought of it myself and am concerned about some hidden UB. – chux - Reinstate Monica Dec 15 '15 at 19:38
  • @chux: excellent point. So it would appear to be safe, except for the slightly esoteric case illustrated by Example 8 (para 15) of 6.5.2.5, which I think requires a loop implemented with a goto and thus should not apply to decent code. – rici Dec 15 '15 at 19:45
  • 1
    @rici It is deliberate. Compound literals do not have "temporary lifetime". 6.2.4/8 refers to structs returned by value from a function (e.g. `foo().x = 5;`) – M.M Dec 15 '15 at 19:58
  • 1
    @m.m.: yeah, with chux's useful observation about compound literals being lvalues, i figured that out. A non-lvalue array literal is thus impossible since functions cannot return arrays. (Also, compound literals cannot be VLAs, so their lifetime can start at the beginning of the block.) – rici Dec 15 '15 at 20:10
  • If feel the last value printed on each line is not in line with the code shown ... `printf("%lu\n", strtoul(TO_BASE(x, 36), NULL, 36));`? – alk Dec 16 '15 at 07:39
  • What is the meaning of the digit `Z` in your base12 output? and `P` ??? Ooops... hahahaha. it's base36!!!! – Luis Colorado Dec 16 '15 at 10:22
  • @alk Luis Colorado The right most numbers were supposed to involve base 36 and to show a successful round-trip. `"base12"` --> `"base36"` – chux - Reinstate Monica Dec 16 '15 at 14:20
  • You don't need the whole array zeroed, just the last byte, so you could maybe remove the `{0}` since you already store a terminating zero at the top of `utoa_base`. – Peter Cordes May 16 '18 at 21:33
  • @PeterCordes Agree about only needing (from an algorithm POV) to zero last byte, yet _compound literals_ require initialization. – chux - Reinstate Monica May 16 '18 at 22:06
  • Unfortunately that doesn't optimize away, even with clang for constant inputs to `test` where it just stores zeros with a vector store, then stores a couple ASCII bytes before calling printf or strtoul. https://godbolt.org/g/2ocZRd. Unfortunately compilers don't CSE between calls to `TO_BASE`, i.e. they don't use the same buffer for printf and strtoul. So if you need the string multiple times, you should definitely use a local array instead of a compound literal. Or to avoid extra vector stores (and extra `vzeroupper` from clang!) – Peter Cordes May 16 '18 at 22:22