justynnuff
justynnuff

Reputation: 531

Concatenate character to __VA_ARGS__ in a macro redefining printf

I'm developing an embedded application that uses a library for interfacing with a SPI NAND memory chip. The library provided uses a print function in which I, as the applications developer, must override for my specific platform.

For instance, they use the function printf_ extensively in their code.

I redefine it as such, to get the print function to print over the serial interface

#define printf_(...) serial.printf(__VA_ARGS__)

It works fine, except that on this platform, you need to supply a carriage return ("\r") for a newline. So right now, when the library is called, the formatting is horrible.

I'd like to append a "\r" to the end of whatever is supplied to the print function. Like

#define printf_(...) serial.printf(__VA_ARGS__##"\r")

Obviously that doesn't work.

I'm really not familiar with, and have sort of a hard time understanding variadic functions in C, so this would provide me with a little learning opportunity if I could figure this out, or at least I would learn if this is not possible.

I also can't just wrap the function using va_lists and vsprintfs because my platform does not support this (and I'd rather redefine it in a macro, as a performance/stylistic choice, personally).

Is there a way to do this?

Upvotes: 1

Views: 2065

Answers (2)

M.M
M.M

Reputation: 141648

Perhaps you could just output '\r' after the call to serial.printf ?

#define printf_(...) do { serial.printf(__VA_ARGS__); serial.printf("\r"); } while(0)

Upvotes: 2

LTClipp
LTClipp

Reputation: 534

I agree with other users that your requirement of CR coming after LF is quite strange, and I have a strong suspicion you mean for it to be CRLF, not LFCR. Regardless, I will help you with your original requirement of appending CR to the string.

It is going to be incredibly difficult to perform this operation in a one-liner macro. Alternatively, you could use a nasty-looking multi-line macro:

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

#define printf_(format, args...)                                        \
do {                                                                    \
    int _len_orig = strlen(format);                                     \
    /* strlen doesn't consider newline. Check for that. */              \
    if ( format[_len_orig] == '\n' )                                    \
        _len_orig++;                                                    \
                                                                        \
    /* len_orig + 2 to make space for null terminator and '\r' */       \
    unsigned _new_size = _len_orig + 2;                                 \
    char* _new_str  = calloc(_new_size, 1);                             \
    strncpy( _new_str, format, _len_orig );                             \
    _new_str[_new_size-2] = '\r';                                       \
                                                                        \
    /* Print out the raw hex of the string to prove it worked */        \
    int _i;                                                             \
    for( _i = 0; _i < _new_size; _i++)                                  \
        printf("%02x ", _new_str[_i]);                                  \
    printf("\n");                                                       \
    free(_new_str);                                                     \
} while(0)


int main(){

    char* str = "@@@@\n";
    printf_( str );

    return 0;
}

In my macro, I have not used your serial.printf function but rather made some short code that prints out the hexidecimal values of the concatenated string for proof that the logic works.

Explanation

  • Macros are required to appear as a single line to the C Preprocessor. The additions of \ at the end of each macro line escape the newline character.

  • gcc allows for named arguments in your macro. Here, format describes the format string you would normally pass to printf, and args... is the variable argument name for "everything else" you would pass into printf. I do not reference args in this example, but you will certainly want to modify my example in your final code to include it. Your call to printf would look something like serial.printf( _new_str, args);

  • Multi-line macros are kind of nasty in that you need to do some trickery to get them to appear as normal functions to the human programmer. The need for enclosing the macro in a do-while is nicely discussed here. The linked page is meant for C++, but it applies to C as well.

If you copy my code, compile, and run it, this is the output:

[clipp@h2ologin3:~]$ ./a.out
40 40 40 40 0a 0d 00

Here, this ASCII dump is showing us that 4 @ symbols were copied, followed by \n, \r, and finally \0.

There is also a strong case for instead writing this as an inline C function. You say that your system doesn't support va_list etc. so if that is true, then a macro may be the only choice.

QED.

Upvotes: 0

Related Questions