Số dòng C / C ++


110

Với mục đích gỡ lỗi, tôi có thể lấy số dòng trong trình biên dịch C / C ++ không? (cách chuẩn hoặc các cách cụ thể cho các trình biên dịch nhất định)

ví dụ

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
@Lucas: Một số người trong chúng ta không muốn gây rối với trình gỡ lỗi. Loại "tuyên bố khẳng định của người nghèo" này đôi khi rõ ràng hơn vì nó là một phần vĩnh viễn của mã và là tài liệu lâu dài về những điều nên đúng về trạng thái của tính toán.
S.Lott

13
@Lucas: Trình gỡ lỗi cũng ít hữu ích hơn đối với các sự cố không liên tục trong các chương trình chạy lâu hoặc để thu thập thông tin về các sự cố trong phần mềm được triển khai tại các trang khách hàng. Trong những trường hợp này, lựa chọn duy nhất là chương trình ghi càng nhiều thông tin về trạng thái của chương trình càng tốt để phân tích sau.
KeithB

1
@Lucas Và trình gỡ lỗi không hoạt động tốt trên một số hệ thống nhúng để lấy thông tin này.
George Stocker

Câu trả lời:


180

Bạn nên sử dụng macro bộ xử lý trước __LINE____FILE__. Chúng là các macro được xác định trước và là một phần của tiêu chuẩn C / C ++. Trong quá trình tiền xử lý, chúng được thay thế tương ứng bằng một chuỗi không đổi chứa một số nguyên đại diện cho số dòng hiện tại và tên tệp hiện tại.

Các biến tiền xử lý khác:

  • __func__: tên hàm (đây là một phần của C99 , không phải tất cả các trình biên dịch C ++ đều hỗ trợ nó)
  • __DATE__ : một chuỗi có dạng "Mmm dd yyyy"
  • __TIME__ : một chuỗi có dạng "hh: mm: ss"

Mã của bạn sẽ là:

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

2
C99 sử dụng __func__ thay vì __FUNCTION__, AFAIK một phần không được dùng nữa. Sự khác biệt có thể phá vỡ mã của bạn, vì __func__ không thể được sử dụng để nối chuỗi liên tục của C.
Joseph Quinsey

1
Tham chiếu từ hướng dẫn sử dụng GCC: "__FUNCTION__ và __PRETTY_FUNCTION__ được coi là chuỗi ký tự; chúng có thể được sử dụng để khởi tạo mảng char và chúng có thể được nối với các chuỗi ký tự khác. GCC 3.4 trở lên coi chúng như các biến, như __func__. Trong C ++, __FUNCTION__ và __PRETTY_FUNCTION__ luôn là các biến. "
Joseph Quinsey

Có cách nào để lấy số dòng dưới dạng chuỗi, giống như tên tệp không? Tôi muốn các tiền xử lý để cho tôi ví dụ như đen chuỗi "22" thay vì số nguyên 22.
sep332

1
@ sep332 Có, nhưng cpp là một con quái vật lạ, vì vậy nó phải được thực hiện theo hai bước với các đối số macro. #define S1(N) #N #define S2(N) S1(N) #define LINESTR S2(__LINE__). Xem c-faq.com/ansi/stringize.html
Rasmus Kaj

1
Nói một cách chính xác, __func__không phải là một macro, nó là một biến được khai báo ngầm.
HolyBlackCat

64

Là một phần của tiêu chuẩn C ++, tồn tại một số macro được xác định trước mà bạn có thể sử dụng. Phần 16.8 của tiêu chuẩn C ++ định nghĩa __LINE__macro.

__LINE__: Số dòng của dòng nguồn hiện tại (một hằng số thập phân).
__FILE__: Tên giả định của tệp nguồn (một chuỗi ký tự theo nghĩa đen).
__DATE__: Ngày dịch của tệp nguồn (một chuỗi ký tự theo nghĩa đen ...)
__TIME__: Thời gian dịch tệp nguồn (một chuỗi ký tự theo nghĩa ...)
__STDC__:__STDC__được xác định trước hay không
__cplusplus: Tên __cplusplusđược xác định cho giá trị 199711L khi biên dịch một đơn vị dịch C ++

