__FILE__ macro hiển thị đường dẫn đầy đủ


164

Macro được xác định trước tiêu chuẩn __FILE__có sẵn trong C hiển thị đường dẫn đầy đủ đến tệp. Có cách nào để rút ngắn con đường? Ý tôi là thay vì

/full/path/to/file.c

tôi hiểu rồi

to/file.c

hoặc là

file.c

18
Nó sẽ thực sự tuyệt vời để tìm một giải pháp chỉ dành cho tiền xử lý. Tôi sợ rằng các đề xuất dựa trên các hoạt động chuỗi sẽ thực thi trong thời gian chạy.
cdleonard

9
Vì bạn đang sử dụng gcc, tôi nghĩ bạn có thể thay đổi những gì __FILE__chứa bằng cách thay đổi tên tệp bạn truyền trên dòng lệnh. Vì vậy, thay vì gcc /full/path/to/file.c, hãy thử cd /full/path/to; gcc file.c; cd -;. Tất nhiên có nhiều hơn một chút so với điều đó nếu bạn đang dựa vào thư mục hiện tại của gcc cho đường dẫn bao gồm hoặc vị trí tệp đầu ra. Chỉnh sửa: các tài liệu gcc đề xuất rằng đó là đường dẫn đầy đủ, không phải là đối số tên tệp đầu vào, nhưng đó không phải là những gì tôi thấy cho gcc 4.5.3 trên Cygwin. Vì vậy, bạn cũng có thể thử nó trên Linux và xem.
Steve Jessop

4
GCC 4.5.1 (được xây dựng cho arm-none-eabi) sử dụng văn bản chính xác của tên tệp trên dòng lệnh của nó. Trong trường hợp của tôi, đó là lỗi của IDE khi gọi GCC với tất cả các tên tệp đủ điều kiện thay vì đặt thư mục hiện tại ở đâu đó hợp lý (vị trí của tệp dự án, có lẽ?) Hoặc có thể định cấu hình và sử dụng các đường dẫn tương đối từ đó. Tôi nghi ngờ rất nhiều IDE làm điều đó (đặc biệt là trên Windows) vì một số loại khó chịu liên quan đến việc giải thích thư mục "hiện tại" thực sự dành cho ứng dụng GUI.
RBerteig

3
@SteveJessop - hy vọng bạn đọc bình luận này. Tôi có một tình huống mà tôi thấy __FILE__được in ../../../../../../../../rtems/c/src/lib/libbsp/sparc/leon2/../../shared/bootcard.cvà tôi muốn biết gcc đã biên dịch tệp ở đâu để tệp này được đặt tương đối giống như nó được hiển thị.
Chân Kim

1
Câu hỏi này không phải là một bản sao của câu hỏi được liên kết. Đối với một, câu hỏi được liên kết là về C ++ và do đó, các câu trả lời sẽ đi sâu vào C ++ macro esoterica. Thứ hai, không có gì trong câu hỏi của OP bắt buộc một giải pháp vĩ mô. Nó chỉ long trọng chỉ ra một vấn đề và đặt một câu hỏi kết thúc mở.
Giáo sư Falken

Câu trả lời:


166

Thử

#include <string.h>

#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)

Đối với Windows, sử dụng '\\' thay vì '/'.


13
/là một dấu tách đường dẫn hợp lệ trong Windows.
Hans Passant

11
/là một dấu tách đường dẫn hợp lệ trong các tên tệp được truyền cho CreateFile () và vv. Tuy nhiên, điều đó không phải lúc nào cũng có nghĩa là bạn có thể sử dụng /bất cứ nơi nào trong Windows vì có một truyền thống (được kế thừa từ CPM) khi sử dụng /làm đối số dẫn đầu tại dấu nhắc lệnh. Nhưng một công cụ chất lượng sẽ cẩn thận phân tách tên tệp ở cả hai ký tự gạch chéo và dấu gạch chéo ngược để tránh mọi vấn đề cho những người quản lý sử dụng /.
RBerteig

5
@AmigableClarkKant, không bạn có thể trộn cả hai dấu phân cách trong cùng một tên tệp.
RBerteig

2
Nếu nền tảng của bạn hỗ trợ thì char* fileName = basename(__FILE__); chắc chắn là có trong Linux và OS X, tuy nhiên, đừng biết về Windows.
JeremyP

