Search code examples
c++macroscharstdstring

Proper string/char casting in C++ macro


I've inherited some code that causes an amazing amount of compiler warnings and am not a strong enough C++ programmer to figure out how to address it properly. To wit:

log.h:

#include <stdarg.h>
#include <libgen.h>
#include <errno.h>
#include <string.h>

void log(const char* fmt, ...) __attribute__((format (printf, 1, 2)));
#define error(fmt, ...) ow_log("ERROR[%s:%s()@%d]: " fmt, basename(__FILE__), __func__, __LINE__, ##__VA_ARGS__)
#define sys_error(fmt, ...) ow_log("ERROR[%s:%s()@%d]: System error: %s:" fmt, basename(__FILE__), __func__, __LINE__, strerror(errno), ##__VA_ARGS__)

log.cpp

#include <stdarg.h>
#include <stdio.h>
#include <pthread.h>

void log(const char* fmt, ...)
{
    time_t t = time(NULL);
    struct tm tm = *localtime(&t);

    printf("%04d-%02d-%02d %02d:%02d:%02d ", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
    va_list args;
    va_start(args, fmt);
    vprintf(fmt, args);
    printf("\n");
    fflush(stdout);
    va_end(args);
}

Example use:

int args = 6;
log("Some string not explicitly typed with %i interpolated arguments", args);
error("Something went wrong here");

C++ programmers probably immediately see it, but I can't. What I get whenever this is used is:

log.h:11:79: warning: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]
#define error(fmt, ...) log("ERROR[%s:%s()@%d]: " fmt, basename(__FILE__), __func__, __LINE__, ##__VA_ARGS__)
                                                                               ^

It looks like the code has been this way for some time, and I don't see any terribly bad ill effects, but it makes reading the compiler output quite difficult (and seems actually wrong), so I tried to fix it. I tried some different casting strategies but nothing seems to work, I think I'm misunderstanding how macros work and all the builtins that it uses.


Solution

  • According to this page the prototype for basename() is

    char *basename(char *path); 
    

    and the documentation says:

    The basename() function may modify the string pointed to by path, and may return a pointer to static storage that may then be overwritten by a subsequent call to basename().

    Therefore you should not pass a string literal to this function.

    The function you should be using is

    char * basename_r(const char *path, char *bname);
    

    where bname is scratch storage for the function to use.