0

My goal is to print the filenames and not relative path to the filename. I'm experimenting with it using the macro TRACE(). Since it's all in the same file, I'm simulating the filename as an input to TRACE(). So in real life, you could say the inputs are replaced with __FILE__.

Code:

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

#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)

#define __FILENAME__(x) TOSTRING(strrchr(x, '\\'))

#define TRACE(s, ...) \
    { \
    if (strrchr(s, '\\')) { \
        static const char str[] = __FILENAME__(s) "\n\r"; \
        printf(str, ##__VA_ARGS__); \
    } else { \
        static const char str[] = s "\n\r"; \
        printf(str, ##__VA_ARGS__); \
    } \
    }

int main() {
    TRACE("file.c");
    TRACE("parent\\file.c");
    return 0;
}

Output:

file.c
strrchr("parent\\file.c", '\\')

So if it's local file, it's printed as file.c, which is great. This means the ifcase in the macro is working :). But when it's a file in another folder, I fail to "stringify" the computation strrchr(s, '\\'). Why?

Furthermore, I don't see an issue with the computation in the define, since everything is defined at compile time!! (That's why the if case is working, right?)

If I remove the TOSTRING() from __FILENAME__ I get loads of errors instead. Because it fails to concatenate the output of __FILENAME__ with str[]

Is there a way to solve this?

niCk cAMel
  • 869
  • 1
  • 10
  • 26
  • Unrelated to your problem, but all symbols starting with two leading underscores (or one leading underscore followed by an upper-case letter) are reserved in all scopes for "the implementation" (compiler and standard library). You should not use such symbols yourself anywhere. – Some programmer dude May 15 '18 at 11:00
  • @Someprogrammerdude Check! – niCk cAMel May 15 '18 at 11:01
  • 2
    *I don't see an issue with the computation in the define, since everything is defined at compile time* -- No, the correct branch is chosen at runtime. – Ajay Brahmakshatriya May 15 '18 at 11:01
  • `TOSTRING(strrchr(s, '\\'))` is converted as `"strchr(s, '\\')" `. Is this what you want? – Ajay Brahmakshatriya May 15 '18 at 11:04
  • not related - placing brackets one over another reduces the readability and it's considered a bad practice.Instead of placing the macro in a "block" with brackets i would recommend using "do-while". – Martin Chekurov May 15 '18 at 11:06
  • 1
    Note that it is more conventional to use `\r\n` than `\n\r` at the end of a line. – Jonathan Leffler May 15 '18 at 14:21

2 Answers2

2

Preliminary observations

Note that in C (as opposed to C++), you can't initialize a static const char str[] array with the result of a function call. If the strrchr() found a backslash, you probably want to print the name from one after the backslash. And the stringification isn't going to stringify the result of invoking strrchr().

Also note that you should not create function or variable names that start with an underscore, in general. C11 §7.1.3 Reserved identifiers says (in part):

  • All identifiers that begin with an underscore and either an uppercase letter or another underscore are always reserved for any use.
  • All identifiers that begin with an underscore are always reserved for use as identifiers with file scope in both the ordinary and tag name spaces.

See also What does double underscore (__const) mean in C?

Since the first argument to your TRACE macro is already a string, there's not much benefit to applying the stringification — unless you want the double quotes to appear when the name is printed.

Simple adaptation

To get more or less the result you want, you would need to accept that there'll be run-time overhead invoking strrchr() each time you pass the trace (or a more elaborate scheme for initialization), along the lines of:

