หมายเลขบรรทัด C / C ++


110

เพื่อวัตถุประสงค์ในการดีบักฉันสามารถรับหมายเลขบรรทัดในคอมไพเลอร์C / C ++ ได้หรือไม่ (วิธีมาตรฐานหรือวิธีเฉพาะสำหรับคอมไพเลอร์บางตัว)

เช่น

if(!Logical)
    printf("Not logical value at line number %d \n",LineNumber);
    // How to get LineNumber without writing it by my hand?(dynamic compilation)

17
@ ลูคัส: พวกเราบางคนไม่ชอบที่จะยุ่งกับผู้แก้ปัญหา บางครั้ง "คำกล่าวยืนยันของผู้ไม่หวังดี" แบบนี้จะชัดเจนกว่าเนื่องจากเป็นส่วนถาวรของรหัสและเอกสารประกอบของสิ่งที่ควรเป็นจริงเกี่ยวกับสถานะของการคำนวณ
S.Lott

13
@Lucas: ดีบักเกอร์ยังมีประโยชน์น้อยกว่าสำหรับปัญหาที่ไม่ต่อเนื่องในโปรแกรมที่ทำงานเป็นเวลานานหรือสำหรับการรวบรวมข้อมูลเกี่ยวกับปัญหาในซอฟต์แวร์ที่ติดตั้งที่ไซต์ไคลเอนต์ ในกรณีเหล่านี้ทางเลือกเดียวคือให้โปรแกรมบันทึกข้อมูลเกี่ยวกับสถานะของโปรแกรมให้มากที่สุดเพื่อการวิเคราะห์ในภายหลัง
KeithB

1
@ ลูคัสและดีบักเกอร์ทำงานได้ไม่ดีในระบบฝังตัวบางระบบเพื่อรับข้อมูลนี้
George Stocker

คำตอบ:


180

คุณควรใช้มาโครตัวประมวลผลล่วงหน้า__LINE__และ__FILE__. เป็นมาโครที่กำหนดไว้ล่วงหน้าและเป็นส่วนหนึ่งของมาตรฐาน C / C ++ ในระหว่างการประมวลผลล่วงหน้าจะถูกแทนที่ตามลำดับด้วยสตริงคงที่ซึ่งถือจำนวนเต็มแทนหมายเลขบรรทัดปัจจุบันและตามชื่อไฟล์ปัจจุบัน

ตัวแปรก่อนตัวประมวลผลอื่น ๆ :

  • __func__: ชื่อฟังก์ชัน (นี่เป็นส่วนหนึ่งของC99ไม่ใช่คอมไพเลอร์ C ++ ทั้งหมดที่รองรับ)
  • __DATE__ : สตริงของรูปแบบ "Mmm dd yyyy"
  • __TIME__ : สตริงของรูปแบบ "hh: mm: ss"

รหัสของคุณจะเป็น:

if(!Logical)
  printf("Not logical value at line number %d in file %s\n", __LINE__, __FILE__);

2
C99 ใช้ __func__ มากกว่า __FUNCTION__ ซึ่ง AFAIK เลิกใช้งานไปแล้วบางส่วน ความแตกต่างสามารถทำลายรหัสของคุณได้เนื่องจากไม่สามารถใช้ __func__ สำหรับการต่อสตริงคงที่ของ C
Joseph Quinsey

1
การอ้างอิงจากคู่มือ GCC: "__FUNCTION__ และ __PRETTY_FUNCTION__ ถือว่าเป็นตัวอักษรสตริงซึ่งสามารถใช้เพื่อเริ่มต้นอาร์เรย์ถ่านและสามารถเชื่อมต่อกับตัวอักษรสตริงอื่นได้ GCC 3.4 และต่อมาถือว่าเป็นตัวแปรเช่น __func__ ใน C ++, __FUNCTION__ และ __PRETTY_FUNCTION__ เป็นตัวแปรเสมอ "
Joseph Quinsey

