แมโคร __FILE__ แสดงเส้นทางแบบเต็ม


164

แมโครมาตรฐานที่กำหนดไว้ล่วงหน้าที่__FILE__มีอยู่ใน C แสดงพา ธ เต็มไปยังไฟล์ มีวิธีใดที่จะทำให้เส้นทางสั้นลงหรือไม่ ฉันหมายถึงแทนที่จะ

/full/path/to/file.c

ฉันเห็น

to/file.c

หรือ

file.c

18
มันจะยอดเยี่ยมจริงๆในการค้นหาโซลูชัน preprocessor เท่านั้น ฉันเกรงว่าคำแนะนำที่อิงตามการทำงานของสตริงจะดำเนินการตอนรันไทม์
cdleonard

9
เนื่องจากคุณใช้ gcc ฉันคิดว่าคุณสามารถเปลี่ยนสิ่งที่__FILE__มีอยู่ได้โดยเปลี่ยนชื่อไฟล์ที่คุณส่งผ่านบรรทัดคำสั่ง ดังนั้นแทนที่จะพยายามgcc /full/path/to/file.c cd /full/path/to; gcc file.c; cd -;แน่นอนว่ามีมากกว่านั้นถ้าคุณใช้ไดเรกทอรีปัจจุบันของ gcc สำหรับพา ธ include หรือตำแหน่งไฟล์เอาต์พุต แก้ไข: เอกสาร gcc แนะนำว่าเป็นเส้นทางแบบเต็มไม่ใช่อาร์กิวเมนต์ชื่อไฟล์อินพุต แต่นั่นไม่ใช่สิ่งที่ฉันเห็นสำหรับ gcc 4.5.3 ใน Cygwin ดังนั้นคุณอาจลองบน Linux และดู
Steve Jessop

4
GCC 4.5.1 (สร้างขึ้นสำหรับ arm-none-eabi โดยเฉพาะ) ใช้ข้อความที่แน่นอนของชื่อไฟล์ในบรรทัดคำสั่ง ในกรณีของฉันมันเป็นความผิดของ IDE ในการเรียกใช้ GCC ด้วยชื่อไฟล์ทั้งหมดที่ผ่านการรับรองโดยสมบูรณ์แทนที่จะวางไดเรกทอรีปัจจุบันไว้ที่ใดที่หนึ่ง (ตำแหน่งของไฟล์โครงการอาจ?) หรือกำหนดค่าได้และใช้เส้นทางสัมพัทธ์จากที่นั่น ฉันสงสัยว่า IDEs จำนวนมากทำเช่นนั้น (โดยเฉพาะอย่างยิ่งใน Windows) จากความไม่สะดวกบางอย่างที่เกี่ยวข้องกับการอธิบายว่าไดเรกทอรี "ปัจจุบัน" นั้นเป็นจริงสำหรับแอป GUI หรือไม่
RBerteig

3
@SteveJessop - หวังว่าคุณจะอ่านความคิดเห็นนี้ ฉันมีสถานการณ์ที่ฉันเห็น__FILE__พิมพ์เป็น../../../../../../../../rtems/c/src/lib/libbsp/sparc/leon2/../../shared/bootcard.cและฉันต้องการที่จะรู้ว่า gcc รวบรวมไฟล์ดังกล่าวว่าไฟล์นี้อยู่ค่อนข้างที่จะปรากฏ
Chan Kim

1
คำถามนี้ไม่ใช่คำถามที่ถูกเชื่อมโยง สำหรับหนึ่งอันที่ถูกเชื่อมโยงนั้นเป็นเรื่องเกี่ยวกับ C ++ และคำตอบจะนำไปสู่ความลึกลับของแมโคร C ++ ประการที่สองไม่มีอะไรในคำถามของ OP ที่กำหนดโซลูชันแมโคร เพียงชี้ปัญหาอย่างจริงจังและถามคำถามปลายเปิด
ศ. Falken

คำตอบ:


166

ลอง

#include <string.h>

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

สำหรับ Windows ให้ใช้ '\\' แทน '/'


13
/เป็นตัวแยกพา ธ ที่ถูกต้องใน Windows
Hans Passant

11
/เป็นตัวคั่นพา ธ ที่ถูกต้องในชื่อไฟล์ที่ส่งผ่านไปยัง CreateFile () และอื่น ๆ อย่างไรก็ตามนั่นไม่ได้หมายความว่าคุณสามารถใช้/ที่ใดก็ได้ใน Windows เนื่องจากมีประเพณี (สืบทอดมาจาก CPM) ของการใช้/เป็นอาร์กิวเมนต์นำในที่พร้อมท์คำสั่ง แต่เครื่องมือที่มีคุณภาพจะต้องระมัดระวังในการแยกชื่อไฟล์ที่ทั้งเฉือนและทับขวาตัวอักษรเพื่อหลีกเลี่ยงปัญหาใด ๆ /สำหรับคนที่จะจัดการกับการใช้งาน
RBerteig

5
@AmigableClarkKant ไม่สามารถผสมตัวคั่นทั้งสองในชื่อไฟล์เดียวกันได้
RBerteig

2
หากแพลตฟอร์มของคุณรองรับchar* fileName = basename(__FILE__); มันแน่นอนว่ามีใน Linux และ OS X ไม่ทราบเกี่ยวกับ Windows
JeremyP

14
strrchr("/" __FILE__, '/') + 1นี้อาจจะลงไป ด้วยการเพิ่ม "/" ถึง__FILE__, strrchr จะรับประกันว่าจะพบบางสิ่งบางอย่างและทำให้มีเงื่อนไขหรือไม่: ไม่จำเป็นอีกต่อไป
uroeuroburɳ

54

นี่คือเคล็ดลับถ้าคุณใช้ cmake จาก: 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 ฉันไม่เห็นเหตุผลที่คุณไม่สามารถขยายสิ่งนี้ไปยัง makefiles ของคุณเองได้ ตัวอย่างเช่นคุณอาจมีบรรทัดเช่นนี้:

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

ที่ไหน $(SOURCE_PREFIX)เป็นคำนำหน้าที่คุณต้องการลบ

จากนั้นใช้ในสถานที่ของ__FILENAME____FILE__


11
ฉันกลัวว่านี่จะไม่ทำงานกับไฟล์ที่อ้างอิงในไฟล์ส่วนหัว
Baiyan Huang

2
เห็นด้วยกับ @BaiyanHuang แต่ไม่แน่ใจว่าความคิดเห็นนั้นชัดเจน __FILE__ไม่ใช่สัญลักษณ์ preprocessor ธรรมดามันเปลี่ยนเป็นไฟล์ปัจจุบันมักจะใช้สำหรับการเปล่งชื่อของไฟล์ปัจจุบัน (ส่วนหัวหรือโมดูลแหล่งที่มา) นี้__FILENAME__เท่านั้นที่จะมีแหล่งที่มาของสุด
nhed

3
คำตอบของคำตอบนี้ไม่สามารถเคลื่อนย้ายได้เนื่องจากมันใช้บอร์นเชลล์หนี ดีกว่าที่จะใช้ CMake เพื่อใช้งานในวิธีที่สะอาดและพกพาได้ ดูมาโคร define_file_basename_for_sourcesที่นี่
โคลินดีเบนเน็ตต์

3
GNU สร้างความแตกต่างของสิ่งนี้คือ CFLAGS + = -D__FILENAME __ = \ "$ (notdir $ <) \"
ctuffli

4
@firegurafiku บนแพลตฟอร์มที่ฝังตัวขนาดรหัสมักจะมีข้อ จำกัด มากกว่าความเร็ว หากคุณมีคำสั่ง debug ในแต่ละไฟล์นั่นสามารถเพิ่มขนาดไฟล์ได้อย่างรวดเร็วด้วยสตริงแบบเต็มพา ธ ฉันกำลังจัดการกับแพลตฟอร์มดังกล่าวในขณะนี้และการอ้างอิง__FILE__ทุกที่ก็ทำให้พื้นที่โค้ดหมดไป
mrtumnus

26

ฉันเพิ่งคิดว่าโซลูชันที่ยอดเยี่ยมสำหรับการทำงานกับทั้งซอร์สไฟล์และส่วนหัวนั้นมีประสิทธิภาพมากและทำงานในเวลารวบรวมในทุกแพลตฟอร์มโดยไม่มีส่วนขยายเฉพาะคอมไพเลอร์ โซลูชันนี้ยังคงรักษาโครงสร้างไดเรกทอรีสัมพัทธ์ของโครงการของคุณเพื่อให้คุณทราบว่าแฟ้มอยู่ในโฟลเดอร์ใดและสัมพันธ์กับรากของโครงการของคุณเท่านั้น

แนวคิดคือการทำให้ขนาดของไดเรกทอรีต้นทางด้วยเครื่องมือสร้างของคุณและเพิ่มลงใน__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_DIRCMAKE_SOURCE_DIR


4
ฉันไม่เข้าใจในตอนแรก ฉันเขียนโค้ดตัวอย่างขึ้นมาแล้ววิ่งแข่งกับ gcc และเสียงดังกราวและมันก็ใช้ได้ ฉันยังทดลองด้วยการต่อท้ายค่าตัวเลขที่หลากหลายและทำงานตามที่คาดไว้ จากนั้นในที่สุดก็ถึงฉัน __FILE__เป็นตัวชี้ไปยังอาร์เรย์ของไบต์ ดังนั้นการเพิ่มตัวอักษรตัวเลขจึงเป็นแค่การเพิ่มพอยน์เตอร์ ฉลาดมาก @ RenatoUtsch
Daniel

มันทำงานเฉพาะในกรณีที่ไฟล์ต้นฉบับอยู่ภายใต้ไดเรกทอรีรายการ cmake หากไฟล์ต้นฉบับอยู่ด้านนอกไฟล์นั้นจะหยุดทำงานอาจมีการเข้าถึงภายนอกสตริงตัวอักษร ดังนั้นระวังด้วย
Andry

จริง ๆ แล้วมันไม่ลดขนาดรหัส ฉันคิดว่าเส้นทางทั้งหมดยังคงถูกรวบรวมเป็นไบนารี่เพียงแค่ตัวชี้ถูกแก้ไข
Tarion

ทั้ง__FILE__แมโครและมาโคร SOURCE_PATH_SIZE เป็นค่าคงที่ที่ทราบ ณ เวลารวบรวม ฉันคาดว่าคอมไพเลอร์การปรับให้เหมาะสมที่ทันสมัยเพื่อให้สามารถตรวจพบว่าส่วนหนึ่งของสตริงไม่ได้ใช้และเพียงแค่ลบออกจากไบนารี อย่างไรก็ตามฉันไม่คิดว่าไบต์สองสามตัวนี้จะสร้างความแตกต่างอย่างมีนัยสำคัญในขนาดไบนารี่ดังนั้นฉันจะไม่แคร์เรื่องนั้นจริงๆ
RenatoUtsch

@RenatoUtsch โครงการที่ฉันทำงานอยู่มีการเปลี่ยนแปลงซึ่งเพิ่งระบุชื่อไฟล์ แต่ก็มีข้อเสียในการตั้งชื่อไฟล์ C ให้กับส่วนหัวด้วย การเปลี่ยนแปลงเกิดขึ้นเพื่อให้ได้บิลด์ที่ทำซ้ำได้ ดังนั้นด้วย gcc กับ -O2 สตริงนั้นจะได้รับการปรับให้เหมาะสมที่สุดหรือไม่และการสร้างจะทำซ้ำได้หรือไม่
Paul Stelian

18

รวบรวมเวลาแก้ปัญหาอย่างหมดจดที่นี่ มันขึ้นอยู่กับข้อเท็จจริงที่ว่าsizeof()สตริงตัวอักษรส่งคืนความยาว + 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__)

อย่าลังเลที่จะขยายตัวดำเนินการตามเงื่อนไขให้เป็นชื่อไฟล์ที่เหมาะสมที่สุดในโครงการ ความยาวเส้นทางไม่สำคัญตราบใดที่คุณตรวจสอบไกลพอจากจุดสิ้นสุดของสตริง

ฉันจะดูว่าฉันจะได้รับแมโครที่คล้ายกันโดยไม่มีความยาวฮาร์ดโค้ดพร้อมการเรียกซ้ำแมโคร ...


3
คำตอบที่ยอดเยี่ยม คุณต้องใช้อย่างน้อย-O1เพื่อใช้ในการรวบรวมเวลา
Digital Trauma

1
ไม่ย้อนหลังหรือ คุณต้องการค้นหาการเกิดขึ้นครั้งสุดท้ายของ '/' ซึ่งหมายความว่าคุณควรเริ่มต้นด้วยการsizeof(s) > 2ตรวจสอบก่อน นอกจากนี้สิ่งนี้ใช้ไม่ได้กับการคอมไพล์เวลาสำหรับฉันที่ -Os สตริงพา ธ แบบเต็มมีอยู่ในเอาต์พุตไบนารี
mrtumnus

15

อย่างน้อยสำหรับ 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"

ปัญหาเดียวของสิ่งนี้คือมันกำหนดหมายเลขบรรทัดที่ระบุให้กับบรรทัดต่อไปนี้และอาร์กิวเมนต์แรกถึง#lineจะต้องเป็นลำดับตัวเลขดังนั้นคุณจึงไม่สามารถทำสิ่งต่าง ๆ เช่น

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

ตรวจสอบให้แน่ใจว่าชื่อไฟล์ใน #lineคำสั่งตรงกับชื่อที่แท้จริงของไฟล์ที่เหลือเป็นแบบฝึกหัด

อย่างน้อยสำหรับ gcc สิ่งนี้จะมีผลกับชื่อไฟล์ที่รายงานในข้อความวินิจฉัย


1
Keith Thompson เป็นโซลูชั่นที่ยอดเยี่ยมขอบคุณ ปัญหาเดียวก็คือว่าดูเหมือนว่าแมโครนี้ตัด__LINE__ค่าโดยหนึ่ง ดังนั้น__LINE__การเขียนในบรรทัดxGEST x-1ประเมินเพื่อ อย่างน้อยกับ gcc 5.4
elklepo

1
@Klepak: ถูกต้องและนั่นเป็นพฤติกรรมมาตรฐาน คำสั่ง "ทำให้การใช้งานประพฤติราวกับว่าลำดับของบรรทัดซอร์สต่อไปนี้เริ่มต้นด้วยบรรทัดซอร์สที่มีหมายเลขบรรทัดตามที่ระบุโดยลำดับหลัก" และมันจะต้องมีหลักลำดับ#line __LINE__-1, "file.c"ดังนั้นคุณจึงไม่สามารถใช้ ฉันจะอัปเดตคำตอบของฉัน
Keith Thompson

1
จะเป็นอย่างไรสำหรับคอมไพเลอร์อื่น ๆ เช่น Clang, MSVC หรือ Intel C Compiler
Franklin Yu

8

สายไปงานเลี้ยง แต่สำหรับGCCมี-ffile-prefix-map=old=newตัวเลือกดู:

เมื่อรวบรวมไฟล์ที่อยู่ในไดเรกทอรีเก่าให้บันทึกการอ้างอิงใด ๆ ในผลลัพธ์ของการรวบรวมราวกับว่าไฟล์อยู่ในไดเรกทอรีใหม่แทน การระบุตัวเลือกนี้เทียบเท่ากับการระบุ-f*-prefix-mapตัวเลือกทั้งหมด สิ่งนี้สามารถนำมาใช้เพื่อสร้างการทำซ้ำที่เป็นสถานที่ที่เป็นอิสระ ดูเพิ่มเติม และ-fmacro-prefix-map-fdebug-prefix-map

ดังนั้นสำหรับเจนกินส์ของฉันฉันจะสร้าง-ffile-prefix-map=${WORKSPACE}/=/และอีกอันเพื่อลบคำนำหน้าการติดตั้งแพคเกจ dev ท้องถิ่น

หมายเหตุน่าเสียดายที่-ffile-prefix-mapตัวเลือกนั้นมีเฉพาะใน GCC 8 เป็นต้นไป-fmacro-prefix-mapซึ่งฉันคิดว่าเป็น__FILE__ส่วนหนึ่ง เพราะพูด GCC 5 เรามีเพียง-fdebug-prefix-mapซึ่งไม่ได้ (ปรากฏ) __FILE__ส่งผลกระทบต่อ


1
ตัวเลือก-ffile-prefix-mapหมายถึงทั้งตัวเลือก-fdebug-prefix-mapและ-fmacro-prefix-mapตัวเลือก ดูการอ้างอิงได้ที่reproducible-builds.org/docs/build-pathบั๊ก GCC ที่ติดตาม-fmacro-prefix-mapและ-ffile-prefix-mapเป็นgcc.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__)]);
    }

รหัสสร้างการคอมไพล์เวลาชดเชยเมื่อ:

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

https://godbolt.org/z/u6s8j3


ผู้ชายนั่นสวยงามและใช้งานได้ดีขอบคุณ! ไม่รู้เลยว่าทำไมคำตอบนี้จึงน้อยเกินไป
โรมัน


4

ไม่มีเวลารวบรวมในการทำเช่นนี้ เห็นได้ชัดว่าคุณสามารถทำได้ที่รันไทม์โดยใช้ C runtime เนื่องจากคำตอบอื่น ๆ แสดงให้เห็น แต่ในเวลาคอมไพล์เมื่อผู้สนับสนุนก่อนเตะเข้าคุณก็โชคไม่ดี


1
strrchrคำตอบที่สามารถฟังได้รับการคำนวณที่รวบรวมเวลาแม้ว่าแน่นอนยังไม่ได้โดยพรีโพรเซสเซอร์ ฉันไม่รู้ว่า gcc จริงหรือไม่ฉันยังไม่ได้ตรวจสอบ แต่ฉันค่อนข้างแน่ใจว่ามันจะคำนวณstrlenตัวอักษรสตริงที่รวบรวมเวลา
Steve Jessop

@ Steve - อาจเป็นไปได้ แต่นั่นเป็นสิ่งที่ต้องพึ่งพาอย่างมากสำหรับพฤติกรรมเฉพาะของคอมไพเลอร์
ฌอน

ฉันไม่คิดว่ามันเป็นการพึ่งพาที่ยิ่งใหญ่เพราะฉันสงสัยอย่างมากว่ารหัสนี้มีประสิทธิภาพที่สำคัญ และถ้าเป็นก็ให้ย้ายออกจากวง ในกรณีที่นี่เป็นเรื่องใหญ่เพราะคุณต้องการสตริงตัวอักษรที่มีเพียงชื่อฐานคุณอาจจะสามารถคำนวณสตริงที่ถูกต้องในเวลาที่สร้างโดยการเรียกใช้สคริปต์บางส่วนเหนือแหล่งที่มา
Steve Jessop

5
มันอาจไม่ได้มีประสิทธิภาพที่สำคัญ แต่ก็สามารถเห็นได้อย่างง่ายดายว่าเป็นความเป็นส่วนตัวที่สำคัญ ไม่มีเหตุผลที่ดีสำหรับการเปิดเผยการปฏิบัติต่อลูกค้าของฉันในสตริงที่ถูกตรึงไว้ในไฟล์ EXE ที่วางจำหน่าย ยิ่งไปกว่านั้นสำหรับแอปพลิเคชันที่สร้างขึ้นในนามของลูกค้าสตริงเหล่านั้นอาจเปิดเผยสิ่งที่ลูกค้าของฉันอาจไม่ชอบเช่นไม่เป็นผู้แต่งผลิตภัณฑ์ของตนเอง เนื่องจาก__FILE__มีการเรียกใช้โดยปริยายโดยassert()การรั่วไหลนี้สามารถเกิดขึ้นได้โดยไม่มีการกระทำใด ๆ ที่ชัดเจน
RBerteig

@Rertertig basename ของ__FILE__ตัวเองอาจเปิดเผยสิ่งที่ลูกค้าอาจไม่ชอบดังนั้นการใช้__FILE__ที่ใดก็ได้เลย - ไม่ว่าจะมีชื่อพา ธ สัมบูรณ์หรือเพียงแค่ชื่อเต็ม - มีปัญหาเดียวกันกับที่คุณชี้ให้เห็น ในสถานการณ์เช่นนี้ผลผลิตทั้งหมดจะต้องได้รับการพิจารณาและควรใช้ API พิเศษสำหรับผลผลิตให้กับลูกค้า ส่วนที่เหลือของเอาต์พุตควรถูกดัมพ์ไปยัง / dev / NULL หรือ stdout และ stderr ควรถูกปิด :-)
tchen

4

ใช้basename ()ฟังก์ชั่นหรือหากคุณอยู่บน Windows, _splitpath ()

#include <libgen.h>

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

ลองman 3 basenameในเปลือกด้วย


2
char file_copy[] = __FILE__; const char *filename = basename(__FILE__);@mahmood: เหตุผลสำหรับการคัดลอกคือชื่อฐานสามารถแก้ไขสตริงอินพุตได้ คุณต้องระวังว่าตัวชี้ผลลัพธ์นั้นดีจนกระทั่งbasenameมีการเรียกอีกครั้ง ซึ่งหมายความว่าไม่ปลอดภัยสำหรับเธรด
Steve Jessop

@SteveJessop อ่าฉันลืมไปแล้ว จริง
ศ. Falken

1
@ เหมาะสม: เพื่อความยุติธรรมฉันสงสัยว่าbasenameอันที่จริงแล้วจะไม่แก้ไขสตริงอินพุตที่เป็นผลมา__FILE__เนื่องจากสตริงอินพุตไม่มี/จุดสิ้นสุดดังนั้นจึงไม่จำเป็นต้องแก้ไข ดังนั้นคุณอาจจะไปกับมัน แต่ฉันคิดว่าครั้งแรกที่มีคนเห็นbasenameพวกเขาควรจะเห็นมันด้วยข้อ จำกัด ทั้งหมด
Steve Jessop

@SteveJessop BSd man page สำหรับ basename () พูดถึงว่า basename รุ่นดั้งเดิม () ใช้ const char * และไม่แก้ไขสตริง หน้าลินุกซ์กล่าวถึงอะไรเกี่ยวกับ const แต่กล่าวถึงว่ามันสามารถกลับเป็นส่วนหนึ่งของสตริงอาร์กิวเมนต์ ดังนั้นควรจัดการกับ basename () อย่างระมัดระวัง
ศ. Falken

@SteveJessop, ฮิฮิฉันเพียงตอนนี้หลังจากดูความคิดเห็นของคุณอย่างระมัดระวังหลังจากสี่ปีตระหนักว่า/ในตอนท้ายของสตริงหมายความว่าชื่อฐานอาจมีเหตุผลที่ดีในการแก้ไขข้อโต้แย้ง
ศ. Falken

4

หากคุณใช้CMAKEกับคอมไพเลอร์ GNU globalคำจำกัดความนี้จะทำงานได้ดี:

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

4

ฉันใช้คำตอบเดียวกันกับ @Patrick มาหลายปีแล้ว

มันมีปัญหาเล็กน้อยเมื่อเส้นทางแบบเต็มประกอบด้วยสัญลักษณ์ลิงค์

ทางออกที่ดีกว่า

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__แมโครใหม่

    สำหรับคอมไพเลอร์เหล่านั้นไม่สนับสนุนสิ่งนี้ให้อ้างอิงกับวิธีที่แข็งแกร่งด้านล่าง

  • ตัดเส้นทางโครงการจากเส้นทางไฟล์เป็นความต้องการที่แท้จริงของคุณ คุณจะไม่ต้องการที่จะเสียเวลาในการหาที่เป็นheader.hไฟล์หรือsrc/foo/header.hsrc/bar/header.h

  • เราควรถอด__FILE__มาโครออกcmakeไฟล์ปรับแต่ง

    แมโครนี้ใช้ในรหัสที่มีอยู่ส่วนใหญ่ เพียงกำหนดใหม่ก็สามารถตั้งคุณฟรี

    คอมไพเลอร์ชอบgccกำหนดมาโครนี้จากอาร์กิวเมนต์บรรทัดคำสั่ง และเส้นทางแบบเต็มจะถูกเขียนในmakefiles cmakeสร้างขึ้นโดย

  • CMAKE_*_FLAGSจำเป็นต้องใช้รหัสฮาร์ด

    มีคำสั่งบางอย่างเพื่อเพิ่มตัวเลือกคอมไพเลอร์หรือคำจำกัดความในบางรุ่นเมื่อเร็ว ๆ นี้เหมือนเป็นและadd_definitions() add_compile_definitions()คำสั่งเหล่านี้จะแยกฟังก์ชั่นการทำเหมือนsubstก่อนนำไปใช้กับไฟล์ต้นฉบับ นั่นไม่ใช่ที่เราต้องการ

-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__=...)บรรทัด


สิ่งนี้ไม่ทำงานสำหรับเนื้อหาที่มาจากไฟล์รวม
chqrlie

คุณสามารถโพสต์รหัสได้ไหม กรณีทั่วไปคือการตั้งค่าตัวแปรในขอบเขตท้องถิ่นและใช้ในอื่น
Levi.G

ตัวอย่างเช่นถ้าคุณใช้__FILE__กับคำจำกัดความของคุณในการสร้างการวินิจฉัยในฟังก์ชั่นแบบอินไลน์ที่กำหนดไว้ในไฟล์ส่วนหัวการวินิจฉัยรันไทม์จะรายงานชื่อของไฟล์ที่ส่งไปยังคอมไพเลอร์แทนชื่อของไฟล์รวมในขณะที่หมายเลขบรรทัดจะ อ้างถึงไฟล์ include
chqrlie

#define LOG(fmt, args...) printf("%s " fmt, __FILE__, ##args)ใช่มันออกแบบมาเพื่อเป็นที่สำหรับการใช้งานที่พบมากที่สุดคือ เมื่อใช้LOG()แมโครคุณไม่ต้องการเห็นlog.hในข้อความจริงๆ ท้ายที่สุด__FILE__แมโครจะถูกขยายในทุกไฟล์ C / Cpp (หน่วยคอมไพล์) แทนไฟล์ที่รวม
Levi.G

3

ความแตกต่างเล็กน้อยในสิ่งที่ @ 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) แทนแต่ละครั้งที่มีการอ้างอิงแมโคร

มันถูก จำกัด ให้ใช้เฉพาะจากไฟล์. c (pp) และจะไม่สามารถใช้งานได้จากไฟล์ส่วนหัว


3

ฉันทำมาโคร __FILENAME__เพื่อหลีกเลี่ยงการตัดเส้นทางเต็มในแต่ละครั้ง ปัญหาคือเก็บชื่อไฟล์ผลลัพธ์ไว้ในตัวแปร cpp-local

สามารถทำได้อย่างง่ายดายโดยการกำหนดตัวแปรโกลบอลแบบคงที่ในไฟล์. h คำจำกัดความนี้ให้ตัวแปรที่แยกต่างหากและเป็นอิสระในแต่ละไฟล์. cppที่มี. h .hเพื่อที่จะเป็นมัลติเธรดที่พิสูจน์แล้วมันมีค่าที่จะทำให้ตัวแปรยังคงเป็นเธรดโลคัล (TLS)

ตัวแปรหนึ่งเก็บชื่อไฟล์ (หด) อีกคนหนึ่งถือค่าที่ไม่ได้ตัดที่__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;
}


2

แค่หวังว่าจะปรับปรุง FILE macro สักหน่อย:

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

สิ่งนี้ดึงดูด / และ \, เหมือน Czarek Tomczak ที่ร้องขอและสิ่งนี้ใช้ได้ดีในสภาพแวดล้อมแบบผสมของฉัน


6
การนิยามแมโครที่มีชื่อว่าFILEเป็นความคิดที่แย่มากถ้าคุณรวม<stdio.h>ไว้
Keith Thompson

ดีแล้วที่รู้. ฉันแค่ต้องการแสดง Czarek \\ / solution ดังนั้นฉันจึงไม่ต้องกังวลกับแผนการตั้งชื่อ
alexander golks

2

ลอง

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

หลังจากคำสั่ง include ในไฟล์ต้นฉบับของคุณและเพิ่ม

#pragma pop_macro("__FILE__")

ในตอนท้ายของไฟล์ต้นฉบับของคุณ


2
push_macroและpop_macroไม่ได้มาตรฐาน (gcc รองรับการทำงานร่วมกันได้กับคอมไพเลอร์ Microsoft Windows) ไม่ว่าในกรณีใด ๆ ไม่มีจุดที่จะผลักดันและกำหนดคำจำกัดความของ__FILE__; ค่าที่กู้คืนจะไม่ถูกใช้หลังจากสิ้นสุดไฟล์ต้นฉบับแล้ว วิธีเปลี่ยนค่าที่สะอาดกว่า__FILE__คือ#line __LINE__ "foobar.c"
Keith Thompson

1
และสิ่งนี้ทำให้เกิดข้อผิดพลาดภายในในตัวประมวลผลล่วงหน้าของ gcc gcc.gnu.org/bugzilla/show_bug.cgi?id=69665
Keith Thompson

1

นี่คือฟังก์ชั่นแบบพกพาที่ใช้งานได้กับทั้ง 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: 

1

นี่คือโซลูชันที่ใช้การคำนวณเวลาคอมไพล์:

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

หากคุณลงเอยด้วยหน้านี้มองหาวิธีที่จะลบพา ธ ของแหล่งที่มาแบบสัมบูรณ์ซึ่งชี้ไปที่สถานที่สร้างน่าเกลียดจากไบนารีที่คุณกำลังจัดส่งด้านล่างอาจเหมาะกับความต้องการของคุณ

