How to use compound literals to `fprintf()` multiple formatted numbers with arbitrary bases?
Asked Answered
S

1

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?

Sendal answered 15/12, 2015 at 14:42 Comment(4)
Why C11 only? It would apply/be solvable to/with C99 as well.Lightface
do you mean you want to use %s instead of %d or whatever ?Mastrianni
@Mastrianni Use "%s", or "%d" or whatever. I would not want to stifle novel ideas.Sendal
@Lightface Q & A amended per your correct observations.Sendal
S
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.

Sendal answered 15/12, 2015 at 14:42 Comment(17)
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 6Sendal
@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.Handspike
An example where scope and lifetime are radically different: void f(void) { static int x = 42; }Handspike
@Handspike Agree about scope/lifetime and the mystical 42. Post amended. Comment should read "I assert the return value is within its lifetime due to "Sendal
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."Handspike
@Handspike 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?Sendal
Yes, yes, yes, this answer('s example) shows why we should love compound literals! ;-)Lightface
@Handspike 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.Sendal
@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.Handspike
@Handspike 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;)Mastrianni
@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.)Handspike
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));?Lightface
What is the meaning of the digit Z in your base12 output? and P ??? Ooops... hahahaha. it's base36!!!!Shopworn
@Lightface Luis Colorado The right most numbers were supposed to involve base 36 and to show a successful round-trip. "base12" --> "base36"Sendal
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.Nahshu
@PeterCordes Agree about only needing (from an algorithm POV) to zero last byte, yet compound literals require initialization.Sendal
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. 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!)Nahshu

© 2022 - 2024 — McMap. All rights reserved.