컴파일 시 경로와 접미사 없이 소스 파일 이름을 추출하는 방법은?
-std=c11인 gcc와 -std=c+14인 g++를 모두 사용합니다.
(으)라는 파일의 :src/dir/Hello.cxx예를 들어 다음과 같은 것으로 확장해야 합니다.
const char basename[] = "Hello";
아니면
const char basename[] = getStaticBasename(__FILE__);
getStaticBasename()는 매크로(C 소스의 경우) 또는 constexpr 함수(C++ 소스의 경우)이며, 결과는 "Hello"입니다.
는 에서 것을 .__FILE__실행 시 경로와 접미사를 실행 파일로 컴파일해서는 안 되기 때문입니다.
해결책은 부스트와 같은 거대한 라이브러리에 의존하지 않는 것이어야 합니다.
저는 메이크 파일이 없기 때문에 이런 솔루션은 제 경우에 사용할 수 없습니다.
그것에 대한 해결책이 있었습니까?
편집 2015-07-02:
- 컴파일러와 링커가 호출되는 방식에 영향을 미치지 않습니다(때로는 makefile을 통해, 때로는 명령줄 또는 일부 IDE(Eclipse CDT managed make, Crossworks, Xcode et cetera).따라서 솔루션은 코드로만 구성되어야 합니다.
- 저의 활용 사례는 소규모 풋프린트 로깅 솔루션을 위한 일종의 "일반 영역 식별자"를 제공하는 것입니다.(내 응용 프로그램 코드는는다)해야 합니다.
#include <Joe/Logger.h>그리고 나중에 전화하는 것으로 예를 들어.LOG_DEBUG(...)저는 자동으로 생성된 "일반 영역 식별자"를 활용할 것입니다. - 가 를 것입니다.
JOE_LOG_FILE_REGION(Hello);후)#include <Joe/Logger.h>(에)을 전에LOG_DEBUG(...)그 암호대로
1. gcc builtin 함수는 컴파일 시 전체 경로의 파일 이름을 얻을 수 있습니다.
#define __FILENAME__ (__builtin_strrchr(__FILE__, '/') ? __builtin_strrchr(__FILE__, '/') + 1 : __FILE__)
아니면
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
2. c++11 constexpr은 컴파일 타임에 이것을 할 수도 있습니다.
c++11 constexpr 함수는 return-statement만 사용할 수 있습니다.
예:
#include <stdio.h>
constexpr const char* str_end(const char *str) {
return *str ? str_end(str + 1) : str;
}
constexpr bool str_slant(const char *str) {
return *str == '/' ? true : (*str ? str_slant(str + 1) : false);
}
constexpr const char* r_slant(const char* str) {
return *str == '/' ? (str + 1) : r_slant(str - 1);
}
constexpr const char* file_name(const char* str) {
return str_slant(str) ? r_slant(str_end(str)) : str;
}
int main() {
constexpr const char *const_file = file_name(__FILE__);
puts(const_file);
return 0;
}
은 입니다.foo/foo1/foo2/foo3/foo4.cpp
사용하다g++ -o foo.exe foo/foo1/foo2/foo3/foo4.cpp -std=c++11 --save-temps이 파일을 컴파일할 수 있습니다.
이거 보이잖아요.
.file "foo4.cpp"
.section .rodata
.LC0:
.string "foo/foo1/foo2/foo3/foo4.cpp"
.text
.globl main
.type main, @function
main:
.LFB4:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movq $.LC0+19, -8(%rbp)
movl $.LC0+19, %edi
call puts
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE4:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4"
.section .note.GNU-stack,"",@progbits
movl $.LC0+19, %edi+ 입니다.LC0 + 19는 경로와 접미사가 없는 파일 이름 문자열의 주소입니다.
3. c++14 constexpr 함수는 이것을 간단한 방법으로 할 수 있습니다.
#include <iostream>
constexpr const char* file_name(const char* path) {
const char* file = path;
while (*path) {
if (*path++ == '/') {
file = path;
}
}
return file;
}
int main() {
constexpr const char* file = file_name(__FILE__);
std::cout << file << std::endl;
return 0;
}
c++14 constexpr 함수는 loop 및 local 변수를 사용할 수 있습니다.
file_name됩니다의 됩니다.const char *컴파일러 시간에. ~
컴파일 시간에 전처리기 트릭이나 외부 스크립트 없이 기본 파일 이름을 추출할 수 있습니까?c++14?문제 없습니다, 각하.
#include <iostream>
#include <string>
using namespace std;
namespace detail {
constexpr bool is_path_sep(char c) {
return c == '/' || c == '\\';
}
constexpr const char* strip_path(const char* path)
{
auto lastname = path;
for (auto p = path ; *p ; ++p) {
if (is_path_sep(*p) && *(p+1)) lastname = p+1;
}
return lastname;
}
struct basename_impl
{
constexpr basename_impl(const char* begin, const char* end)
: _begin(begin), _end(end)
{}
void write(std::ostream& os) const {
os.write(_begin, _end - _begin);
}
std::string as_string() const {
return std::string(_begin, _end);
}
const char* const _begin;
const char* const _end;
};
inline std::ostream& operator<<(std::ostream& os, const basename_impl& bi) {
bi.write(os);
return os;
}
inline std::string to_string(const basename_impl& bi) {
return bi.as_string();
}
constexpr const char* last_dot_of(const char* p) {
const char* last_dot = nullptr;
for ( ; *p ; ++p) {
if (*p == '.')
last_dot = p;
}
return last_dot ? last_dot : p;
}
}
// the filename with extension but no path
constexpr auto filename = detail::strip_path(__FILE__);
constexpr auto basename = detail::basename_impl(filename, detail::last_dot_of(filename));
auto main() -> int
{
cout << filename << endl;
cout << basename << endl;
cout << to_string(basename) << endl;
return 0;
}
에서 gcc 를가 __FILE__절대 경로(IDE를 통해 gcc로 전달)를 통과하는 경우보다.
gcc test.c -otest.exe나를 줍니다__FILE__~하듯이test.c.gcc c:\tmp\test.c -otest.exe나를 줍니다__FILE__~하듯이c:\tmp\test.c.
혹시 소스가 위치한 경로에서 gcc로 전화를 걸면 해결책으로 충분할까요?
편집
여기 컴파일 시간에 파일 확장자를 제거하는 "더러운" 안전한 해킹이 있습니다.제가 추천하고 싶은 것은 아니지만, 글 쓰는 것은 재미있었습니다 :) 그러니 가치 있는 것으로 받아들이세요.C에서만 작동합니다.
#include <stdio.h>
#define EXT_LENGTH (sizeof(".c") - 1) // -1 null term
typedef union
{
char filename_no_nul [sizeof(__FILE__)-EXT_LENGTH-1]; // -1 null term
char filename_nul [sizeof(__FILE__)-EXT_LENGTH];
} remove_ext_t;
int main (void)
{
const remove_ext_t file = { __FILE__ };
puts(file.filename_nul);
return 0;
}
유니언은 확장자와 널 터미네이터를 뺀 전체 경로를 담을 수 있는 크기의 멤버를 하나 할당합니다.그리고 null terminator를 사용하여 전체 경로에서 확장을 제외하고 유지할 수 있는 크기의 멤버 하나의 멤버를 할당합니다.
를 다 .__FILE__다를 많이 됩니다.__FILE__알맞게 이것은 에서는 .이것은 C에서는 괜찮지만 C++에서는 허용되지 않습니다. 만약에__FILE__.test.c이 입니다를(를) 됩니다.test 터미네이터가 다.
그러나 이 해킹은 다른 조합원이 "집계/조합" 초기화 규칙에 따라 초기화되었다는 사실을 악용하기 때문에 해당 문자열 뒤에는 여전히 0이 뒤따를 것입니다.이 규칙에 따라 "aggregate"에 남아 있는 항목은 정적 저장 기간(즉, 0)을 가진 것처럼 초기화됩니다.Null 터미네이터의 값입니다.
아주 간단한 것으로 드러났는데, 당신은 단지 전처리 지시서가 필요합니다, 예를 들어보세요.
#line 0 "Hello"
파일의 맨 위에, 만약 당신이 원하는 것이 파일 이름을 완전히 숨기는 것뿐이라면, 이것은 그대로입니다.
#line 0 ""
효과가 있을 겁니다.
않으실 Makefile 돼, .
file=cfile;
content=$(sed -e "1s/^/#line 0 \"$file\"\n/" example/${file}.c);
echo $content | gcc -xc -O3 -o ${file} -
-xc위의 gcc 플래그는 (gcc의 문서에서) 다음을 의미합니다.
-x언어:컴파일러가 파일 이름 접미사를 기반으로 기본값을 선택하도록 하지 말고 다음 입력 파일의 언어를 명시적으로 지정합니다.이 옵션은 next-x 옵션까지 다음 입력 파일에 모두 적용됩니다.언어에 사용할 수 있는 값은 다음과 같습니다.
c c-header cpp-output c++ c++-header c++-cpp-output objective-c objective-c-header objective-c-cpp-output objective-c++ objective-c++-header objective-c++-cpp-output assembler assembler-with-cpp ada f77 f77-cpp-input f95 f95-cpp-input go java
소스를 만드는 데 도움이 되는 스크립트가 없다면 할 방법이 없다고 생각합니다.
또한 위의 gcc 문서의 인용문을 보면 파일을 확장자 없이 저장할 수 있고, @Lundin의 원래 솔루션을 이것과 결합하여 사용할 수 있음을 알 수 있습니다.
gcc -xc -o file filename_without_extension
이 경우에는__FILE__로 확장될 것입니다."filename_without_extension", 파일이 존재하는 동일한 디렉토리에 파일을 컴파일해야 하지만, 그렇지 않으면 파일에 대한 경로가 포함되기 때문에 원하는 것을 달성할 수 있습니다.
가장 많이 투표된 솔루션은 전체 파일 경로가 이진법에 저장되고 경로의 마지막 부분에 대한 포인터만 (마지막 '/' 문자에서) 계산되어 사용되기 때문에 OP에 의존하지 않습니다.
@pexeer 답변에서 제안 솔루션의 어셈블리 출력 참조:
.LC0:
.string "/app/example.cpp"
main:
push rax
mov esi, OFFSET FLAT:.LC0+5
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
xor eax, eax
pop rdx
ret
_GLOBAL__sub_I_main:
push rax
mov edi, OFFSET FLAT:_ZStL8__ioinit
call std::ios_base::Init::Init() [complete object constructor]
mov edx, OFFSET FLAT:__dso_handle
mov esi, OFFSET FLAT:_ZStL8__ioinit
pop rcx
mov edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
jmp __cxa_atexit
전체 파일 경로를 저장하지 않으려면 다음과 같은 작업이 필요합니다.
#include <iostream>
#include <utility>
constexpr const char* file_name(const char* path) {
const char* file = path;
while (*path) {
if (*path++ == '/') {
file = path;
}
}
return file;
}
constexpr size_t file_length(const char * path) {
size_t i = 0;
const char * file = file_name(path);
while (*file) { i ++; file++; }
return i;
}
template<std::size_t... I>
const char * print_impl(std::index_sequence<I...>) {
static const char file[file_length(__FILE__)+1] = { file_name(__FILE__)[I]...};
return file;
}
inline const char* print_file() {
return print_impl(std::make_index_sequence<file_length(__FILE__) + 1>());
}
int main() {
std::cout<<print_file()<<std::endl;
return 0;
}
전체 파일 경로가 저장되지 않은 어셈블리 출력이 나타납니다.
main:
push rax
mov esi, OFFSET FLAT:print_impl<0ul, 1ul, 2ul, 3ul, 4ul, 5ul, 6ul, 7ul, 8ul, 9ul, 10ul, 11ul>(std::integer_sequence<unsigned long, 0ul, 1ul, 2ul, 3ul, 4ul, 5ul, 6ul, 7ul, 8ul, 9ul, 10ul, 11ul>)::file
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
xor eax, eax
pop rdx
ret
_GLOBAL__sub_I_main:
push rax
mov edi, OFFSET FLAT:_ZStL8__ioinit
call std::ios_base::Init::Init() [complete object constructor]
mov edx, OFFSET FLAT:__dso_handle
mov esi, OFFSET FLAT:_ZStL8__ioinit
pop rcx
mov edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
jmp __cxa_atexit
print_impl<0ul, 1ul, 2ul, 3ul, 4ul, 5ul, 6ul, 7ul, 8ul, 9ul, 10ul, 11ul>(std::integer_sequence<unsigned long, 0ul, 1ul, 2ul, 3ul, 4ul, 5ul, 6ul, 7ul, 8ul, 9ul, 10ul, 11ul>)::file:
.string "example.cpp"
여기 예제
여기서 기본 아이디어는 원하는 문자열만 포함하는 정적 초기화된 문자 배열을 구성하는 것입니다(전체 파일 경로를 포함하는 정적 문자 배열에 대한 포인터는 포함하지 않음).파일 길이를 줄이는 것은 사소한 일이지만 통화할 수 없기 때문에 필요합니다.strlen상수 함수로
그런 다음 자연스러운 선언처럼 정수 시퀀스를 파일의 포인티드 배열의 인덱스로 사용하는 방법이 있습니다.const char f[] = {"str"[0], "str"[1], ...}). 정수 수열은 가변 템플릿 인스턴스화에 사용될 수 있으므로 이러한 컨텍스트에서 호출되어야 합니다.
에서 GCC 됩니다.print_impl만다),계용)수strip --strip-all /path/to/binary)
안타깝게도 모든 사람들이 다양한 마법 같은 방법으로 경로에서 원하지 않는 부분을 제거하느라 바쁜 것 같습니다(--> 대부분이 작동하지 않습니다).
올바른 방법은 컴파일러에게 매크로에서 경로를 변경/제거하여 땜질의 필요성을 모두 피하도록 지시하는 것입니다.gcc의 경우 매개 변수를 fmacro-prefix-map이라고 합니다.다음과 같이 사용할 수 있습니다.
-fmacro-prefix-map=/path/to/source/=
"/path/to/source/main.cpp"를 그냥 "main.cpp"로 변경합니다.
그런데: std::source_location에서도 사용할 수 있으며 물론 전체 경로(변경되지 않음)는 결과 바이너리에 저장되지 않습니다.
언급URL : https://stackoverflow.com/questions/31050113/how-to-extract-the-source-filename-without-path-and-suffix-at-compile-time
'programing' 카테고리의 다른 글
| PHP - 제거PHP - 제거문자열에서 따옴표로 옮기다문자열에서 따옴표로 옮기다 (0) | 2023.10.07 |
|---|---|
| PHP로 나중에 배열로 액세스하려면 배열을 파일에 저장하려면 어떻게 해야 합니까? (0) | 2023.10.02 |
| 사용자를 작성하고 Maria의 특정 데이터베이스에 대한 액세스를 취소합니다.DB (0) | 2023.10.02 |
| 알 수 없는 NullPointerJdbcOdbcDriver.finalize() 행의 예외: 96 (0) | 2023.10.02 |
| Android Design Library - 부동 동작 버튼 패딩/마진 이슈 (0) | 2023.10.02 |