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?
CodePudding user response:
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.
CodePudding user response:
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