14
Điều này có thể được rút ngắn để strrchr("/" __FILE__, '/') + 1. Bằng cách thêm "/" vào __FILE__, strrchr được đảm bảo tìm thấy thứ gì đó, và do đó điều kiện ?: Không còn cần thiết nữa.
ɲeuroburɳ

54

Đây là một mẹo nếu bạn đang sử dụng cmake. Từ: http://public.kitware.com/pipermail/cmake/2013-Janemony/053117.html

Tôi đang sao chép mẹo để tất cả trên trang này:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__FILENAME__='\"$(subst
  ${CMAKE_SOURCE_DIR}/,,$(abspath $<))\"'")

Nếu bạn đang sử dụng GNU make, tôi thấy không có lý do gì bạn không thể mở rộng điều này sang các tệp tạo của riêng bạn. Ví dụ: bạn có thể có một dòng như thế này:

CXX_FLAGS+=-D__FILENAME__='\"$(subst $(SOURCE_PREFIX)/,,$(abspath $<))\"'"

đâu $(SOURCE_PREFIX)là tiền tố mà bạn muốn loại bỏ.

Sau đó sử dụng __FILENAME__thay thế __FILE__.


11
Tôi sợ sau đó điều này không hoạt động cho FILE được tham chiếu trong tệp tiêu đề.
Baiyan Huang

2
Đồng ý với @BaiyanHuang nhưng không chắc rằng nhận xét đó là rõ ràng. __FILE__không phải là một ký hiệu tiền xử lý đơn giản, nó thay đổi thành tệp hiện tại thường được sử dụng để phát ra tên của tệp hiện tại (tiêu đề hoặc mô-đun nguồn). Điều này __FILENAME__sẽ chỉ có nguồn ngoài cùng
năm14

3
Giải pháp của câu trả lời này không khả dụng vì nó sử dụng thoát vỏ Bourne. Tốt hơn là sử dụng CMake để thực hiện nó theo cách sạch sẽ và di động. Xem câu trả lời macro macro_file_basename_for_source tại đây.
Colin D Bennett

3
Biến thể GNU Make của cái này là CFLAGS + = -D__FILENAME __ = \ "$ (notdir $ <) \"
ctuffli

4
@firegurafiku Trên các nền tảng nhúng, kích thước mã thường bị ràng buộc nhiều hơn là tốc độ. Nếu bạn có các câu lệnh gỡ lỗi trong mỗi tệp, điều đó có thể nhanh chóng tăng kích thước tệp lên bằng các chuỗi đường dẫn đầy đủ. Tôi đang xử lý một nền tảng như vậy vào lúc này và việc tham khảo __FILE__ở khắp mọi nơi đang thổi ra không gian mã.
mrtumnus

26

Tôi vừa nghĩ ra một giải pháp tuyệt vời cho việc này hoạt động với cả tệp nguồn và tệp tiêu đề, rất hiệu quả và hoạt động trên thời gian biên dịch trong tất cả các nền tảng mà không cần các tiện ích mở rộng dành riêng cho trình biên dịch. Giải pháp này cũng bảo tồn cấu trúc thư mục tương đối của dự án của bạn, vì vậy bạn biết tệp nằm trong thư mục nào và chỉ liên quan đến thư mục gốc của dự án.

Ý tưởng là lấy kích thước của thư mục nguồn bằng công cụ xây dựng của bạn và chỉ cần thêm nó vào __FILE__macro, loại bỏ hoàn toàn thư mục và chỉ hiển thị tên tệp bắt đầu từ thư mục nguồn của bạn.

Ví dụ sau được triển khai bằng CMake, nhưng không có lý do gì nó không hoạt động với bất kỳ công cụ xây dựng nào khác, vì thủ thuật này rất đơn giản.

Trên tệp CMakeLists.txt, xác định một macro có độ dài đường dẫn đến dự án của bạn trên 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}")

Trên mã nguồn của bạn, xác định một __FILENAME__macro chỉ thêm kích thước đường dẫn nguồn vào __FILE__macro:

#define __FILENAME__ (__FILE__ + SOURCE_PATH_SIZE)

Sau đó, chỉ cần sử dụng macro mới này thay vì __FILE__macro. Điều này hoạt động vì __FILE__đường dẫn sẽ luôn bắt đầu với đường dẫn đến thư mục nguồn CMake của bạn. Bằng cách loại bỏ nó khỏi __FILE__chuỗi, bộ xử lý trước sẽ đảm nhiệm việc chỉ định tên tệp chính xác và tất cả sẽ liên quan đến thư mục gốc của dự án CMake của bạn.

Nếu bạn quan tâm đến hiệu suất, điều này cũng hiệu quả như việc sử dụng __FILE__, bởi vì cả hai __FILE__SOURCE_PATH_SIZEđược biết là hằng số thời gian biên dịch, do đó nó có thể được tối ưu hóa bởi trình biên dịch.

Nơi duy nhất thất bại là nếu bạn đang sử dụng tệp này trên các tệp được tạo và chúng nằm trong thư mục xây dựng ngoài nguồn. Sau đó, có lẽ bạn sẽ phải tạo một macro khác bằng cách sử dụng CMAKE_BUILD_DIRbiến thay vì CMAKE_SOURCE_DIR.


4
Tôi đã không hiểu điều này lúc đầu. Tôi đã mã hóa các ví dụ và chạy chúng chống lại gcc và clang và chúng hoạt động. Tôi cũng thử nghiệm với việc chỉ nối thêm các giá trị số theo nghĩa đen khác nhau và nó hoạt động như tôi mong đợi. Rồi cuối cùng nó cũng chợt nhận ra tôi. __FILE__là một con trỏ tới một mảng byte. Vì vậy, thêm một chữ số chỉ là bổ sung con trỏ. Rất thông minh @RenatoUtsch
Daniel

Nó chỉ hoạt động nếu một tệp nguồn nằm trong thư mục danh sách cmake. Nếu một tệp nguồn ở bên ngoài thì nó sẽ bị hỏng, có thể có quyền truy cập bên ngoài một chuỗi ký tự. Vì vậy, hãy cẩn thận với điều đó.
Andry

Nó thực sự không làm giảm kích thước mã. Tôi đoán toàn bộ đường dẫn vẫn được biên dịch thành nhị phân, chỉ con trỏ được sửa đổi.
Tarion

Cả __FILE__macro và macro SOURCE_PATH_SIZE đều là các hằng số được biết đến tại thời điểm biên dịch. Tôi mong muốn các trình biên dịch tối ưu hóa hiện đại có thể phát hiện ra rằng một phần của chuỗi không được sử dụng và chỉ cần xóa nó khỏi nhị phân. Dù sao, tôi không nghĩ vài byte này sẽ tạo ra sự khác biệt đáng kể trong kích thước nhị phân, vì vậy tôi sẽ không thực sự quan tâm đến điều đó.
RenatoUtsch

@RenatoUtsch Dự án tôi đang thực hiện có một thay đổi chỉ xác định tên tệp, nhưng cũng có nhược điểm là đặt tên tệp C cho tiêu đề. Sự thay đổi đã được thực hiện để có được bản dựng có thể tái tạo. Vì vậy, với gcc với -O2, chuỗi có thực sự được tối ưu hóa và bản dựng được tạo lại không?
Paul Stelian

18

Hoàn toàn biên dịch giải pháp thời gian ở đây. Nó dựa trên thực tế là sizeof()một chuỗi ký tự trả về độ dài của nó + 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__)

Vui lòng mở rộng tầng toán tử có điều kiện đến tên tệp hợp lý tối đa trong dự án. Độ dài đường dẫn không quan trọng, miễn là bạn kiểm tra đủ xa từ cuối chuỗi.

Tôi sẽ xem liệu tôi có thể có được một macro tương tự không có độ dài được mã hóa cứng với đệ quy macro không ...


3
Câu trả lời tuyệt vời. Bạn cần sử dụng ít nhất -O1để sử dụng thời gian này để biên dịch.
Chấn thương kỹ thuật số

1
Đây không phải là ngược? Bạn muốn tìm sự xuất hiện cuối cùng của '/', có nghĩa là bạn nên bắt đầu với sizeof(s) > 2kiểm tra trước. Ngoài ra, điều này không hoạt động vào thời gian biên dịch đối với tôi, tại -Os. Các chuỗi đường dẫn đầy đủ đã có mặt trong nhị phân đầu ra.
mrtumnus

15

Ít nhất là đối với gcc, giá trị của __FILE__là đường dẫn tệp như được chỉ định trên dòng lệnh của trình biên dịch . Nếu bạn biên dịch file.cnhư thế này:

gcc -c /full/path/to/file.c

những __FILE__sẽ mở rộng tới "/full/path/to/file.c". Nếu bạn thay vào đó làm điều này:

cd /full/path/to
gcc -c file.c

sau đó __FILE__sẽ mở rộng ra chỉ "file.c".

Điều này có thể hoặc không thể thực tế.

Tiêu chuẩn C không yêu cầu hành vi này. Tất cả những gì nó nói __FILE__là nó mở rộng thành "Tên giả định của nguồn hiện tại fi le (một chuỗi ký tự theo nghĩa đen)".

Một cách khác là sử dụng #linechỉ thị. Nó ghi đè số dòng hiện tại và tùy chọn tên tệp nguồn. Nếu bạn muốn ghi đè tên tệp nhưng chỉ để lại số dòng, hãy sử dụng __LINE__macro.

Ví dụ: bạn có thể thêm phần này ở gần đầu file.c:

#line __LINE__ "file.c"

Vấn đề duy nhất với điều này là nó gán số dòng được chỉ định cho dòng sau và đối số đầu tiên #linephải là một chuỗi số để bạn không thể làm gì đó như

#line (__LINE__-1) "file.c"  // This is invalid

Đảm bảo rằng tên tệp trong lệnh #linekhớp với tên thực của tệp được để lại dưới dạng bài tập.

Ít nhất là đối với gcc, điều này cũng sẽ ảnh hưởng đến tên tệp được báo cáo trong các thông báo chẩn đoán.


1
Keith Thompson đó là giải pháp tuyệt vời, cảm ơn bạn. Vấn đề duy nhất là dường như macro này cắt giảm __LINE__giá trị của một. Vì vậy, __LINE__viết trong dòng xcử chỉ đánh giá x-1. Ít nhất là với gcc 5.4.
elklepo

1
@Klepak: Bạn nói đúng, và đó là hành vi tiêu chuẩn. Lệnh "làm cho việc triển khai thực hiện như thể chuỗi các dòng nguồn sau bắt đầu bằng một dòng nguồn có số dòng như được chỉ định bởi chuỗi số". Và nó phải là một chuỗi số , vì vậy bạn không thể sử dụng #line __LINE__-1, "file.c". Tôi sẽ cập nhật câu trả lời của tôi.
Keith Thompson

1
Nó sẽ như thế nào đối với các trình biên dịch khác như Clang, MSVC hoặc Intel C Compiler?
Franklin Yu

8

Đến bữa tiệc muộn một chút, nhưng với GCC, hãy xem -ffile-prefix-map=old=newtùy chọn:

Khi biên dịch các tệp nằm trong thư mục , hãy ghi lại bất kỳ tham chiếu nào đến chúng trong kết quả biên dịch như thể các tệp nằm trong thư mục mới thay thế. Chỉ định tùy chọn này tương đương với chỉ định tất cả các -f*-prefix-maptùy chọn riêng lẻ . Điều này có thể được sử dụng để làm cho các bản dựng có thể lặp lại độc lập với vị trí. Xem thêm -fmacro-prefix-map-fdebug-prefix-map.

Vì vậy, đối với các bản dựng Jenkins của tôi, tôi sẽ thêm -ffile-prefix-map=${WORKSPACE}/=/và một bản khác để loại bỏ tiền tố cài đặt gói dev cục bộ.

LƯU Ý Thật không may là -ffile-prefix-maptùy chọn chỉ avalable trong GCC 8 trở đi, như là -fmacro-prefix-maptrong đó, tôi nghĩ rằng , hiện các __FILE__phần. Đối với, GCC 5, chúng tôi chỉ có -fdebug-prefix-mapmà không (dường như) ảnh hưởng __FILE__.


1
Tùy chọn -ffile-prefix-mapthực sự ngụ ý cả -fdebug-prefix-map-fmacro-prefix-maptùy chọn. Xem thêm các tài liệu tham khảo tại reproducible-builds.org/docs/build-path Lỗi GCC rằng ca khúc -fmacro-prefix-map-ffile-prefix-mapgcc.gnu.org/bugzilla/show_bug.cgi?id=70268
Lekensteyn

6
  • C ++ 11
  • msvc2015u3, gcc5.4, clang3.8.0

    template <typename T, size_t S>
    inline constexpr size_t get_file_name_offset(const T (& 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 <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__)]);
    }

