简体   繁体   English

__FILE__ 编译时的宏操作处理

[英]__FILE__ macro manipulation handling at compile time

One of the issues I have had in porting some stuff from Solaris to Linux is that the Solaris compiler expands the macro __FILE__ during preprocessing to the file name (eg MyFile.cpp) whereas gcc on Linux expandeds out to the full path (eg /home/user/MyFile.cpp).我在将一些东西从 Solaris 移植到 Linux 时遇到的问题之一是 Solaris 编译器在预处理期间将宏__FILE__扩展为文件名(例如 MyFile.cpp),而 Linux 上的 gcc 扩展到完整路径(例如 /home /user/MyFile.cpp)。 This can be reasonably easily resolved using basename() but....if you're using it a lot, then all those calls to basename() have got to add up, right?使用 basename() 可以很容易地解决这个问题,但是....如果你经常使用它,那么所有对 basename() 的调用都必须加起来,对吧?

Here's the question.这是问题。 Is there a way using templates and static metaprogramming, to run basename() or similar at compile time?有没有办法使用模板和静态元编程,在编译时运行 basename() 或类似的? Since __FILE__ is constant and known at compile time this might make it easier.由于__FILE__是常量并且在编译时已知,这可能会使它更容易。 What do you think?你怎么认为? Can it be done?可以做到吗?

In projects using CMake to drive the build process, you can use a macro like this to implement a portable version that works on any compiler or platform.在使用 CMake 驱动构建过程的项目中,您可以使用这样的宏来实现可在任何编译器或平台上运行的可移植版本。 Though personally I pity the fool who must use something other than gcc... :)虽然我个人很同情那个必须使用 gcc 以外的东西的傻瓜...... :)

# Helper function to add preprocesor definition of FILE_BASENAME
# to pass the filename without directory path for debugging use.
#
# Note that in header files this is not consistent with
# __FILE__ and __LINE__ since FILE_BASENAME will be the
# compilation unit source file name (.c/.cpp).
#
# Example:
#
#   define_file_basename_for_sources(my_target)
#
# Will add -DFILE_BASENAME="filename" for each source file depended on
# by my_target, where filename is the name of the file.
#
function(define_file_basename_for_sources targetname)
    get_target_property(source_files "${targetname}" SOURCES)
    foreach(sourcefile ${source_files})
        # Add the FILE_BASENAME=filename compile definition to the list.
        get_filename_component(basename "${sourcefile}" NAME)
        # Set the updated compile definitions on the source file.
        set_property(
            SOURCE "${sourcefile}" APPEND
            PROPERTY COMPILE_DEFINITIONS "FILE_BASENAME=\"${basename}\"")
    endforeach()
endfunction()

Then to use the macro, just call it with the name of the CMake target:然后要使用宏,只需使用 CMake 目标的名称调用它:

define_file_basename_for_sources(myapplication)

Using C++11, you have a couple of options.使用 C++11,您有几个选择。 Let's first define:我们首先定义:

constexpr int32_t basename_index (const char * const path, const int32_t index = 0, const int32_t slash_index = -1)
{
     return path [index]
         ? ( path [index] == '/'
             ? basename_index (path, index + 1, index)
             : basename_index (path, index + 1, slash_index)
           )
         : (slash_index + 1)
     ;
}

If your compiler supports statement expressions, and you want to be sure that the basename computation is being done at compile-time, you can do this:如果您的编译器支持语句表达式,并且您想确保在编译时完成 basename 计算,您可以这样做:

// stmt-expr version
#define STRINGIZE_DETAIL(x) #x
#define STRINGIZE(x) STRINGIZE_DETAIL(x)

#define __FILELINE__ ({ static const int32_t basename_idx = basename_index(__FILE__);\
                        static_assert (basename_idx >= 0, "compile-time basename");  \
                        __FILE__ ":" STRINGIZE(__LINE__) ": " + basename_idx;})

If your compiler doesn't support statement expressions, you can use this version:如果你的编译器不支持语句表达式,你可以使用这个版本:

// non stmt-expr version
#define __FILELINE__ (__FILE__ ":" STRINGIZE(__LINE__) ": " + basename_index(__FILE__))

With this non stmt-expr version, gcc 4.7 and 4.8 call basename_index at run-time, so you're better off using the stmt-expr version with gcc.使用此非 stmt-expr 版本,gcc 4.7 和 4.8 在运行时调用 basename_index,因此最好将 stmt-expr 版本与 gcc 一起使用。 ICC 14 produces optimal code for both versions. ICC 14 为两个版本生成最佳代码。 ICC13 can't compile the stmt-expr version, and produces suboptimal code for the non stmt-expr version. ICC13 无法编译 stmt-expr 版本,并为非 stmt-expr 版本生成次优代码。

