Cで利用可能な標準の事前定義済みMACRO __FILE__は、ファイルへのフルパスを示しています。パスを短くする方法はありますか?代わりに
/full/path/to/file.c
そうですか
to/file.c
または
file.c
試してみる
#include <string.h>
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
Windowsでは、「/」の代わりに「\\」を使用します。
Cmakeを使用している場合のヒントを次に示します。 From: http://public.kitware.com/pipermail/cmake/2013-January/053117.html
ヒントをコピーしているので、すべてこのページにあります。
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__FILENAME__='\"$(subst
${CMAKE_SOURCE_DIR}/,,$(abspath $<))\"'")
GNU makeを使用している場合、これを独自のmakefileに拡張できなかった理由はありません。たとえば、次のような行があるとします。
CXX_FLAGS+=-D__FILENAME__='\"$(subst $(SOURCE_PREFIX)/,,$(abspath $<))\"'"
ここで、$(SOURCE_PREFIX)
は削除するプレフィックスです。
次に、__FILENAME__
の代わりに__FILE__
を使用します。
ソースファイルとヘッダーファイルの両方で機能し、非常に効率的で、コンパイラ固有の拡張機能を持たないすべてのプラットフォームでコンパイル時に機能する、これに対する優れたソリューションを考えました。このソリューションでは、プロジェクトの相対ディレクトリ構造も保持されるため、ファイルがどのフォルダーにあり、プロジェクトのルートに対してのみ相対的であるかがわかります。
アイデアは、ビルドツールでソースディレクトリのサイズを取得し、それを__FILE__
マクロに追加して、ディレクトリを完全に削除し、ソースディレクトリから始まるファイル名のみを表示することです。
次の例はCMakeを使用して実装されていますが、他のビルドツールでは機能しない理由はありません。トリックは非常に簡単だからです。
CMakeLists.txtファイルで、CMakeのプロジェクトへのパスの長さを持つマクロを定義します。
# The additional / is important to remove the last character from the path.
# Note that it does not matter if the OS uses / or \, because we are only
# saving the path size.
string(LENGTH "${CMAKE_SOURCE_DIR}/" SOURCE_PATH_SIZE)
add_definitions("-DSOURCE_PATH_SIZE=${SOURCE_PATH_SIZE}")
ソースコードで、ソースパスサイズを__FILENAME__
マクロに追加するだけの__FILE__
マクロを定義します。
#define __FILENAME__ (__FILE__ + SOURCE_PATH_SIZE)
次に、__FILE__
マクロの代わりにこの新しいマクロを使用します。 __FILE__
パスは常にCMakeソースディレクトリへのパスで始まるため、これは機能します。 __FILE__
文字列から削除することにより、プリプロセッサは正しいファイル名の指定を処理し、CMakeプロジェクトのルートに対して相対的な名前になります。
パフォーマンスを気にする場合、これは__FILE__
と__FILE__
の両方が既知のコンパイル時定数であり、コンパイラーによって最適化できるため、SOURCE_PATH_SIZE
を使用するのと同じくらい効率的です。
これが失敗する唯一の場所は、生成されたファイルでこれを使用していて、それらがオフソースのビルドフォルダーにある場合です。次に、おそらくCMAKE_BUILD_DIR
の代わりにCMAKE_SOURCE_DIR
変数を使用して別のマクロを作成する必要があります。
ここで純粋にコンパイル時のソリューション。これは、文字列リテラルのsizeof()
がlength + 1を返すという事実に基づいています。
#define STRIPPATH(s)\
(sizeof(s) > 2 && (s)[sizeof(s)-2] == '/' ? (s) + sizeof(s) - 1 : \
sizeof(s) > 3 && (s)[sizeof(s)-3] == '/' ? (s) + sizeof(s) - 2 : \
sizeof(s) > 4 && (s)[sizeof(s)-4] == '/' ? (s) + sizeof(s) - 3 : \
sizeof(s) > 5 && (s)[sizeof(s)-5] == '/' ? (s) + sizeof(s) - 4 : \
sizeof(s) > 6 && (s)[sizeof(s)-6] == '/' ? (s) + sizeof(s) - 5 : \
sizeof(s) > 7 && (s)[sizeof(s)-7] == '/' ? (s) + sizeof(s) - 6 : \
sizeof(s) > 8 && (s)[sizeof(s)-8] == '/' ? (s) + sizeof(s) - 7 : \
sizeof(s) > 9 && (s)[sizeof(s)-9] == '/' ? (s) + sizeof(s) - 8 : \
sizeof(s) > 10 && (s)[sizeof(s)-10] == '/' ? (s) + sizeof(s) - 9 : \
sizeof(s) > 11 && (s)[sizeof(s)-11] == '/' ? (s) + sizeof(s) - 10 : (s))
#define __JUSTFILE__ STRIPPATH(__FILE__)
条件演算子カスケードをプロジェクト内の意味のある最大ファイル名まで自由に拡張してください。文字列の最後から十分にチェックする限り、パスの長さは重要ではありません。
マクロの再帰を使用して、ハードコーディングされた長さのない同様のマクロを取得できるかどうかを確認します...
少なくともgccの場合、__FILE__
の値は、ファイルパスコンパイラのコマンドラインで指定です。次のようにfile.c
をコンパイルする場合:
gcc -c /full/path/to/file.c
__FILE__
は"/full/path/to/file.c"
に展開されます。代わりにこれを行う場合:
cd /full/path/to
gcc -c file.c
__FILE__
は"file.c"
に展開されます。
これは実際的な場合とそうでない場合があります。
C標準では、この動作は必要ありません。 __FILE__
について述べているのは、「現在のソースファイルの推定名(文字列リテラル)」に展開することだけです。
別の方法は、#line
ディレクティブを使用することです。現在の行番号、およびオプションでソースファイル名を上書きします。ファイル名を上書きしたいが、行番号をそのままにしたい場合は、__LINE__
マクロを使用します。
たとえば、これをfile.c
の上部近くに追加できます。
#line __LINE__ "file.c"
これに関する唯一の問題は、指定された行番号をfollowing行に割り当て、#line
への最初の引数がdigit-sequenceでなければならないことです。のようなことをする
#line (__LINE__-1) "file.c" // This is invalid
#line
ディレクティブのファイル名がファイルの実際の名前と一致することを確認することは、演習として残しておきます。
少なくともgccの場合、これは診断メッセージで報告されるファイル名にも影響します。
GNUコンパイラでCMAKE
を使用している場合、このglobal
定義は正常に機能します。
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__MY_FILE__='\"$(notdir $(abspath $<))\"'")
basename() 関数を使用するか、Windowsの場合は _ splitpath() を使用します。
#include <libgen.h>
#define PRINTFILE() { char buf[] = __FILE__; printf("Filename: %s\n", basename(buf)); }
また、シェルでman 3 basename
を試してください。
vsと、/ FCを使用する場合、FILEは、/ FCを使用しないフルパスと等しいFILEはファイル名と同じです。 ref ここ
これを行うコンパイル時の方法はありません。明らかに、他のいくつかの答えが示したように、実行時にCランタイムを使用してそれを行うことができますが、コンパイル時に、プリプロセッサが起動すると、運が悪くなります。
@ red1ynxが提案したもののわずかなバリエーションは、次のマクロを作成することです。
#define SET_THIS_FILE_NAME() \
static const char* const THIS_FILE_NAME = \
strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__;
各.c(pp)ファイルに次を追加します。
SET_THIS_FILE_NAME();
その後、THIS_FILE_NAME
の代わりに__FILE__
を参照できます。
printf("%s\n", THIS_FILE_NAME);
これは、マクロが参照されるたびにではなく、.c(pp)ファイルごとに1回構築が実行されることを意味します。
.c(pp)ファイルからの使用のみに制限されており、ヘッダーファイルからは使用できません。
msvc2015u3、gcc5.4、clang3.8.0
template <size_t S>
inline constexpr size_t get_file_name_offset(const char (& str)[S], size_t i = S - 1)
{
return (str[i] == '/' || str[i] == '\\') ? i + 1 : (i > 0 ? get_file_name_offset(str, i - 1) : 0);
}
template <size_t S>
inline constexpr size_t get_file_name_offset(const wchar_t (& str)[S], size_t i = S - 1)
{
return (str[i] == L'/' || str[i] == L'\\') ? i + 1 : (i > 0 ? get_file_name_offset(str, i - 1) : 0);
}
template <size_t S>
inline constexpr size_t get_file_name_offset(const char16_t (& str)[S], size_t i = S - 1)
{
return (str[i] == u'/' || str[i] == u'\\') ? i + 1 : (i > 0 ? get_file_name_offset(str, i - 1) : 0);
}
template <size_t S>
inline constexpr size_t get_file_name_offset(const char32_t (& str)[S], size_t i = S - 1)
{
return (str[i] == U'/' || str[i] == U'\\') ? i + 1 : (i > 0 ? get_file_name_offset(str, i - 1) : 0);
}
template <typename T>
inline constexpr size_t get_file_name_offset(T (& str)[1])
{
return 0;
}
'
int main()
{
printf("%s\n", &__FILE__[get_file_name_offset(__FILE__)]);
}
コードは、次の場合にコンパイル時オフセットを生成します。
gcc
:少なくともgcc6.1 + -O1
msvc
:結果をconstexpr変数に入れます:
constexpr auto file = &__FILE__[get_file_name_offset(__FILE__)];
printf("%s\n", file);
clang
:コンパイル時でない評価で持続します
最適化を無効にしたデバッグ構成でも、3つのコンパイラすべてでコンパイル時評価を強制するトリックがあります。
namespace utility {
template <typename T, T v>
struct const_expr_value
{
static constexpr const T value = v;
};
}
#define UTILITY_CONST_EXPR_VALUE(exp) ::utility::const_expr_value<decltype(exp), exp>::value
int main()
{
printf("%s\n", &__FILE__[UTILITY_CONST_EXPR_VALUE(get_file_name_offset(__FILE__))]);
}
毎回完全なパスをカットしないマクロ__FILENAME__
を作成しました。問題は、結果のファイル名をcpp-local変数に保持することです。
。hファイルで静的なグローバル変数を定義することで簡単に行えます。この定義では、。hを含む。cppファイルごとに個別の独立した変数を指定します。マルチスレッドに対応するには、変数もスレッドローカル(TLS)にする価値があります。
1つの変数には、ファイル名(縮小)が格納されます。別のものは、__FILE__
が与えた非カット値を保持します。 hファイル:
static __declspec( thread ) const char* fileAndThreadLocal_strFilePath = NULL;
static __declspec( thread ) const char* fileAndThreadLocal_strFileName = NULL;
マクロ自体は、すべてのロジックでメソッドを呼び出します。
#define __FILENAME__ \
GetSourceFileName(__FILE__, fileAndThreadLocal_strFilePath, fileAndThreadLocal_strFileName)
そして、関数は次のように実装されます:
const char* GetSourceFileName(const char* strFilePath,
const char*& rstrFilePathHolder,
const char*& rstrFileNameHolder)
{
if(strFilePath != rstrFilePathHolder)
{
//
// This if works in 2 cases:
// - when first time called in the cpp (ordinary case) or
// - when the macro __FILENAME__ is used in both h and cpp files
// and so the method is consequentially called
// once with strFilePath == "UserPath/HeaderFileThatUsesMyMACRO.h" and
// once with strFilePath == "UserPath/CPPFileThatUsesMyMACRO.cpp"
//
rstrFileNameHolder = removePath(strFilePath);
rstrFilePathHolder = strFilePath;
}
return rstrFileNameHolder;
}
RemovePath()はさまざまな方法で実装できますが、高速で単純なのはstrrchrのようです。
const char* removePath(const char* path)
{
const char* pDelimeter = strrchr (path, '\\');
if (pDelimeter)
path = pDelimeter+1;
pDelimeter = strrchr (path, '/');
if (pDelimeter)
path = pDelimeter+1;
return path;
}
パーティーに少し遅れましたが、GCCについては-ffile-prefix-map=old=new
オプションを見てください:
ディレクトリoldにあるファイルをコンパイルするときは、ファイルがディレクトリnewにあるかのように、それらの参照をコンパイル結果に記録します。このオプションを指定することは、個々の
-f*-prefix-map
オプションをすべて指定することと同等です。これは、場所に依存しない再現可能なビルドを作成するために使用できます。-fmacro-prefix-map
および-fdebug-prefix-map
も参照してください。
Jenkinsビルドの場合は-ffile-prefix-map=${WORKSPACE}/=/
を追加し、もう1つはローカルのdevパッケージのインストールプレフィックスを削除します。
NOTE残念ながら、-ffile-prefix-map
オプションは、GCC 8以降でのみ使用可能です。また、-fmacro-prefix-map
は、I think、__FILE__
部分を実行します。たとえば、GCC 5では、-fdebug-prefix-map
のみがあり、__FILE__
には影響を与えません(表示されません)。
試してみる
#pragma Push_macro("__FILE__")
#define __FILE__ "foobar.c"
ソースファイルのincludeステートメントの後に追加します
#pragma pop_macro("__FILE__")
ソースファイルの最後。
fILEマクロを少し改善したいと思っています。
#define FILE (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)
これは、Czarek Tomczakが要求したように/と\をキャッチします。これは、私の混在環境でうまく機能します。
このページに移動して、出荷しているバイナリからいビルド場所を指している絶対ソースパスを削除する方法を探している場合は、以下がニーズに合っている可能性があります。
これは、著者がCMakeの使用を想定しているために対する希望を表明した答えを正確には生成しませんが、かなり近くなります。これは、時間の節約になっていたので、これまで誰も言及していなかったのは残念です。
OPTION(CMAKE_USE_RELATIVE_PATHS "If true, cmake will use relative paths" ON)
上記の変数をON
に設定すると、次の形式でビルドコマンドが生成されます。
cd /ugly/absolute/path/to/project/build/src &&
gcc <.. other flags ..> -c ../../src/path/to/source.c
その結果、__FILE__
マクロは../../src/path/to/source.c
に解決されます
ただし、ドキュメントページの警告に注意してください。
相対パスを使用します(動作しない可能性があります!)。
すべてのケースで動作することが保証されているわけではありませんが、私の場合-CMake 3.13 + gcc 4.5で動作しました
Linux(パス '/')とWindows( '\'と '/'の混合)の両方で機能する移植可能な関数を次に示します。
gcc、clang、vsでコンパイルします。
#include <string.h>
#include <stdio.h>
const char* GetFileName(const char *path)
{
const char *name = NULL, *tmp = NULL;
if (path && *path) {
name = strrchr(path, '/');
tmp = strrchr(path, '\\');
if (tmp) {
return name && name > tmp ? name + 1 : tmp + 1;
}
}
return name ? name + 1 : path;
}
int main() {
const char *name = NULL, *path = NULL;
path = __FILE__;
name = GetFileName(path);
printf("path: %s, filename: %s\n", path, name);
path ="/tmp/device.log";
name = GetFileName(path);
printf("path: %s, filename: %s\n", path, name);
path = "C:\\Downloads\\crisis.avi";
name = GetFileName(path);
printf("path: %s, filename: %s\n", path, name);
path = "C:\\Downloads/nda.pdf";
name = GetFileName(path);
printf("path: %s, filename: %s\n", path, name);
path = "C:/Downloads\\Word.doc";
name = GetFileName(path);
printf("path: %s, filename: %s\n", path, name);
path = NULL;
name = GetFileName(NULL);
printf("path: %s, filename: %s\n", path, name);
path = "";
name = GetFileName("");
printf("path: %s, filename: %s\n", path, name);
return 0;
}
標準出力:
path: test.c, filename: test.c
path: /tmp/device.log, filename: device.log
path: C:\Downloads\crisis.avi, filename: crisis.avi
path: C:\Downloads/nda.pdf, filename: nda.pdf
path: C:/Downloads\Word.doc, filename: Word.doc
path: (null), filename: (null)
path: , filename:
何年もの間、@ Patrickの answer で同じソリューションを使用しました。
フルパスにシンボルリンクが含まれる場合、小さな問題があります。
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-builtin-macro-redefined -D'__FILE__=\"$(subst $(realpath ${CMAKE_SOURCE_DIR})/,,$(abspath $<))\"'")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-builtin-macro-redefined -D'__FILE__=\"$(subst $(realpath ${CMAKE_SOURCE_DIR})/,,$(abspath $<))\"'")
-Wno-builtin-macro-redefined
は、__FILE__
マクロを再定義するためのコンパイラの警告をミュートします。
これらのコンパイラはこれをサポートしていませんので、以下のRobust wayを参照してください。
real要件であるファイルパスからプロジェクトパスを削除します。 header.h
ファイル、src/foo/header.h
、またはsrc/bar/header.h
がどこにあるかを見つけるために時間を無駄にしたくないでしょう。
cmake
構成ファイルで__FILE__
マクロを削除する必要があります。
このマクロは、ほとんどの既存のコードで使用されます。再定義するだけで、あなたを自由に設定できます。
gcc
などのコンパイラは、コマンドライン引数からこのマクロを事前定義します。そして、フルパスはmakefile
によって生成されたcmake
sに書き込まれます。
CMAKE_*_FLAGS
のハードコードが必要です。
add_definitions()
やadd_compile_definitions()
など、最近のバージョンにはコンパイラオプションまたは定義を追加するためのコマンドがいくつかあります。これらのコマンドは、ソースファイルに適用する前に、subst
などのmake関数を解析します。それは望んでいないことです。
-Wno-builtin-macro-redefined
の堅牢な方法。include(CheckCCompilerFlag)
check_c_compiler_flag(-Wno-builtin-macro-redefined SUPPORT_C_WNO_BUILTIN_MACRO_REDEFINED)
if (SUPPORT_C_WNO_BUILTIN_MACRO_REDEFINED)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-builtin-macro-redefined")
endif (SUPPORT_C_WNO_BUILTIN_MACRO_REDEFINED)
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag(-Wno-builtin-macro-redefined SUPPORT_CXX_WNO_BUILTIN_MACRO_REDEFINED)
if (SUPPORT_CXX_WNO_BUILTIN_MACRO_REDEFINED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-builtin-macro-redefined")
endif (SUPPORT_CXX_WNO_BUILTIN_MACRO_REDEFINED)
set(*_FLAGS ... -D__FILE__=...)
行からこのコンパイラオプションを忘れずに削除してください。
#include <algorithm>
#include <string>
using namespace std;
string f( __FILE__ );
f = string( (find(f.rbegin(), f.rend(), '/')+1).base() + 1, f.end() );
// searches for the '/' from the back, transfers the reverse iterator
// into a forward iterator and constructs a new sting with both
文字列ライブラリを持たない環境(Linuxカーネル、組み込みシステムなど)で機能するソリューションを次に示します。
#define FILENAME ({ \
const char* filename_start = __FILE__; \
const char* filename = filename_start; \
while(*filename != '\0') \
filename++; \
while((filename != filename_start) && (*(filename - 1) != '/')) \
filename--; \
filename; })
ここで、__FILENAME__
の代わりにFILENAME
を使用してください。はい、それはまだ実行時のものですが、動作します。
GCCを使用しているため、 活用する of
__BASE_FILE__
このマクロは、C文字列定数の形式で、メイン入力ファイルの名前に展開されます。これは、プリプロセッサまたはCコンパイラのコマンドラインで指定されたソースファイルです。
コンパイル時にソースファイルの表現(フルパス/相対パス/ベース名)を変更して、ファイル名の表示方法を制御します。
コンパイル時の計算を使用するソリューションは次のとおりです。
constexpr auto* getFileName(const char* const path)
{
const auto* startPosition = path;
for (const auto* currentCharacter = path;*currentCharacter != '\0'; ++currentCharacter)
{
if (*currentCharacter == '\\' || *currentCharacter == '/')
{
startPosition = currentCharacter;
}
}
if (startPosition != path)
{
++startPosition;
}
return startPosition;
}
std::cout << getFileName(__FILE__);