Mã tạo ra thời gian biên dịch bù khi:

  • gcc: ít nhất là gcc6.1 + -O1
  • msvc: đưa kết quả vào biến constexpr:

      constexpr auto file = &__FILE__[get_file_name_offset(__FILE__)];
      printf("%s\n", file);
  • clang: kiên trì không biên dịch đánh giá thời gian

Có một mẹo để buộc cả 3 trình biên dịch thực hiện đánh giá thời gian ngay cả trong cấu hình gỡ lỗi với tối ưu hóa bị vô hiệu hóa:

    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__))]);
    }

https://godbolt.org/z/u6s8j3


Man, thật đẹp và nó hoạt động, cảm ơn bạn! Không biết tại sao câu trả lời này được đánh giá thấp như vậy.
La Mã


4

Không có cách biên dịch thời gian để làm điều này. Rõ ràng bạn có thể thực hiện nó trong thời gian chạy bằng cách sử dụng thời gian chạy C, như một số câu trả lời khác đã chứng minh, nhưng tại thời điểm biên dịch, khi người tiền xử lý bắt đầu, bạn đã hết may mắn.


1
các strrchrcâu trả lời plausibly có thể được tính tại thời gian biên dịch, mặc dù tất nhiên vẫn không bằng vi xử lý. Tôi không biết liệu gcc có thực sự làm điều đó không, tôi chưa kiểm tra, nhưng tôi khá chắc chắn rằng nó có tính toán strlencác chuỗi ký tự trong thời gian biên dịch.
Steve Jessop

@Steve - có thể, nhưng đó là một sự phụ thuộc lớn vào hành vi cụ thể của trình biên dịch.
Sean

Tôi không nghĩ đó là một sự phụ thuộc lớn, bởi vì tôi rất nghi ngờ rằng mã này rất quan trọng về hiệu năng. Và nếu có, di chuyển nó ra khỏi vòng lặp. Trong trường hợp đây là một vấn đề lớn, bởi vì bạn hoàn toàn cần một chuỗi ký tự chỉ chứa tên cơ sở, có lẽ bạn có thể tính đúng chuỗi khi xây dựng bằng cách chạy một số tập lệnh qua nguồn.
Steve Jessop

5
Nó có thể không phải là hiệu suất quan trọng, nhưng nó có thể dễ dàng được coi là quan trọng về quyền riêng tư. Không có lý do chính đáng nào để tiết lộ các hoạt động tổ chức theo từng khách hàng của tôi trong các chuỗi được đóng băng vào tệp EXE được phát hành. Tồi tệ hơn, đối với các ứng dụng được tạo ra thay mặt cho khách hàng, các chuỗi đó có thể tiết lộ những điều khách hàng của tôi có thể không thích, chẳng hạn như không phải là tác giả của sản phẩm của chính họ. Kể từ khi __FILE__được gọi ngầm assert(), rò rỉ này có thể xảy ra mà không có hành động công khai nào khác.
RBerteig

@RBerteig tên cơ sở của __FILE__chính nó cũng có thể tiết lộ những điều khách hàng có thể không thích, vì vậy sử dụng __FILE__mọi nơi - cho dù nó có chứa tên đường dẫn tuyệt đối đầy đủ hay chỉ là tên cơ sở - có cùng các vấn đề mà bạn đã chỉ ra. Trong tình huống này, tất cả đầu ra sẽ cần được xem xét kỹ lưỡng và một API đặc biệt sẽ được giới thiệu cho đầu ra cho khách hàng. Phần còn lại của đầu ra nên được kết xuất thành / dev / NULL hoặc stdout và stderr nên được đóng lại. :-)
tchen

4

Sử dụng hàm basename () hoặc, nếu bạn đang ở trên Windows, _splitpath () .

#include <libgen.h>

#define PRINTFILE() { char buf[] = __FILE__; printf("Filename:  %s\n", basename(buf)); }

Cũng thử man 3 basenametrong một cái vỏ.


2
@mahmood : char file_copy[] = __FILE__; const char *filename = basename(__FILE__);. Lý do cho việc sao chép là tên cơ sở có thể sửa đổi chuỗi đầu vào. Bạn cũng phải coi chừng con trỏ kết quả chỉ tốt cho đến khi basenameđược gọi lại. Điều này có nghĩa là nó không an toàn cho chủ đề.
Steve Jessop