Just for completeness, here's the code all in one place:为了完整起见,以下是所有代码:

#include <iostream>
#include <stdint.h>

constexpr int32_t basename_index (const char * const path, const int32_t index = 0, const int32_t slash_index = -1)
{
   return path [index]
       ? ( path [index] == '/'
           ? basename_index (path, index + 1, index)
           : basename_index (path, index + 1, slash_index)
           )
       : (slash_index + 1)
       ;
}

#define STRINGIZE_DETAIL(x) #x
#define STRINGIZE(x) STRINGIZE_DETAIL(x)

#define __FILELINE__ ({ static const int32_t basename_idx = basename_index(__FILE__); \
                        static_assert (basename_idx >= 0, "compile-time basename");   \
                        __FILE__ ":" STRINGIZE(__LINE__) ": " + basename_idx;})


int main() {
  std::cout << __FILELINE__ << "It works" << std::endl;
}

There is currently no way of doing full string processing at compile time (the maximum we can work with in templates are the weird four-character-literals).目前没有办法在编译时进行完整的字符串处理(我们可以在模板中使用的最大值是奇怪的四字符文字)。

Why not simply save the processed name statically, eg:为什么不简单地静态保存处理过的名称,例如:

namespace 
{
  const std::string& thisFile() 
  {
      static const std::string s(prepocessFileName(__FILE__));
      return s;
  }
}

This way you are only doing the work once per file.这样你每个文件只做一次工作。 Of course you can also wrap this into a macro etc.当然你也可以把它包装成一个宏等。

you might want to try the __BASE_FILE__ macro.您可能想尝试__BASE_FILE__宏。 This page describes a lot of macros which gcc supports.这个页面描述了很多 gcc 支持的宏。

Another C++11 constexpr method is as follows:另一种C++11 constexpr方法如下:

constexpr const char * const strend(const char * const str) {
    return *str ? strend(str + 1) : str;
}

constexpr const char * const fromlastslash(const char * const start, const char * const end) {
    return (end >= start && *end != '/' && *end != '\\') ? fromlastslash(start, end - 1) : (end + 1);
}

constexpr const char * const pathlast(const char * const path) {
    return fromlastslash(path, strend(path));
}

The usage is pretty simple also:用法也很简单:

std::cout << pathlast(__FILE__) << "\n";

The constexpr will be performed at compile-time if possible, otherwise it will fallback to run-time execution of the statements.如果可能, constexpr将在编译时执行,否则它将回退到语句的运行时执行。

The algorithm is a little different in that it finds the end of the string and then works backwards to find the last slash.该算法略有不同,因为它找到字符串的结尾,然后向后工作以找到最后一个斜杠。 It is probably slower than the other answer but since it is intended to be executed at compile-time it shouldn't be an issue.它可能比其他答案慢,但由于它打算在编译时执行,因此不应该成为问题。

I like @Chetan Reddy's answer , which suggests using static_assert() in a statement expression to force a compile time call to function finding the last slash, thus avoiding runtime overhead.我喜欢@Chetan Reddy 的回答,它建议在语句表达式中使用static_assert()强制编译时调用查找最后一个斜杠的函数,从而避免运行时开销。

However, statement expressions are a non-standard extension and are not universally supported.但是,语句表达式是一种非标准的扩展,并没有得到普遍支持。 For instance, I was unable to compile the code from that answer under Visual Studio 2017 (MSVC++ 14.1, I believe).例如,我无法在 Visual Studio 2017(我相信是 MSVC++ 14.1)下编译该答案中的代码。

Instead, why not use a template with integer parameter, such as:相反,为什么不使用带有整数参数的模板,例如:

template <int Value>
struct require_at_compile_time
{
    static constexpr const int value = Value;
};

Having defined such a template, we can use it with basename_index() function from @Chetan Reddy's answer:定义了这样的模板后,我们可以将它与 @Chetan Reddy 的回答中的basename_index()函数一起使用:

require_at_compile_time<basename_index(__FILE__)>::value

This ensures that basename_index(__FILE__) will in fact be called at compile time, since that's when the template argument must be known.这确保了basename_index(__FILE__)实际上会在编译时被调用,因为那时必须知道模板参数。

With this, the complete code for, let's call it JUST_FILENAME , macro, evaluating to just the filename component of __FILE__ would look like this:有了这个,完整的代码,让我们称之为JUST_FILENAME ,宏,评估__FILE__的文件名组件将如下所示:

constexpr int32_t basename_index (
    const char * const path, const int32_t index = 0, const int32_t slash_index = -1
)
{
     return path [index]
         ? ((path[index] == '/' || path[index] == '\\')  // (see below)
             ? basename_index (path, index + 1, index)
             : basename_index (path, index + 1, slash_index)
           )
         : (slash_index + 1)
     ;
}