Vì vậy, mã của bạn sẽ là:

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

19

Bạn có thể sử dụng macro có cùng hành vi như printf () , ngoại trừ việc nó cũng bao gồm thông tin gỡ lỗi như tên hàm, lớp và số dòng:

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

Các macro này sẽ hoạt động giống hệt printf () , trong khi bao gồm thông tin giống java stacktrace. Đây là một ví dụ chính:

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

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

Kết quả nào trong kết quả sau:

main (main.cpp: 11) Trước exampleMethod () ...
exampleMethod (main.cpp: 7) Cú pháp printf (): string = foobar, int = 42
main (main.cpp: 13) Thành công!


để phát triển c, bạn sẽ thay đổi #includethành<stdio.h>
phyatt

11

Sử dụng __LINE__(đó là dấu gạch dưới kép LINE dấu gạch dưới kép), bộ tiền xử lý sẽ thay thế nó bằng số dòng mà nó gặp phải.



5

C ++ 20 cung cấp một cách mới để đạt được điều này bằng cách sử dụng std :: source_location . Điều này hiện có thể truy cập trong gcc một tiếng kêu như std::experimental::source_locationvới #include <experimental/source_location>.

Vấn đề với các macro như vậy __LINE__là nếu bạn muốn tạo ví dụ một hàm ghi nhật ký xuất ra số dòng hiện tại cùng với một thông báo, bạn luôn phải chuyển __LINE__dưới dạng đối số của hàm, vì nó được mở rộng tại địa chỉ gọi. Một cái gì đó như thế này:

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

Sẽ luôn xuất ra dòng khai báo hàm chứ không phải dòng logthực sự được gọi từ đó. Mặt khác, std::source_locationbạn có thể viết một cái gì đó như thế này:

#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;
}

Ở đây, locđược khởi tạo với số dòng trỏ đến vị trí logđược gọi. Bạn có thể thử trực tuyến tại đây.


4

Hãy thử __FILE____LINE__.
Bạn cũng có thể tìm thấy __DATE____TIME__hữu ích.
Mặc dù trừ khi bạn phải gỡ lỗi một chương trình trên máy khách và do đó cần ghi lại những thông tin này, bạn nên sử dụng gỡ lỗi bình thường.


Tại sao tôi lại bỏ phiếu cho điều này và tại sao mmyers lại chỉnh sửa bài đăng của tôi?
Sanctus2099

@ Sanctus2099: Nó đã được chỉnh sửa, vì Markdown đã biến đổi dấu gạch dưới kép của bạn để hiển thị FILE và LINE ở phông chữ đậm (bạn không kiểm tra câu trả lời của mình trông như thế nào?). Một điểm khác có thể là (ít nhất là với tôi bây giờ là như vậy) là bạn đã đưa ra câu trả lời 1 giờ sau khi một câu trả lời đã đúng được đưa ra, vì vậy bạn không có thêm giá trị nào.
Felix Kling

Dấu gạch dưới kép là cú pháp đánh dấu cho chữ đậm . Để hiển thị đúng hai dấu gạch dưới, bạn phải thoát khỏi chúng (như thế này: \ _ \ _) hoặc sử dụng dấu gạch ngược để đánh dấu chúng là raw code(như thế này: `__`). @mmyers đã cố gắng giúp đỡ, nhưng anh ấy chỉ thoát khỏi một trong những dấu gạch dưới, và do đó bạn bị bỏ lại với cú pháp đánh dấu cho chữ nghiêng . Tuy nhiên, những người phản đối ở đây hơi khắc nghiệt, tôi đồng ý.
Matt B.

Được rồi, tôi đã không nhận ra điều về việc gạch dưới kép biến văn bản thành chữ in đậm và tôi phải đi và không có thời gian để xem câu trả lời của mình như thế nào. Bây giờ tôi đã hiểu. Ngay cả khi câu trả lời của tôi trễ một giờ thì nó vẫn là một câu trả lời hay. Nó không thêm bất kỳ giá trị nào nhưng nó cũng không sai nên không có lý do gì để downvote. Đó là những gì bạn nhận được khi cố gắng giúp đỡ ...
Sanctus2099

