นับจำนวนไฟล์ซอร์สโดยใช้มาโครหรือไม่


15

เป็นไปได้หรือไม่ที่จะใช้ตัวประมวลผลล่วงหน้า C / C ++ เพื่อนับบรรทัดภายในไฟล์ต้นฉบับไปเป็นมาโครหรือค่าคอมไพล์เวลาที่รวบรวมได้ เช่นฉันสามารถแทนที่MAGIC1, MAGIC2และMAGIC3ในต่อไปนี้และได้รับค่า 4 อย่างใดเมื่อใช้MAGIC3?

MAGIC1 // can be placed wherever you like before the relevant 
       // lines - either right before them, or in global scope etc.
foo(); MAGIC2
bar(); MAGIC2
baz(); MAGIC2
quux(); MAGIC2
// ... possibly a bunch of code here; not guaranteed to be in same scope ...
MAGIC3

หมายเหตุ:

  • ส่วนขยายคอมไพเลอร์เฉพาะสำหรับความสามารถของตัวประมวลผลล่วงหน้าเป็นที่ยอมรับ แต่ไม่พึงประสงค์
  • หากเป็นไปได้ด้วยความช่วยเหลือของ C ++ บางตัวซึ่งตรงข้ามกับ C, สร้างมันก็เป็นที่ยอมรับ แต่ไม่เป็นที่พึงปรารถนา (เช่นฉันต้องการบางสิ่งที่เหมาะกับ C)
  • เห็นได้ชัดว่าสิ่งนี้สามารถทำได้โดยการเรียกใช้ไฟล์ต้นฉบับผ่านสคริปต์โปรเซสเซอร์ภายนอกบางตัว แต่นั่นไม่ใช่สิ่งที่ฉันขอ

6
มีมาโครชื่อ__LINE__ที่แสดงถึงจำนวนบรรทัดปัจจุบัน
ForceBru

2
กำลังค้นหา__COUNTER__และ / หรือBOOST_PP_COUNTER?
KamilCuk

11
อะไรคือสิ่งที่เกิดขึ้นจริงปัญหาที่คุณต้องแก้ปัญหา? ทำไมคุณต้องการสิ่งนี้
โปรแกรมเมอร์บางคนเพื่อน

1
ไม่นี้ความช่วยเหลือ?
user1810087

1
@PSkocik: ฉันต้องการบางสิ่งบางอย่างที่ฉันสามารถใช้เป็นค่าคงที่เวลารวบรวมเช่นสำหรับการพูดint arr[MAGIC4]และรับจำนวนบรรทัดในบางส่วนก่อนหน้านี้นับรหัสของฉัน
einpoklum

คำตอบ:


15

มี__LINE__มาโครตัวประมวลผลล่วงหน้าที่ให้จำนวนเต็มสำหรับบรรทัดปรากฏบน คุณสามารถหาค่าของมันในบางบรรทัดและต่อมาก็เป็นบางบรรทัดและเปรียบเทียบ

static const int BEFORE = __LINE__;
foo();
bar();
baz();
quux();
static const int AFTER = __LINE__;
static const int COUNT = AFTER - BEFORE - 1; // 4

หากคุณต้องการนับจำนวนสิ่งที่เกิดขึ้นมากกว่าบรรทัดของแหล่งข้อมูล__COUNTER__อาจเป็นตัวเลือกที่ไม่ได้มาตรฐานรองรับโดยคอมไพเลอร์บางตัวเช่น GCC และ MSVC

#define MAGIC2_2(c)
#define MAGIC2(c) MAGIC2_2(c)
static const int BEFORE = __COUNTER__;
void foo(); MAGIC2(__COUNTER__);
void bar(
    int multiple,
    float lines); MAGIC2(__COUNTER__);
void baz(); MAGIC2(__COUNTER__);
void quux(); MAGIC2(__COUNTER__);
static const int AFTER = __COUNTER__;
static const int COUNT = AFTER - BEFORE - 1; // 4

ฉันใช้ค่าเริ่มต้นเป็น__COUNTER__เพราะมันอาจถูกนำมาใช้ก่อนหน้านี้ในไฟล์ต้นฉบับหรือบางส่วนรวมอยู่ด้วย