@SteveJessop, ah tôi quên mất. Thật.
Giáo sư Falken

1
@Amigable: công bằng mà nói, tôi nghi ngờ rằng basenametrên thực tế sẽ không sửa đổi chuỗi đầu vào do đó __FILE__, bởi vì chuỗi đầu vào không có /cuối và do đó không cần sửa đổi. Vì vậy, bạn có thể thoát khỏi nó, nhưng tôi nghĩ rằng lần đầu tiên ai đó nhìn thấy basename, họ sẽ thấy nó với tất cả các hạn chế.
Steve Jessop

@SteveJessop trang man BSd cho basename () đề cập rằng phiên bản kế thừa của basename () lấy const char * và không sửa đổi chuỗi. Trang man linux không đề cập gì đến const nhưng đề cập rằng nó có thể trả về một phần của chuỗi đối số. Vì vậy, tốt nhất nên bảo thủ đối phó với tên cơ sở ().
Giáo sư Falken

@SteveJessop, hehe, tôi chỉ bây giờ sau khi xem xét nhận xét của bạn một cách cẩn thận, sau bốn năm, nhận ra rằng /ở cuối chuỗi có nghĩa là tên cơ sở có thể có lý do chính đáng để sửa đổi đối số của nó.
Giáo sư Falken

4

Nếu bạn đang sử dụng CMAKEvới trình biên dịch GNU, globalđịnh nghĩa này hoạt động tốt:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__MY_FILE__='\"$(notdir $(abspath $<))\"'")

4

Tôi đã sử dụng cùng một giải pháp với câu trả lời của @Patrick trong nhiều năm.

Nó có một vấn đề nhỏ khi đường dẫn đầy đủ chứa liên kết ký hiệu.

Giải pháp tốt hơn.

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 $<))\"'")

Tại sao nên sử dụng cái này?

  • -Wno-builtin-macro-redefinedđể tắt tiếng cảnh báo trình biên dịch để xác định lại __FILE__macro.

    Đối với những trình biên dịch không hỗ trợ điều này, hãy tham khảo cách mạnh mẽ dưới đây.

  • Tách đường dẫn dự án từ đường dẫn tệp là yêu cầu thực sự của bạn . Bạn sẽ không muốn lãng phí thời gian để tìm ra một header.htập tin, src/foo/header.hhoặc src/bar/header.h.

  • Chúng ta nên loại bỏ __FILE__macro trong cmaketập tin cấu hình.

    Macro này được sử dụng trong hầu hết các mã tồn tại. Đơn giản chỉ cần xác định lại nó có thể giải phóng bạn.

    Trình biên dịch như gccxác định trước macro này từ các đối số dòng lệnh. Và đường dẫn đầy đủ được viết bằng makefiles được tạo bởi cmake.

  • Mã cứng trong CMAKE_*_FLAGSlà bắt buộc.

    Có một số lệnh để thêm tùy chọn trình biên dịch hoặc định nghĩa trong một số phiên bản gần đây hơn, như add_definitions()add_compile_definitions(). Các lệnh này sẽ phân tích các hàm tạo như substtrước khi áp dụng cho các tệp nguồn. Đó không phải là chúng tôi muốn.

Cách mạnh mẽ cho -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)

Hãy nhớ loại bỏ tùy chọn trình biên dịch này khỏi set(*_FLAGS ... -D__FILE__=...)dòng.


Điều này không hoạt động cho nội dung đến từ bao gồm các tập tin.
chqrlie

Bạn có thể đăng một số mã không? Một trường hợp phổ biến là đặt các biến trong phạm vi cục bộ và sử dụng nó trong một phạm vi khác.
Levi.G

Ví dụ: nếu bạn sử dụng __FILE__với định nghĩa của mình để tạo chẩn đoán trong hàm nội tuyến được xác định trong tệp tiêu đề, chẩn đoán thời gian chạy sẽ báo cáo tên của tệp được chuyển đến trình biên dịch thay vì tên của tệp bao gồm, trong khi số dòng sẽ tham khảo các tập tin bao gồm.
chqrlie

vâng, nó được thiết kế để được sử dụng phổ biến nhất #define LOG(fmt, args...) printf("%s " fmt, __FILE__, ##args). khi sử dụng LOG()macro, bạn không thực sự muốn thấy log.htrong tin nhắn. xét cho cùng, __FILE__macro được mở rộng trong mọi tệp C / Cpp (đơn vị biên dịch) thay vì các tệp được bao gồm.
Levi.G