แม้ว่าสิ่งนี้จะไม่ได้คำตอบที่ผู้เขียนแสดงความต้องการอย่างแน่นอนเพราะมันใช้ CMakeแต่ก็ใกล้เคียงกันมาก มันน่าเสียดายที่ทุกคนไม่ได้เอ่ยถึงก่อนหน้านี้เพราะมันช่วยให้ฉันประหยัดเวลาได้มาก

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

การตั้งค่าตัวแปรด้านบนเพื่อONสร้างคำสั่ง build ในรูปแบบ:

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

เป็นผลให้__FILE__แมโครจะแก้ไข../../src/path/to/source.c

เอกสารประกอบ CMake

ระวังคำเตือนในหน้าเอกสารแม้ว่า:

ใช้เส้นทางสัมพัทธ์ (อาจไม่ทำงาน!)

ไม่รับประกันว่าจะทำงานได้ในทุกกรณี แต่ทำงานในเหมือง - CMake 3.13 + gcc 4.5


CMake 3.4+ ระบุว่า "ตัวแปรนี้ไม่มีผลใด ๆ เอฟเฟกต์ที่นำไปใช้บางส่วนที่มีในรีลีสก่อนหน้านี้ถูกลบออกใน CMake 3.4" ถ้ามันใช้ได้กับคุณด้วย 3.13 ก็มีอีกเหตุผลหนึ่งที่
mike.dld

0

เนื่องจากคุณใช้ GCC คุณสามารถใช้ประโยชน์จาก

__BASE_FILE__แมโครนี้ขยายเป็นชื่อของไฟล์อินพุตหลักในรูปแบบของค่าคงที่สตริง C นี่คือไฟล์ต้นฉบับที่ระบุไว้ในบรรทัดคำสั่งของตัวประมวลผลล่วงหน้าหรือคอมไพเลอร์ C

จากนั้นควบคุมวิธีที่คุณต้องการแสดงชื่อไฟล์โดยเปลี่ยนการแสดงไฟล์ต้นฉบับ (เส้นทางแบบเต็ม / เส้นทางแบบเต็ม / เส้นทางสัมพัทธ์ / ชื่อฐาน) ในเวลารวบรวม


6
ทำให้ไม่มีความแตกต่าง ฉันใช้__FILE__และ__BASE_FILE__อย่างไรก็ตามพวกเขาทั้งสองแสดงพา ธ แบบเต็มไปยังไฟล์
mahmood

คุณเรียกคอมไพเลอร์ได้อย่างไร
ziu

2
จากนั้นฉันเดิมพัน scons จะเรียก GCC gcc /absolute/path/to/file.cเช่นนี้ หากคุณพบวิธีที่จะเปลี่ยนพฤติกรรมนี้ (เปิดคำถามอื่นใน SO, lol?) คุณไม่จำเป็นต้องแก้ไขสตริงที่รันไทม์
ziu

17
คำตอบนี้ผิด 100% __BASE_FILE__(ดังที่เอกสารระบุไว้แม้ว่าจะไม่ชัดเจน) จะสร้างชื่อของไฟล์ที่ระบุในบรรทัดคำสั่งเช่นtest.cหรือ/tmp/test.cขึ้นอยู่กับว่าคุณเรียกใช้คอมไพเลอร์อย่างไร นั่นคือสิ่งเดียวกับ__FILE__ยกเว้นถ้าคุณในไฟล์ส่วนหัวซึ่งในกรณีที่__FILE__ผลิตชื่อของไฟล์ปัจจุบัน (เช่นfoo.h) ในขณะที่ยังคงผลิต__BASE_FILE__ test.c
Quuxplusone

0

ต่อไปนี้เป็นโซลูชันที่ใช้งานได้กับสภาพแวดล้อมที่ไม่มีไลบรารีสตริง (เคอร์เนล 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__ใช่มันยังคงเป็นเรื่องรันไทม์ แต่ใช้งานได้


อาจต้องการตรวจสอบทั้ง '/' และ '\' ขึ้นอยู่กับแพลตฟอร์ม
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

คำตอบสั้น ๆ ที่ใช้งานได้สำหรับทั้ง Windows และ * nix:

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

ทำไมคุณต้องการstd::max<const char*>แทนที่จะเป็นเพียงแค่std::max?
chqrlie
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.