ใน C มากกว่า C ++ มีข้อ จำกัด เกี่ยวกับตัวแปรคงที่ดังนั้นenumอาจใช้แทน

enum MyEnum
{
    FOO = COUNT // C: error: enumerator value for ‘FOO’ is not an integer constant
};

แทนที่ const ด้วยenum:

enum {BEFORE = __LINE__};
foo();
bar();
baz();
quux();
enum { COUNT = __LINE__ - BEFORE - 1};
enum MyEnum
{
    FOO = COUNT // OK
};

ฉันคิดว่านี่เป็นเรื่องที่ใกล้เคียงที่สุดเท่าที่คุณจะทำได้ด้วย preprocessor ตัวประมวลผลล่วงหน้าคือ one-pass ดังนั้นคุณจึงไม่สามารถแบ็คแพชค่าที่คำนวณภายหลังได้ แต่การอ้างอิงตัวแปรทั่วโลกจะใช้ได้และควรปรับให้เหมาะสมเหมือนกัน พวกเขาจะไม่ทำงานที่นิพจน์ค่าคงที่จำนวนเต็ม แต่อาจเป็นไปได้ที่จะจัดโครงสร้างโค้ดเพื่อไม่ให้จำเป็นสำหรับการนับ
PSkocik

2
__COUNTER__ไม่ได้มาตรฐานใน C หรือ C ++ หากคุณรู้ว่ามันใช้งานได้กับคอมไพเลอร์ตัวใดตัวหนึ่งให้ระบุ
ปีเตอร์

@einpoklum ไม่BEFOREและAFTERไม่ใช่มาโคร
Alan Birtles

มีปัญหากับรุ่นที่ไม่ใช่ตัวนับของโซลูชันนี้: ก่อนและหลังสามารถใช้งานได้ในขอบเขตเดียวกับบรรทัดแหล่งที่มา แก้ไขตัวอย่างข้อมูล "เช่น" ของฉันเพื่อแสดงว่านี่เป็นปัญหา
einpoklum

1
@ user694733 คำถามจริงถูกแท็ก [C ++] สำหรับค่าคงที่ C enum ทำงาน
ไฟแลนเซอร์

9

ฉันรู้ว่าคำขอของ OP คือใช้มาโคร แต่ฉันต้องการเพิ่มวิธีอื่นในการทำเช่นนี้ซึ่งไม่เกี่ยวข้องกับการใช้มาโคร

C ++ 20 แนะนำsource_locationคลาสที่แสดงข้อมูลบางอย่างเกี่ยวกับซอร์สโค้ดเช่นชื่อไฟล์หมายเลขบรรทัดและชื่อฟังก์ชัน เราสามารถใช้มันได้อย่างง่ายดายในกรณีนี้

#include <iostream>
#include <source_location>

static constexpr auto line_number_start = std::source_location::current().line();
void foo();
void bar();
static constexpr auto line_number_end = std::source_location::current().line();

int main() {
    std::cout << line_number_end - line_number_start - 1 << std::endl; // 2

    return 0;
}

และเป็นตัวอย่างที่มีชีวิตที่นี่


หากไม่มีมาโครจะดีกว่ามาโคร อย่างไรก็ตาม - ด้วยวิธีนี้ฉันสามารถใช้การนับบรรทัดในขอบเขตเดียวกับบรรทัดที่ฉันนับ นอกจากนี้ - ด้วยsource_locationการทดลองใน C ++ 20
einpoklum

ฉันเห็นด้วยว่าการแก้ปัญหาโดยไม่มีมาโครนั้นดีกว่ามาโครมาก source_locationตอนนี้เป็นส่วนหนึ่งของ C ++ 20 อย่างเป็นทางการ ตรวจสอบที่นี่ ฉันหารุ่นคอมไพเลอร์ gcc ไม่พบในgodbolt.orgที่รองรับมันในแง่ที่ไม่ได้ทดลองแล้ว คุณช่วยอธิบายเพิ่มเติมอีกเล็กน้อยเกี่ยวกับรายการของคุณได้หรือไม่ - ฉันสามารถใช้การนับบรรทัดในขอบเขตเดียวกับบรรทัดที่ฉันนับได้เท่านั้น
NutCracker

สมมติว่าฉันใส่ข้อเสนอแนะของคุณในฟังก์ชั่น (เช่นสายนับเป็น invocations ไม่ใช่การประกาศ) มันใช้งานได้ - แต่ฉันมีline_number_startและline_number_endอยู่ในขอบเขตนั้นไม่มีที่อื่น ถ้าฉันต้องการที่อื่นฉันจำเป็นต้องผ่านมันในเวลาทำงาน - ซึ่งเอาชนะวัตถุประสงค์
einpoklum

ลองดูที่ตัวอย่างที่ได้มาตรฐานให้ที่นี่ ถ้ามันเป็นอาร์กิวเมนต์เริ่มต้นก็ยังคงเป็นส่วนหนึ่งของการรวบรวมเวลาใช่มั้ย?
NutCracker

ใช่ แต่นั่นไม่line_number_endปรากฏให้เห็นในเวลารวบรวมนอกขอบเขต ช่วยแก้ให้ด้วยนะถ้าฉันผิด.
einpoklum

7

เพื่อความสมบูรณ์: หากคุณยินดีที่จะเพิ่มMAGIC2หลังจากทุกบรรทัดคุณสามารถใช้__COUNTER__:

#define MAGIC2 static_assert(__COUNTER__ + 1, "");

/* some */     MAGIC2
void source(); MAGIC2
void lines();  MAGIC2

constexpr int numberOfLines = __COUNTER__;

int main()
{
    return numberOfLines;
}

https://godbolt.org/z/i8fDLx (ส่งคืน3)

__COUNTER__คุณสามารถทำให้มันนำมาใช้ใหม่โดยการจัดเก็บเริ่มต้นและสิ้นสุดของค่า

โดยรวมแล้วมันค่อนข้างยุ่งยากจริงๆ คุณจะไม่สามารถนับจำนวนบรรทัดที่มีคำสั่ง preprocessor หรือลงท้ายด้วย//ความคิดเห็น ฉันจะใช้__LINE__แทนดูคำตอบอื่น


1
ทำไมคุณใช้static_assert?
idclev 463035818

1
สิ่งนี้ให้ "9" ในไฟล์ต้นฉบับที่ฉันทิ้งลงไปคุณไม่สามารถสันนิษฐานได้ว่า__COUNTER__ยังคงเป็นศูนย์ในตอนแรกเนื่องจากส่วนหัวอื่น ๆ ฯลฯ อาจใช้งานได้
ไฟแลนเซอร์

คุณต้องใช้ค่า__COUNTER__สองเท่าและสร้างความแตกต่าง
idclev 463035818

1
@ ก่อนหน้านี้ที่รู้จักกัน _463035818 __COUNTER__ด้วยตัวของมันเองจะไม่ได้รับอนุญาตและมันจะต้องได้รับการขยายไปยังบางสิ่งหรือมันจะไม่นับ (ฉันจำกฎไม่ได้ 100% กับสิ่งนี้)
Lancer ไฟ

7

วิธีแก้ปัญหาที่ค่อนข้างแข็งแกร่งกว่าซึ่งอนุญาตสำหรับเคาน์เตอร์ที่แตกต่างกัน (ตราบเท่าที่พวกเขาไม่ได้ผสมและไม่มีประโยชน์__COUNTER__สำหรับงานอื่น ๆ ):

#define CONCATENATE(s1, s2) s1##s2
#define EXPAND_THEN_CONCATENATE(s1, s2) CONCATENATE(s1, s2)

#define COUNT_THIS_LINE static_assert(__COUNTER__ + 1, "");
#define START_COUNTING_LINES(count_name) \
  enum { EXPAND_THEN_CONCATENATE(count_name, _start) = __COUNTER__ };
#define FINISH_COUNTING_LINES(count_name) \
  enum { count_name = __COUNTER__ - EXPAND_THEN_CONCATENATE(count_name, _start) - 1 };

นี่จะซ่อนรายละเอียดการใช้งาน (แม้ว่าจะซ่อนไว้ในมาโคร ... ) มันเป็นลักษณะทั่วไปของคำตอบของ @ MaxLanghof โปรดทราบว่า__COUNTER__อาจมีค่าที่ไม่เป็นศูนย์เมื่อเราเริ่มนับ