2
@ Sanctus2099 Một số người nhanh chóng bỏ phiếu, đó là lý do tại sao điều quan trọng là đảm bảo câu trả lời của bạn là chính xác. Trong trường hợp này, bạn đã đăng một câu trả lời sai và để nó không được chỉnh sửa trong 4 giờ. Bạn không có ai để đổ lỗi ngoài chính bạn.
Mudgar

1

Vì tôi cũng đang gặp phải vấn đề này và tôi không thể thêm câu trả lời cho một câu hỏi khác nhưng cũng hợp lệ được hỏi ở đây , tôi sẽ cung cấp một giải pháp ví dụ cho vấn đề: chỉ lấy số dòng của nơi hàm đã được gọi C ++ sử dụng các mẫu.

Bối cảnh: trong C ++ người ta có thể sử dụng các giá trị số nguyên không phải kiểu làm đối số mẫu. Điều này khác với cách sử dụng điển hình của các kiểu dữ liệu làm đối số mẫu. Vì vậy, ý tưởng là sử dụng các giá trị nguyên như vậy cho một lời gọi hàm.

#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;
}

Đầu ra:

hàm đã được gọi ở dòng số: 0

hàm đã được gọi ở dòng số: 16

Một điều cần đề cập ở đây là trong C ++ 11 Standard, có thể cung cấp các giá trị mẫu mặc định cho các hàm sử dụng mẫu. Trong các giá trị mặc định trước C ++ 11 cho các đối số không phải kiểu dường như chỉ hoạt động cho các đối số mẫu lớp. Vì vậy, trong C ++ 11, sẽ không cần phải có các định nghĩa hàm trùng lặp như trên. Trong C ++ 11, nó cũng hợp lệ để có các đối số mẫu const char * nhưng không thể sử dụng chúng với các ký tự như __FILE__hoặc __func__như đã đề cập ở đây .

Vì vậy, cuối cùng nếu bạn đang sử dụng C ++ hoặc C ++ 11, đây có thể là một sự thay thế rất thú vị so với việc sử dụng macro để nhận dòng gọi.


1

Sử dụng __LINE__, nhưng loại của nó là gì?

LINE Số dòng giả định (trong tệp nguồn hiện tại) của dòng nguồn hiện tại (một hằng số nguyên).

Là một hằng số nguyên , mã thường có thể giả định giá trị là __LINE__ <= INT_MAXvà kiểu như vậy int.

Để in trong C, printf()cần sự xác định phù hợp: "%d". Đây là một mối quan tâm ít hơn nhiều trong C ++ với cout.

Mối quan tâm lớn: Nếu số dòng vượt quá INT_MAX1 (hơi có thể hình dung được với 16-bit int), hy vọng trình biên dịch sẽ đưa ra cảnh báo. Thí dụ:

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

Ngoài ra, mã có thể buộc các loại rộng hơn chặn các cảnh báo như vậy.

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

Tránh printf()

Để tránh tất cả các giới hạn số nguyên: stringify . Mã có thể in trực tiếp mà không cần printf()gọi: một điều tốt để tránh khi xử lý lỗi 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 Thực hành lập trình kém chắc chắn để có một tệp lớn như vậy, nhưng có lẽ mã do máy tạo ra có thể tăng cao.

2 Trong gỡ lỗi, đôi khi mã chỉ đơn giản là không hoạt động như mong đợi. Việc gọi các hàm phức tạp như *printf()tự nó có thể phát sinh vấn đề so với một hàm đơn giản fputs().


1

Đối với những người có thể cần nó, macro "FILE_LINE" để dễ dàng in tệp và dòng:

#define STRINGIZING(x) #x
#define STR(x) STRINGIZING(x)
#define FILE_LINE __FILE__ ":" STR(__LINE__)
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.