3

Một thay đổi nhỏ về những gì @ red1ynx đề xuất sẽ tạo ra macro sau:

#define SET_THIS_FILE_NAME() \
    static const char* const THIS_FILE_NAME = \
        strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__;

Trong mỗi tệp .c (pp) của bạn thêm:

SET_THIS_FILE_NAME();

Sau đó, bạn có thể tham khảo THIS_FILE_NAMEthay vì __FILE__:

printf("%s\n", THIS_FILE_NAME);

Điều này có nghĩa là việc xây dựng được thực hiện một lần trên mỗi tệp .c (pp) thay vì mỗi lần macro được tham chiếu.

Nó được giới hạn chỉ sử dụng từ các tệp .c (pp) và sẽ không thể sử dụng được từ các tệp tiêu đề.


3

Tôi đã làm một macro __FILENAME__tránh cắt toàn bộ đường dẫn mỗi lần. Vấn đề là giữ tên tệp kết quả trong một biến cpp-local.

Nó có thể được thực hiện dễ dàng bằng cách định nghĩa một biến toàn cục tĩnh trong tệp .h . Định nghĩa này đưa ra các biến riêng biệt và độc lập trong mỗi tệp .cpp bao gồm .h . Để trở thành một bằng chứng đa luồng, đáng để biến (các) biến cũng thành luồng cục bộ (TLS).

Một biến lưu trữ Tên tệp (thu nhỏ). Một cái khác giữ giá trị không cắt mà __FILE__đã cho. Tập tin h:

static __declspec( thread ) const char* fileAndThreadLocal_strFilePath = NULL;
static __declspec( thread ) const char* fileAndThreadLocal_strFileName = NULL;

Bản thân macro gọi phương thức với tất cả logic:

#define __FILENAME__ \
    GetSourceFileName(__FILE__, fileAndThreadLocal_strFilePath, fileAndThreadLocal_strFileName)

Và chức năng được thực hiện theo cách này:

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 () có thể được triển khai theo nhiều cách khác nhau, nhưng dường như nhanh và đơn giản với 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;
}


2

chỉ hy vọng cải thiện macro FILE một chút:

#define FILE (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)

cái này bắt / và \, như Czarek Tomczak yêu cầu, và cái này hoạt động rất tốt trong môi trường hỗn hợp của tôi.


6
Xác định một macro có tên FILElà một ý tưởng thực sự tồi tệ nếu bạn bao gồm <stdio.h>.
Keith Thompson

tốt để biết tôi chỉ muốn hiển thị Czarek của tôi / giải pháp, vì vậy tôi không bận tâm với các kế hoạch đặt tên.
alexander golks

2

Thử

#pragma push_macro("__FILE__")
#define __FILE__ "foobar.c"

sau các câu lệnh bao gồm trong tệp nguồn của bạn và thêm

#pragma pop_macro("__FILE__")

ở cuối tập tin nguồn của bạn


2
push_macropop_macrokhông chuẩn. (gcc hỗ trợ chúng để tương thích với trình biên dịch Microsoft Windows.) Trong mọi trường hợp, không có điểm nào trong việc đẩy và bật định nghĩa về __FILE__; giá trị được khôi phục sẽ không được sử dụng sau khi kết thúc tệp nguồn. Một cách sạch hơn để thay đổi giá trị __FILE__#line __LINE__ "foobar.c"
Keith Thompson

1
Và điều này gây ra lỗi nội bộ trong bộ xử lý trước của gcc. gcc.gnu.org/ormszilla/show_orms.cgi?id=69665
Keith Thompson

1

Đây là một chức năng di động hoạt động cho cả Linux (đường dẫn '/') và Windows (kết hợp '\' và '/').
Biên dịch với gcc, clang và 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;
}

Đầu ra tiêu chuẩn:

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: 

1

Đây là giải pháp sử dụng tính toán thời gian biên dịch:

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__);

1

Nếu bạn đã kết thúc trên trang này để tìm cách xóa đường dẫn nguồn tuyệt đối đang chỉ đến vị trí xây dựng xấu xí từ nhị phân mà bạn đang vận chuyển, dưới đây có thể phù hợp với nhu cầu của bạn.