นี่คือวิธีการใช้งาน:

START_COUNTING_LINES(ze_count)

int hello(int x) {
    x++;
    /* some */     COUNT_THIS_LINE
    void source(); COUNT_THIS_LINE
    void lines();  COUNT_THIS_LINE
    return x;
}

FINISH_COUNTING_LINES(ze_count)

int main()
{
    return ze_count;
}

และนี่คือค่า C ที่ถูกต้อง - หากตัวประมวลผลล่วงหน้าของคุณรองรับ__COUNTER__นั่นคือ

ทำงานใน GodBolt

หากคุณใช้ C ++ คุณสามารถแก้ไขโซลูชันนี้เพื่อไม่ให้ทำให้เนมสเปซส่วนกลางหมดไป - โดยวางเคาน์เตอร์ภายในnamespace macro_based_line_counts { ... }หรือnamespace detailอื่น ๆ )


5

จากความคิดเห็นของคุณหากคุณต้องการระบุขนาดอาร์เรย์ (เวลาคอมไพล์) ใน C หรือ C ++ คุณสามารถทำได้

int array[]; //incomplete type
enum{ LINE0 = __LINE__ }; //enum constants are integer constant expressions
/*lines to be counted; may use array (though not sizeof(array)) */
/*...*/
int array[ __LINE__ - LINE0 ]; //complete the definition of int array[]

หากคุณต้องการsizeof(array)ในบรรทัดที่แทรกเข้ามาคุณสามารถแทนที่ด้วยการอ้างอิงตัวแปรแบบคงที่ (ยกเว้นกรณีที่จำเป็นต้องเป็นนิพจน์ค่าคงที่จำนวนเต็ม) และคอมไพเลอร์ที่เพิ่มประสิทธิภาพควรปฏิบัติกับมันเหมือนกัน (กำจัดความต้องการตัวแปรแบบคงที่ ในความทรงจำ)

int array[]; static int count;
enum{ LINE0 = __LINE__ }; //enum constants are integer constant expressions
//... possibly use count here
enum { LINEDIFF = __LINE__ - LINE0 }; 
int array[ LINEDIFF ]; /*complete the definition of int array[]*/ 
static int count = LINEDIFF; //fill the count in later

__COUNTER__แก้ปัญหาชั่น (ถ้าส่วนขยายที่มีอยู่) เมื่อเทียบกับ__LINE__ชั่นหนึ่งจะทำงานเดียวกัน

constexprs ใน C ++ ควรทำงานได้ดีenumแต่enumจะทำงานใน C ธรรมดาเช่นกัน (โซลูชันของฉันด้านบนเป็นโซลูชัน C ธรรมดา)


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

1
@einpoklum __COUNTER__วิธีการแก้ปัญหามีปัญหาเช่นกัน: คุณหวังว่าแมโครมายากลของคุณจะเป็นผู้ใช้เพียงอย่างเดียว__COUNTER__ก่อนที่คุณจะใช้งาน__COUNTER__เสร็จ ปัญหาที่เกิดขึ้นโดยทั่วไปทั้งหมดลงมาเพื่อข้อเท็จจริงที่เรียบง่ายที่__COUNTER__/__LINE__มีคุณสมบัติ preprocessor และงาน preprocessor ในหนึ่งผ่านดังนั้นคุณจึงไม่สามารถ backpatch การแสดงออกจำนวนเต็มคงที่ต่อมาบนพื้นฐาน/__COUNTER__ __LINE__วิธีเดียว (ใน C อย่างน้อย) คือการหลีกเลี่ยงความต้องการในสถานที่แรกเช่นโดยใช้การประกาศไปข้างหน้าอาร์เรย์ที่ไม่มีขนาด (การประกาศอาร์เรย์ที่พิมพ์ไม่สมบูรณ์)
PSkocik

1
สำหรับเรกคอร์ดนั้น\ ไม่มีผลต่อ__LINE__- ถ้ามีตัวแบ่งบรรทัด__LINE__เพิ่ม ตัวอย่างที่ 1 , 2 ตัวอย่าง
Max Langhof

@ MaxLanghof ขอบคุณ ไม่ได้ตระหนักถึงสิ่งนั้น แก้ไขแล้ว.
PSkocik
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.