#define TRACE(s, ...) \
    do { \
        const char *basename = strrchr(s, '\\'); \
        if (basename == 0) \
            basename = s; \
        else \
            basename++; \
        printf(basename, ## __VA_ARGS__); \
    } while (0)

The do { … } while (0) idiom is standard; it allows you to write:

if (something)
    TRACE("hocuspocus.c: test passed\n");
else
    TRACE("abracadabra.c: test failed\n");

If you use the braces-only notation in the question, the semicolon after the first TRACE makes the else into a syntax error. See also C #define macro for debug printing and Why use apparently meaningles do { … } while (0) and if … else statements in macros? and do { … } while (0) — what is it good for?

The ## __VA_ARGS__ trick is fine as long as you know that it is a GCC (and Clang because it is compatible with GCC) extension, and not a part of standard C.

It also isn't entirely clear how you plan to use the variable arguments. It looks as though you'd be able to do:

TRACE("some\\kibbitzer.c: value %d is out of the range [%d..%d]\n",
      value, MIN_RANGE, MAX_RANGE);

where the file name is embedded in the format string. Maybe you have in mind:

TRACE(__FILE__ ": value %d is out of the range [%d..%d]\n",
      value, MIN_RANGE, MAX_RANGE);

That can work; __FILE__ is a string literal, unlike __func__ which is a predefined identifier (static const char __func__[] = "…function name…";).

Finally (for now), consider whether trace output should go to standard output or to standard error. It is easily arguable it should go to standard error; it (probably) isn't part of the regular output of the program.

I recommend looking at the 'debug macro' question and answer — but I am biassed since I wrote the top-scoring answer.

Reducing runtime overhead

You can reduce the runtime overhead to a single call to strrchr() per file name, as long as you aren't messing with automatic variables etc. You'll be OK if you're using string literals.

#define TRACE(s, ...) \
    do { \
        static const char *basename = 0;
        if (basename == 0) \
        {
            if ((basename = strrchr(s, '\\')) == 0) \
                basename = s; \
            else \
                basename++; \
        } \
        printf(basename, ## __VA_ARGS__); \
    } while (0)

This initializes the basename to null; on the first pass through the code, basename is set to the correct position in the string; thereafter, there is no further call to strrchr().

Warning: the code shown has not been compiled.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Excellent answer! Great links. Works! But it's like you say regarding the "run-time overhead". Anyway, accepting since this answers the question, as opposed to "you shouldn't do that"-answers. – niCk cAMel May 15 '18 at 19:57
  • See the addendum for how you can reduce the runtime overhead. – Jonathan Leffler May 15 '18 at 20:16
  • I will, tnx. But preferably I would like to skip runtime overall and have it in compile time like I do when using `__FILE__`. But then you get the entire relative path. It amazes me that `__FILENAME__` isn't built in gcc – niCk cAMel May 15 '18 at 20:20
  • It appears `%` is not [reserved](https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words) as a file name character. Certain to create havoc with OP's `TRACE()`. – chux - Reinstate Monica May 15 '18 at 20:34
  • Not enough people have felt a need for it., where 'enough people' might only need to be one person if they're willing to make and document the change. However, since you're using GCC, you should read the [manual]( https://gcc.gnu.org/onlinedocs/gcc-8.1.0/cpp/Common-Predefined-Macros.html#Common-Predefined-Macros) on common macros. There's a `__BASE_FILE__` macro. _[…time passes…]_ Oh; that's the `__FILE__` for the main source file being compiled, even if you're processing a header or other file at the time. No help. – Jonathan Leffler May 15 '18 at 20:41
1

I think there is some issue with the understanding of how macros and functions work.

Macros are not "executed", they are just simple text substitution. Yes, that happens in the compile time (actually pre compiling), but just the substitution.

Macros won't execute and code or call any functions (like strrchr) while compiling.

In your code you have -

#define __FILENAME__(x) TOSTRING(strrchr(x, '\\'))

Whenever __FILENAME__(foo) is used, it is replaced with "strrchr(foo, '\\')". I am sure this is not what you want.

Personally, I don't see any reason for using macros here. Just make it into a normal function. The compiler will optimize it for you.

Ajay Brahmakshatriya
  • 8,993
  • 3
  • 26
  • 49
  • Thanks for your inputs. But I can't accept this as an answer. – niCk cAMel May 15 '18 at 20:00
  • You might note that the parameter to the macro you quoted is `x`, but the expansion references `s`. That glitch has been fixed in the question since you wrote your answer — you might care to update it to that (because, as written, `__FILENAME__(foo)` is translated to `strrchr(s, '\\')` regardless of what is provided as an argument. – Jonathan Leffler May 18 '18 at 00:29
  • @JonathanLeffler thanks, I will fix that. I had copied the line from the question. Don't know how it slipped my eye. – Ajay Brahmakshatriya May 18 '18 at 05:37