มีวิธีรับหมายเลขบรรทัดเป็นสตริงเหมือนกับชื่อไฟล์หรือไม่? ฉันต้องการให้ตัวประมวลผลล่วงหน้าให้ฉันเช่นสตริง
ลิเทอรัล

1
@ sep332 ใช่ แต่ cpp เป็นสัตว์ประหลาดดังนั้นจึงต้องทำสองขั้นตอนด้วยอาร์กิวเมนต์มาโคร #define S1(N) #N #define S2(N) S1(N) #define LINESTR S2(__LINE__). ดูc-faq.com/ansi/stringize.html
Rasmus Kaj

1
พูดตรงๆว่า__func__ไม่ใช่มาโคร แต่เป็นตัวแปรที่ประกาศโดยปริยาย
HolyBlackCat

64

ในฐานะส่วนหนึ่งของมาตรฐาน C ++ มีมาโครที่กำหนดไว้ล่วงหน้าบางส่วนที่คุณสามารถใช้ได้ ส่วนที่ 16.8 ของมาตรฐาน C ++ กำหนดสิ่งอื่น ๆ ได้แก่__LINE__มาโคร

__LINE__:หมายเลขบรรทัดของบรรทัดต้นทางปัจจุบัน (ค่าคงที่ทศนิยม)
__FILE__:ชื่อสันนิษฐานของไฟล์ต้นฉบับ (ลิเทอรัลสตริงอักขระ)
__DATE__:วันของการแปลของแฟ้มแหล่งที่มา (สตริงตัวอักษรตัวอักษร ... )
__TIME__:เวลาของการแปลของแฟ้มแหล่งที่มา (สายอักขระตัวอักษร ... )
__STDC__:ไม่ว่าจะ__STDC__มีการกำหนดไว้ล่วงหน้า
__cplusplus:ชื่อที่__cplusplusถูกกำหนดให้ค่า 199711L เมื่อ รวบรวมหน่วยการแปล C ++

ดังนั้นรหัสของคุณจะเป็น:

if(!Logical)
  printf("Not logical value at line number %d \n",__LINE__);

19

คุณสามารถใช้มาโครที่มีลักษณะการทำงานเดียวกันกับprintf ()ยกเว้นว่าจะมีข้อมูลการดีบักเช่นชื่อฟังก์ชันคลาสและหมายเลขบรรทัด:

#include <cstdio>  //needed for printf
#define print(a, args...) printf("%s(%s:%d) " a,  __func__,__FILE__, __LINE__, ##args)
#define println(a, args...) print(a "\n", ##args)

มาโครเหล่านี้ควรทำงานเหมือนกันกับprintf ()ในขณะที่รวมข้อมูลที่เหมือน java stacktrace นี่คือตัวอย่างหลัก:

void exampleMethod() {
    println("printf() syntax: string = %s, int = %d", "foobar", 42);
}

int main(int argc, char** argv) {
    print("Before exampleMethod()...\n");
    exampleMethod();
    println("Success!");
}

ซึ่งส่งผลให้เกิดผลลัพธ์ต่อไปนี้:

main (main.cpp: 11) ก่อน exampleMethod () ...
exampleMethod (main.cpp: 7) printf () syntax: string = foobar, int = 42
main (main.cpp: 13) สำเร็จ!


สำหรับการพัฒนาคคุณจะเปลี่ยน#includeไป<stdio.h>
phyatt

11

ใช้__LINE__(นั่นคือขีดล่างสองขีดของ LINE สองขีด) ตัวประมวลผลล่วงหน้าจะแทนที่ด้วยหมายเลขบรรทัดที่พบ



5

C ++ 20 ข้อเสนอวิธีการใหม่เพื่อให้บรรลุนี้โดยใช้มาตรฐาน :: source_location นี่คือปัจจุบันสามารถเข้าถึงได้ใน GCC เสียงดังกราวเป็นด้วยstd::experimental::source_location#include <experimental/source_location>

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

