0

I am trying to write a preprocessor macro MYPRINTF(x, ...) that should work as printf but with an additional length specifier w. The definition of w is supposed to be operating system dependent, but on Linux it is equivalent to l.

The code works fine, but I'm having problems with evaluating expressions during compile time when x is a constant expression. As an example, if I write

long int x = 13;
MYPRINTF("%wd\n", x);

on a Linux system, I would like the compiler to convert it during compile time to

long int x = 13;
printf("%ld\n", x);

Looking at the assembly code, I can say that this is not what is happening.

Here is the code that I'm running

#include <stdio.h>

#define SET_CHAR(ptr, cr)                       \
    do                                          \
    {                                           \
        *(ptr) = (cr);                          \
        (ptr)++;                                \
    } while (0)

#define SET_WORD_FMT(x)                         \
    do                                          \
    {                                           \
        SET_CHAR(x, 'l');                       \
    } while (0)

#define IS_FORMAT(c) ((c) == 'c' || (c) == 's' || (c) == 'd' || (c) == 'i' || (c) == 'o' || (c) == 'x' || (c) == 'X' || (c) == 'u' || (c) == 'f' || (c) == 'F' || (c) == 'e' || (c) == 'E' || (c) == 'a' || (c) == 'A' || (c) == 'g' || (c) == 'G' || (c) == 'n' || (c) == 'p')

#define MYPRINTF(x, ...)                        \
    do                                          \
    {                                           \
        char _str[512];                         \
        char * _strptr = _str;                  \
        const char * _xptr = (x);               \
        while (*_xptr != '\0')                  \
        {                                       \
            if (*_xptr != '%')                  \
            {                                   \
                SET_CHAR(_strptr, *_xptr);      \
                _xptr++;                        \
                continue;                       \
            }                                   \
                                                \
            SET_CHAR(_strptr, '%');             \
            _xptr++;                            \
                                                \
            if (*_xptr == '%')                  \
            {                                   \
                SET_CHAR(_strptr, '%');         \
                _xptr++;                        \
                continue;                       \
            }                                   \
            else while (!IS_FORMAT(*_xptr))     \
            {                                   \
                SET_CHAR(_strptr, *_xptr);      \
                _xptr++;                        \
            }                                   \
                                                \
            if (_strptr[-1] == 'w')             \
            {                                   \
                _strptr--;                      \
                SET_WORD_FMT(_strptr);          \
            }                                   \
                                                \
            SET_CHAR(_strptr, *_xptr);          \
            _xptr++;                            \
        }                                       \
        *_strptr = '\0';                        \
        printf(_str, __VA_ARGS__);              \
    } while (0)

int
main()
{
    long int slx = 18273817628731872;
    int x = 13;
    int lx = 7128172;
    long long int llx = 1928398172938791872;

    MYPRINTF("hello %wd, %d, %ld, %% and %lld\n", slx, x, lx, llx);
}

which is compiled using GCC 12.1.0 with flags -O3 -march=native.

Is it possible to evaluate this during compile time? If so, how?

aahlback
  • 82
  • 1
  • 5
  • The usual way is `printf("%" w "d\n", x);` and have `#define w "l" /* or whatever */`. BTW: I suggest you use something other than `w` :-) ... see [``](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/inttypes.h.html) – pmg Jun 10 '22 at 15:26
  • What @pmg is suggesting is much the same as the way the "standard" library implements its format specifiers for the fixed-width integer types. See https://stackoverflow.com/q/45922817/10871073 for example. – Adrian Mole Jun 10 '22 at 15:29
  • @pmg Thanks for your answer. I'm already aware of this, and this would be my preferred solution. However, as this is intended to replace a function within a library, I cannot do this. – aahlback Jun 10 '22 at 15:31
  • An array variable is not a constant expression. A compiler is not obligated to compute it at compile time. `clang` sometimes can do this [example](https://godbolt.org/z/oMo6qzn17) but not when your calculations are as complex as in your macro. `gcc` doesn't seem to do it at all. – n. m. could be an AI Jun 10 '22 at 16:52
  • If you really need this to be done at compile time, your best bet is probably a source level transformation (replace `...%w...` with `... %" WIDE "...` in a dedicated build step). I don't think it makes a lot of sense though. Just do it at run time. Code that doesn't want to follow best practices should suffer, how else can we teach it to behave? – n. m. could be an AI Jun 10 '22 at 17:04

2 Answers2

2

Is it possible to evaluate this during compile time?

No, it is not possible.

For example, writing of a constant-expression strlen for strings up to 62 is very memory consuming and your compiler can easily hit gigabytes of memory (mostly, because the generated preprocessor code takes so much). Writing a constant-expression string parsing function for every possible case is just not feasible and not compilable in practice.

If you want such functionality, move to a different programming language or run a preprocessor through your code. For example, preprocess your code with M4 preprocessor.

// run with m4 -P
m4_define(`MYPRINTF', `printf(m4_patsubst(`$1', `%wd', `%ld'), m4_shift($@))')
MYPRINTF("%wd", a)   // expands to printf("%ld", a)

that should work as printf but with an additional length specifier w

With glibc you can redefine %d conversion and add w flag. https://www.gnu.org/software/libc/manual/html_node/Registering-New-Conversions.html

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
1

The way to do this is to define a separate macro with the length specifier in an OS dependent way. Then you can use that where you want.

#ifdef __linux__
#define WIDE "l"
#else
#define WIDE "L"
#endif

...

printf("%" WIDE "d\n", x);

Note that this takes advantage of automatic combination of consecutive string literals.

dbush
  • 205,898
  • 23
  • 218
  • 273
  • Thanks. However, this is not accepted in my situation. I'm only looking for a way to convert a constant string during compile time. – aahlback Jun 10 '22 at 15:34
  • 1
    @aahlback Not possible. C++ can do it, I think. – user253751 Jun 10 '22 at 15:36
  • @user253751 Do you have a source for this? GCC can evaluate other things during compile time, why can't it do it for this case? – aahlback Jun 10 '22 at 15:43
  • 1
    @aahlback You're looking at the problem the wrong way. If all you want is a custom width specifier, the above is the way to do it. – dbush Jun 10 '22 at 15:47
  • @aahlback The preprocessor doesn't perform substitutions inside of string literals. So you can't use the preprocessor to modify string literals. You need to write your own preprocessor to do that. – user3386109 Jun 10 '22 at 15:47
  • As mentioned in another comment, this would be my preferred solution. However, the macro I'm writing is supposed to be a replacement for a function in a library where I'm not able to write it this way for backwards compatibility reasons. – aahlback Jun 10 '22 at 15:52
  • @user3386109 I'm creating another string and then I'm copying the content from the string literal; I'm not modifying the string literal. – aahlback Jun 10 '22 at 15:59
  • @aahlback You seem to be trying to use the C++ `constexpr` feature. There is no such feature in C. – user3386109 Jun 10 '22 at 16:05
  • Re "*where I'm not able to write it this way for backwards compatibility reasons*", Then you will need to do it at run time (like in the Question). – ikegami Jun 10 '22 at 16:19