Mặc dù điều này không tạo ra chính xác câu trả lời mà tác giả đã bày tỏ mong muốn của mình vì nó giả định việc sử dụng CMake , nhưng nó đã trở nên khá gần gũi. Thật đáng tiếc điều này đã không được đề cập trước đó bởi bất cứ ai vì nó sẽ giúp tôi tiết kiệm rất nhiều thời gian.

OPTION(CMAKE_USE_RELATIVE_PATHS "If true, cmake will use relative paths" ON)

Đặt biến ở trên thành ONsẽ tạo lệnh xây dựng theo định dạng:

cd /ugly/absolute/path/to/project/build/src && 
    gcc <.. other flags ..> -c ../../src/path/to/source.c

Do đó, __FILE__macro sẽ giải quyết../../src/path/to/source.c

Tài liệu CMake

Cảnh giác với cảnh báo trên trang tài liệu mặc dù:

Sử dụng đường dẫn tương đối (Có thể không hoạt động!).

Nó không được đảm bảo để hoạt động trong mọi trường hợp, nhưng hoạt động trong mỏ của tôi - CMake 3.13 + gcc 4.5


Tài liệu CMake 3.4+ nói rằng "Biến này không có hiệu lực. Hiệu ứng được thực hiện một phần trong các bản phát hành trước đã bị xóa trong CMake 3.4." Nếu nó hoạt động với bạn với 3.13, có một lý do khác cho điều đó.
mike.dld

0

Vì bạn đang sử dụng GCC, bạn có thể tận dụng lợi thế của

__BASE_FILE__Macro này mở rộng thành tên của tệp đầu vào chính, dưới dạng hằng số chuỗi C. Đây là tệp nguồn được chỉ định trên dòng lệnh của trình biên dịch tiền xử lý hoặc trình biên dịch C

và sau đó kiểm soát cách bạn muốn hiển thị tên tệp bằng cách thay đổi biểu diễn tệp nguồn (đường dẫn đầy đủ / đường dẫn tương đối / tên cơ sở) tại thời điểm biên dịch.


6
không có sự khác biệt Tôi đã sử dụng __FILE____BASE_FILE__tuy nhiên cả hai đều hiển thị đường dẫn đầy đủ đến tệp
mahmood

Làm thế nào để bạn gọi trình biên dịch?
ziu

2
Sau đó, tôi đặt cược SCONS đang gọi gcc như thế này gcc /absolute/path/to/file.c. Nếu bạn tìm cách thay đổi hành vi này (mở một câu hỏi khác trên SO, lol?), Bạn không cần phải sửa đổi chuỗi khi chạy
ziu

17
Câu trả lời này sai 100%. __BASE_FILE__(như các tài liệu nói, mặc dù không rõ ràng) tạo ra tên của tệp được chỉ định trên dòng lệnh , ví dụ test.choặc /tmp/test.ctùy thuộc vào cách bạn gọi trình biên dịch. Điều đó hoàn toàn giống với __FILE__, ngoại trừ nếu bạn đang ở trong một tệp tiêu đề, trong trường hợp đó sẽ __FILE__tạo ra tên của tệp hiện tại (ví dụ foo.h) trong khi __BASE_FILE__vẫn tiếp tục sản xuất test.c.
Quuxplusone

0

Đây là một giải pháp hoạt động cho các môi trường không có thư viện chuỗi (nhân Linux, hệ thống nhúng, v.v.):

#define FILENAME ({ \
    const char* filename_start = __FILE__; \
    const char* filename = filename_start; \
    while(*filename != '\0') \
        filename++; \
    while((filename != filename_start) && (*(filename - 1) != '/')) \
        filename--; \
    filename; })

Bây giờ chỉ cần sử dụng FILENAMEthay vì __FILENAME__. Vâng, nó vẫn là một điều thời gian chạy nhưng nó hoạt động.


Có thể muốn kiểm tra cả '/' và '\', tùy thuộc vào nền tảng.
Technophile

0
#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

0

Câu trả lời ngắn gọn, hiệu quả cho cả Windows và * nix:

#define __FILENAME__ std::max<const char*>(__FILE__,\
    std::max(strrchr(__FILE__, '\\')+1, strrchr(__FILE__, '/')+1))

Tại sao bạn cần std::max<const char*>thay vì chỉ std::max?
chqrlie
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.