void log(const std::string msg) {
    std::cout << __LINE__ << " " << msg << std::endl;
}

จะส่งออกบรรทัดของการประกาศฟังก์ชันเสมอไม่ใช่บรรทัดที่logถูกเรียกใช้จริง ในทางกลับกันstd::source_locationคุณสามารถเขียนสิ่งนี้:

#include <experimental/source_location>
using std::experimental::source_location;

void log(const std::string msg, const source_location loc = source_location::current())
{
    std::cout << loc.line() << " " << msg << std::endl;
}

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


4

ลอง__FILE__และ__LINE__.
คุณอาจพบ__DATE__และ__TIME__มีประโยชน์
แม้ว่าคุณจะไม่ต้องดีบักโปรแกรมบนไคลเอนต์และจำเป็นต้องบันทึกข้อมูลเหล่านี้คุณควรใช้การดีบักตามปกติ


เหตุใดฉันจึงถูกโหวตให้ลงคะแนนและเหตุใด mmyers จึงแก้ไขโพสต์ของฉัน
Sanctus2099

@ Sanctus2099: ได้รับการแก้ไขเนื่องจาก Markdown เปลี่ยนขีดล่างคู่ของคุณเพื่อแสดง FILE และ LINE ในแบบอักษรตัวหนา (คุณไม่ตรวจสอบว่าคำตอบของคุณเป็นอย่างไร) อีกประเด็นหนึ่งอาจเป็น (อย่างน้อยตอนนี้ฉันก็มองแบบนี้) ที่คุณให้คำตอบ 1 ชั่วโมงหลังจากได้รับคำตอบที่ถูกต้องแล้วดังนั้นคุณจึงไม่ได้เพิ่มคุณค่า
Felix Kling