template <int32_t Value>
struct require_at_compile_time
{
    static constexpr const int32_t value = Value;
};

#define JUST_FILENAME (__FILE__ + require_at_compile_time<basename_index(__FILE__)>::value)

I've stolen basename_index() almost verbatim from the previously mentioned answer , except I added a check for Windows-specific backslash separator.我从前面提到的答案中几乎逐字地窃取了basename_index() ,除了我添加了对 Windows 特定反斜杠分隔符的检查。

Another possible approach when using CMake is to add a custom preprocessor definition that directly uses make 's automatic variables (at the cost of some arguably ugly escaping):使用 CMake 时的另一种可能方法是添加直接使用make自动变量的自定义预处理器定义(以一些可以说是丑陋的转义为代价):

add_definitions(-D__FILENAME__=\\"$\(<F\)\\")

Or, if you're using CMake >= 2.6.0:或者,如果您使用 CMake >= 2.6.0:

cmake_policy(PUSH)
cmake_policy(SET CMP0005 OLD) # Temporarily disable new-style escaping.
add_definitions(-D__FILENAME__=\\"$\(<F\)\\")
cmake_policy(POP)

(Otherwise CMake will over-escape things .) (否则 CMake会过度逃避。)

Here, we take advantage of the fact make substitutes $(<F) with the source file name without leading components and this should show up as -D__FILENAME__=\\"MyFile.cpp\\" in the executed compiler command.在这里,我们利用make用源文件名替换$(<F)而没有前导组件这一-D__FILENAME__=\\"MyFile.cpp\\" ,这应该在执行的编译器命令中显示为-D__FILENAME__=\\"MyFile.cpp\\"

(While make 's documentation recommends using $(notdir path $<) instead, not having whitespace in the added definition seems to please CMake better.) (虽然make的文档建议使用$(notdir path $<)代替,但在添加的定义中没有空格似乎更能取悦 CMake。)

You can then use __FILENAME__ in your source code like you'd use __FILE__ .然后,您可以在源代码中使用__FILENAME__ ,就像使用__FILE__ For compatibility purposes you may want to add a safe fallback:出于兼容性目的,您可能需要添加安全回退:

#ifndef __FILENAME__
#define __FILENAME__ __FILE__
#endif

For Objective-C the following macro provides a CString which can replace the __FILE__ macro, but omitting the initial path components.对于 Objective-C,以下宏提供了一个 CString,它可以替换__FILE__宏,但省略了初始路径组件。

#define __BASENAME__ [[[NSString stringWithCString:__FILE__              \
                                        encoding:NSUTF8StringEncoding]   \
                                                    lastPathComponent]   \
                            cStringUsingEncoding:NSUTF8StringEncoding]   

That is to say it converts: /path/to/source/sourcefile.m into: sourcefile.m也就是说它把: /path/to/source/sourcefile.m转换成: sourcefile.m

It works by taking the ouptput of the __FILE__ macro (which is a C-formatted, null terminated string), converting it to an Objective-C string object, then stripping out the initial path components and finally converting it back into a C formatted string.它的工作原理是获取__FILE__宏的输出(它是一个 C 格式的空终止字符串),将其转换为 Objective-C 字符串对象,然后剥离初始路径组件,最后将其转换回 C 格式的字符串.

This is useful to get logging format which is more readable, replacing, (for example) a logging macro like this:这对于获取更具可读性的日志格式很有用,可以替换(例如)这样的日志宏:

#define MyLog(fmt, ...) MyLog((@"E %s [Line %d] " fmt),                \
                               __FILE__, __LINE__, ##__VA_ARGS__)

with:和:

#define __BASENAME__ [[[NSString stringWithCString:__FILE__            \
                                        encoding:NSUTF8StringEncoding] \
                                                    lastPathComponent] \
                            cStringUsingEncoding:NSUTF8StringEncoding]

#define MyLog(fmt, ...) MyLog((@"E %s [Line %d] " fmt),                \
                               __BASENAME__, __LINE__, ##__VA_ARGS__)

It does contain some runtime elements, and in that sense does not fully comply with the question, but it is probably appropriate for most circumstances.它确实包含一些运行时元素,从这个意义上说并不完全符合问题,但它可能适用于大多数情况。

I've compressed the constexpr version down to one recursive function that finds the last slash and returns a pointer to the character after the slash.我已将constexpr版本压缩为一个递归函数,该函数找到最后一个斜杠并返回一个指向斜杠后字符的指针。 Compile time fun.编译时间很有趣。

constexpr const char* const fileFromPath(const char* const str, const char* const lastslash = nullptr) {
    return *str ? fileFromPath(str + 1, ((*str == '/' || *str == '\\') ? str + 1 : (nullptr==lastslash?str:lastslash)) : (nullptr==lastslash?str:lastslash);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM