Đếm các dòng tệp nguồn bằng cách sử dụng macro?


15

Có thể, bằng cách sử dụng bộ tiền xử lý C / C ++, để đếm các dòng trong tệp nguồn, thành một macro hoặc một loại giá trị thời gian biên dịch nào đó? Ví dụ như tôi có thể thay thế MAGIC1, MAGIC2MAGIC3trong những điều sau đây, và có được giá trị 4 bằng cách nào đó khi sử dụng 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

Ghi chú:

  • Các phần mở rộng dành riêng cho trình biên dịch cho các khả năng của bộ tiền xử lý có thể chấp nhận được nhưng không mong muốn.
  • Nếu điều này chỉ có thể với sự trợ giúp của một số C ++, trái ngược với C, xây dựng, điều đó cũng có thể chấp nhận được nhưng không mong muốn (ví dụ tôi muốn một cái gì đó sẽ hoạt động cho C).
  • Rõ ràng điều này có thể được thực hiện bằng cách chạy tệp nguồn thông qua một số tập lệnh bộ xử lý bên ngoài, nhưng đó không phải là điều tôi đang hỏi.

6
một macro được gọi là__LINE__ đại diện cho số dòng hiện tại
ForceBru

2
Đang tìm kiếm __COUNTER__và / hoặc BOOST_PP_COUNTER?
KamilCuk

11
Là gì thực tế vấn đề bạn cần phải giải quyết? Tại sao bạn cần điều này?
Một số lập trình viên anh chàng

1
Điều này có giúp gì không?
dùng1810087

1
@PSkocik: Tôi muốn một cái gì đó tôi có thể sử dụng như một hằng số thời gian biên dịch, ví dụ như để nói int arr[MAGIC4]và lấy số lượng dòng trong một số phần được tính trước đó của mã của tôi.
einpoklum

Câu trả lời:


15

__LINE__macro tiền xử lý cung cấp cho bạn một số nguyên cho dòng được xuất hiện trên. Bạn có thể lấy giá trị của nó trên một số dòng, rồi một số dòng sau và so sánh.

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

Nếu bạn muốn đếm số lần xuất hiện của một thứ gì đó thay vì các dòng nguồn, __COUNTER__có thể là một tùy chọn không chuẩn, được hỗ trợ bởi một số trình biên dịch như GCC và 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

Tôi lấy giá trị ban đầu __COUNTER__vì nó có thể đã được sử dụng trước đó trong tệp nguồn hoặc một số tiêu đề được bao gồm.

Trong C thay vì C ++, có những hạn chế đối với các biến không đổi, vì vậy enumcó thể sử dụng một biến thay thế.

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

Thay thế const bằng enum:

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

Tôi nghĩ rằng đây là gần nhất như bạn có thể nhận được chỉ với bộ tiền xử lý. Bộ tiền xử lý là một lần, vì vậy bạn không thể sao lưu giá trị được tính sau này, nhưng các tham chiếu biến toàn cục sẽ hoạt động và sẽ tối ưu hóa như nhau. Chúng chỉ không hoạt động ở các biểu thức hằng số nguyên, nhưng có thể cấu trúc mã sao cho những thứ đó không cần thiết cho số đếm.
PSkocik

2
__COUNTER__không phải là tiêu chuẩn trong C hoặc C ++. Nếu bạn biết nó hoạt động với các trình biên dịch cụ thể, chỉ định chúng.
Peter

@einpoklum không, BEFOREAFTERkhông phải là macro
Alan Birtles

Có một vấn đề với phiên bản không truy cập của giải pháp này: trước và sau chỉ có thể sử dụng được trong cùng phạm vi với các dòng nguồn. Đã chỉnh sửa đoạn "ví dụ" của tôi để phản ánh rằng đây là một vấn đề.
einpoklum

1
@ user694733 Câu hỏi đúng đã được gắn thẻ [C ++]. Đối với hằng số C enum làm việc.
Lancer lửa

9

Tôi biết rằng yêu cầu của OP là sử dụng macro, nhưng tôi muốn thêm một cách khác để thực hiện việc này không liên quan đến việc sử dụng macro.

C ++ 20 giới thiệu source_locationlớp đại diện cho thông tin nhất định về mã nguồn, chẳng hạn như tên tệp, số dòng và tên hàm. Chúng ta có thể sử dụng nó khá dễ dàng trong trường hợp này.

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

Và ví dụ sống ở đây .


Không có macro thậm chí còn tốt hơn với macro. Tuy nhiên - với phương pháp này, tôi chỉ có thể sử dụng số dòng trong cùng phạm vi với các dòng tôi đã đếm. Ngoài ra - với source_locationthử nghiệm trong C ++ 20?
einpoklum

Tôi đồng ý rằng giải pháp không có macro sẽ tốt hơn nhiều so với macro. source_locationbây giờ chính thức là một phần của C ++ 20. Kiểm tra tại đây . Tôi không thể tìm thấy phiên bản trình biên dịch gcc trên godbolt.org đã hỗ trợ nó theo nghĩa phi thực nghiệm. Bạn có thể vui lòng giải thích thêm một chút về tuyên bố của mình - Tôi chỉ có thể sử dụng số dòng trong cùng phạm vi với các dòng tôi đã đếm ?
NutCracker

Giả sử tôi đặt đề xuất của bạn trong một hàm (tức là các dòng được tính là các lệnh, không phải là khai báo). Nó hoạt động - nhưng tôi chỉ có line_number_startline_number_endtrong phạm vi đó, không ở đâu khác. Nếu tôi muốn nó ở nơi khác, tôi cần phải vượt qua nó trong thời gian chạy - điều đó đánh bại mục đích.
einpoklum

Hãy xem ví dụ mà tiêu chuẩn cung cấp ở đây . Nếu nó là đối số mặc định, thì nó vẫn là một phần của thời gian biên dịch, phải không?
NutCracker

Có, nhưng điều đó không line_number_endhiển thị tại thời gian biên dịch ngoài phạm vi của nó. Sửa tôi nếu tôi sai.
einpoklum

7

Để hoàn thiện: Nếu bạn sẵn sàng thêm MAGIC2sau mỗi dòng, bạn có thể sử dụng __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 (trả lại 3)

Bạn có thể làm cho nó có thể sử dụng lại bằng cách lưu trữ giá trị bắt đầu và kết thúc của __COUNTER__.

Nhìn chung, điều này thực sự là cồng kềnh. Bạn cũng sẽ không thể đếm các dòng có chứa các chỉ thị tiền xử lý hoặc kết thúc bằng các //bình luận. Tôi sẽ sử dụng __LINE__thay thế, xem câu trả lời khác.


1
tại sao bạn sử dụng static_assert?
idclev 463035818

1
Điều này đã cho "9" trong tệp nguồn mà tôi đã thả nó vào, __COUNTER__ban đầu bạn không thể cho rằng vẫn bằng không vì các tiêu đề khác, v.v. có thể sử dụng nó.
Lancer lửa

bạn phải sử dụng giá trị của __COUNTER__hai lần và lấy chênh lệch
idclev 463035818

1
@ trước đây là Unknown_463035818, __COUNTER__nó sẽ không được phép và nó cần được mở rộng sang thứ gì đó hoặc nó sẽ không được tính (Tôi không thể nhớ các quy tắc 100% về điều này).
Lancer lửa

7

Một giải pháp mạnh mẽ hơn, cho phép các bộ đếm khác nhau (miễn là chúng không xen kẽ và không sử dụng __COUNTER__cho các nhiệm vụ khác):

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

Điều này ẩn các chi tiết triển khai (mặc dù nó ẩn chúng trong các macro ...). Đây là một khái quát về câu trả lời của @ MaxLanghof. Lưu ý rằng __COUNTER__có thể có giá trị khác không khi chúng ta bắt đầu đếm.

Đây là cách nó được sử dụng:

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

Ngoài ra, đây là C hợp lệ - nếu bộ xử lý trước của bạn hỗ trợ __COUNTER__, đó là.

Hoạt động trên GodBolt .

Nếu bạn đang sử dụng C ++, bạn có thể sửa đổi giải pháp này để thậm chí không gây ô nhiễm không gian tên toàn cầu - bằng cách đặt các bộ đếm bên trong namespace macro_based_line_counts { ... }, namespace detailv.v.)


5

Dựa trên nhận xét của bạn, nếu bạn muốn chỉ định kích thước mảng (thời gian biên dịch) trong C hoặc C ++, bạn có thể làm

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[]

Nếu bạn cần sizeof(array)trong các dòng can thiệp, bạn có thể thay thế nó bằng một tham chiếu biến tĩnh (trừ khi nó hoàn toàn cần phải là một biểu thức hằng số nguyên) và một trình biên dịch tối ưu hóa sẽ xử lý nó giống nhau (loại bỏ sự cần thiết của biến tĩnh được đặt trong trí nhớ)

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

Một __COUNTER__giải pháp dựa trên cơ sở (nếu tiện ích mở rộng đó khả dụng) trái ngược với giải pháp __LINE__dựa trên cơ sở sẽ hoạt động như nhau.

constexprs trong C ++ cũng hoạt động tốt enum, nhưng enumcũng sẽ hoạt động ở đồng bằng C (giải pháp của tôi ở trên là giải pháp C đơn giản).


Điều này sẽ chỉ hoạt động nếu việc sử dụng số đếm dòng của tôi có cùng phạm vi với các dòng được tính. IIANM. Lưu ý tôi đã chỉnh sửa câu hỏi của mình một chút để nhấn mạnh rằng đó có thể là một vấn đề.
einpoklum

1
@einpoklum Một __COUNTER__giải pháp dựa trên cũng có vấn đề: bạn nên hy vọng macro ma thuật của mình là người dùng duy nhất __COUNTER__, ít nhất là trước khi bạn sử dụng xong __COUNTER__. Vấn đề về cơ bản tất cả __COUNTER__/__LINE__là do các tính năng đơn giản là các tính năng của bộ tiền xử lý và bộ tiền xử lý hoạt động trong một lần, vì vậy bạn không thể sao lưu một biểu thức hằng số nguyên sau đó dựa trên __COUNTER__/ __LINE__. Cách duy nhất (ít nhất là bằng C) là tránh sự cần thiết ở vị trí đầu tiên, ví dụ, bằng cách sử dụng khai báo mảng chuyển tiếp không có kích thước (khai báo mảng không được gõ đầy đủ).
PSkocik

1
Đối với bản ghi, \ không ảnh hưởng __LINE__- nếu có ngắt dòng, __LINE__tăng. Ví dụ 1 , ví dụ 2 .
Max Langhof

@MaxLanghof Cảm ơn. Không nhận ra điều đó. Đã sửa.
PSkocik
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.