ขีดคู่ไวยากรณ์มาร์กอัปสำหรับตัวหนา ในการแสดงขีดล่างคู่อย่างถูกต้องคุณต้องหลีกเลี่ยง (เช่นนี้: \ _ \ _) หรือใช้ backticks เพื่อทำเครื่องหมายเป็นraw code(ดังนี้: "__`) @mmyers พยายามที่จะช่วยเหลือ แต่เขาเท่านั้นที่หนีหนึ่งขีดและทำให้คุณถูกทิ้งให้อยู่กับมาร์กอัปไวยากรณ์สำหรับตัวเอียง การโหวตลงค่อนข้างรุนแรงที่นี่ฉันเห็นด้วย
Matt B.

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

2
@ Sanctus2099 บางคนลงคะแนนอย่างรวดเร็วนั่นเป็นเหตุผลว่าทำไมคำตอบของคุณจึงเป็นสิ่งสำคัญ ในกรณีนี้คุณโพสต์คำตอบผิดและปล่อยไว้โดยไม่มีการแก้ไขเป็นเวลา 4 ชั่วโมง คุณไม่มีใครจะตำหนินอกจากตัวคุณเอง
meagar

2

สำหรับผู้ที่อาจต้องการมาโคร "FILE_LINE" เพื่อพิมพ์ไฟล์และบรรทัด:

#define STRINGIZING(x) #x
#define STR(x) STRINGIZING(x)
#define FILE_LINE __FILE__ ":" STR(__LINE__)

1

เนื่องจากตอนนี้ฉันกำลังประสบปัญหานี้และฉันไม่สามารถเพิ่มคำตอบให้กับคำถามอื่น แต่ถูกต้องด้วยที่ถามที่นี่ฉันจะให้ตัวอย่างวิธีแก้ปัญหา: รับเฉพาะหมายเลขบรรทัดของตำแหน่งที่มีการเรียกใช้ฟังก์ชัน C ++ โดยใช้เทมเพลต

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

#include <iostream>

class Test{
    public:
        template<unsigned int L>
        int test(){
            std::cout << "the function has been called at line number: " << L << std::endl;
            return 0;
        }
        int test(){ return this->test<0>(); }
};

int main(int argc, char **argv){
    Test t;
    t.test();
    t.test<__LINE__>();
    return 0;
}

เอาท์พุต:

ฟังก์ชันถูกเรียกใช้ที่หมายเลขบรรทัด: 0

ฟังก์ชันถูกเรียกใช้ที่หมายเลขบรรทัด: 16

สิ่งหนึ่งที่ต้องพูดถึงที่นี่ก็คือใน C ++ 11 Standard คุณสามารถกำหนดค่าเทมเพลตเริ่มต้นสำหรับฟังก์ชันโดยใช้เทมเพลตได้ ในค่าเริ่มต้นก่อน C ++ 11 สำหรับอาร์กิวเมนต์ที่ไม่ใช่ประเภทดูเหมือนจะใช้ได้กับอาร์กิวเมนต์เทมเพลตคลาสเท่านั้น ดังนั้นใน C ++ 11 จึงไม่จำเป็นต้องมีข้อกำหนดฟังก์ชันที่ซ้ำกันดังที่กล่าวมา ใน C ++ 11 ยังใช้ได้กับอาร์กิวเมนต์เทมเพลต const char * แต่ไม่สามารถใช้กับตัวอักษรเช่น__FILE__หรือ__func__ตามที่กล่าวไว้ที่นี่ได้

ดังนั้นในท้ายที่สุดหากคุณใช้ C ++ หรือ C ++ 11 นี่อาจเป็นทางเลือกที่น่าสนใจมากกว่าการใช้มาโครเพื่อรับสายโทร


1

ใช้__LINE__แต่ล่ะประเภท?

LINEหมายเลขบรรทัดที่สันนิษฐาน (ภายในซอร์สไฟล์ปัจจุบัน) ของบรรทัดต้นทางปัจจุบัน (ค่าคงที่จำนวนเต็ม)

ในฐานะที่เป็นเลขคงรหัสมักจะถือว่ามีค่าเป็น__LINE__ <= INT_MAXและอื่น ๆ intประเภทคือ

จะพิมพ์ใน C, ต้องการระบุตรงกัน:printf() "%d"นี่คือความกังวลไกลน้อยใน C ++ coutด้วย

ความกังวลเกี่ยวกับ Pedantic:หากหมายเลขบรรทัดเกินINT_MAX1 (เป็นไปได้บ้างกับ 16 บิตint) หวังว่าคอมไพเลอร์จะแจ้งเตือน ตัวอย่าง:

format '%d' expects argument of type 'int', but argument 2 has type 'long int' [-Wformat=]

อีกวิธีหนึ่งรหัสอาจบังคับให้ประเภทที่กว้างขึ้นเพื่อป้องกันคำเตือนดังกล่าว

printf("Not logical value at line number %ld\n", (long) __LINE__);
//or
#include <stdint.h>
printf("Not logical value at line number %jd\n", INTMAX_C(__LINE__));

หลีกเลี่ยง printf()

เพื่อหลีกเลี่ยงข้อ จำกัด จำนวนเต็มทั้งหมด: stringify รหัสสามารถพิมพ์ได้โดยตรงโดยไม่ต้องprintf()โทร: เป็นสิ่งที่ดีที่จะหลีกเลี่ยงในการจัดการข้อผิดพลาด2 .

#define xstr(a) str(a)
#define str(a) #a

fprintf(stderr, "Not logical value at line number %s\n", xstr(__LINE__));
fputs("Not logical value at line number " xstr(__LINE__) "\n", stderr);

1การเขียนโปรแกรมที่ไม่ดีอย่างแน่นอนที่จะมีไฟล์ขนาดใหญ่เช่นนี้ แต่บางทีโค้ดที่เครื่องสร้างขึ้นอาจสูง

2ในการดีบักบางครั้งโค้ดก็ไม่ทำงานตามที่หวังไว้ โทรฟังก์ชั่นที่ซับซ้อนเช่นสามารถตัวเองปัญหาเกิดขึ้นเมื่อเทียบกับที่เรียบง่าย*printf()fputs()

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