Hitung baris file sumber menggunakan makro?

15

Apakah mungkin, menggunakan preprocessor C / C ++, untuk menghitung baris dalam file sumber, menjadi makro atau semacam kompilasi-nilai waktu yang tersedia? Misalnya saya bisa mengganti MAGIC1, MAGIC2dan MAGIC3berikut ini, dan mendapatkan nilai 4 entah bagaimana saat menggunakan 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

Catatan:

  • Ekstensi khusus kompiler untuk kemampuan preprosesor dapat diterima tetapi tidak diinginkan.
  • Jika ini hanya mungkin dengan bantuan beberapa C ++, sebagai lawan C, konstruk, itu juga dapat diterima tetapi tidak diinginkan (yaitu saya ingin sesuatu yang akan bekerja untuk C).
  • Jelas ini dapat dilakukan dengan menjalankan file sumber melalui beberapa skrip prosesor eksternal, tapi bukan itu yang saya tanyakan.
einpoklum
sumber
6
Ada makro yang disebut__LINE__ yang mewakili nomor baris saat ini
ForceBru
2
Apakah mencari __COUNTER__dan / atau BOOST_PP_COUNTER?
KamilCuk
11
Apa masalah aktual yang perlu Anda selesaikan? Mengapa Anda membutuhkan ini?
Beberapa programmer Bung
1
Apakah ini membantu?
user1810087
1
@PSkocik: Saya ingin sesuatu yang bisa saya gunakan sebagai konstanta waktu kompilasi, misalnya untuk mengatakan int arr[MAGIC4]dan mendapatkan jumlah baris di beberapa bagian kode saya yang sebelumnya dihitung.
einpoklum

Jawaban:

15

Ada __LINE__makro preprocessor yang memberi Anda bilangan bulat untuk garis muncul. Anda bisa mengambil nilainya pada beberapa baris, lalu beberapa baris berikutnya, dan membandingkan.

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

Jika Anda ingin menghitung kemunculan sesuatu daripada garis sumber, __COUNTER__mungkin merupakan opsi non-standar, yang didukung oleh beberapa kompiler seperti GCC dan 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

Saya mengambil nilai awal __COUNTER__karena mungkin telah digunakan sebelumnya di file sumber, atau beberapa header yang disertakan.

Dalam C daripada C ++ ada batasan pada variabel konstan, jadi enumbisa digunakan sebagai gantinya.

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

Mengganti const dengan enum:

enum {BEFORE = __LINE__};
foo();
bar();
baz();
quux();
enum { COUNT = __LINE__ - BEFORE - 1};
enum MyEnum
{
    FOO = COUNT // OK
};
Lancer Api
sumber
Saya pikir ini tentang yang terdekat yang bisa Anda dapatkan hanya dengan preprosesor. Preprosesor adalah sekali jalan, jadi Anda tidak dapat mengirim balik nilai yang dikomputasi nanti, tetapi referensi variabel global akan berfungsi dan harus mengoptimalkan yang sama. Mereka tidak akan bekerja pada ekspresi konstanta integer, tetapi dimungkinkan untuk menyusun kode sehingga tidak diperlukan untuk penghitungan.
PSkocik
2
__COUNTER__bukan standar dalam C atau C ++. Jika Anda tahu itu bekerja dengan kompiler tertentu, tentukan mereka.
Peter
@einpoklum tidak, BEFOREdan AFTERbukan makro
Alan Birtles
Ada masalah dengan versi non-counter dari solusi ini: sebelum dan sesudah hanya dapat digunakan dalam lingkup yang sama dengan baris sumber. Mengedit cuplikan "eg" saya untuk mencerminkan bahwa ini adalah masalah.
einpoklum
1
@ user694733 Pertanyaan sesungguhnya diberi tag [C ++]. Untuk konstanta C enum bekerja.
Fire Lancer
9

Saya tahu bahwa permintaan OP adalah untuk menggunakan makro, tapi saya ingin menambahkan cara lain untuk melakukan ini yang tidak melibatkan penggunaan makro.

C ++ 20 memperkenalkan source_locationkelas yang mewakili informasi tertentu tentang kode sumber, seperti nama file, nomor baris, dan nama fungsi. Kita bisa menggunakannya dengan mudah dalam hal ini.

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

Dan contoh hidup di sini .

Alat pemecah buah keras
sumber
Tanpa macro bahkan lebih baik daripada dengan macro. Namun - dengan pendekatan ini, saya hanya bisa menggunakan jumlah baris dalam cakupan yang sama dengan baris yang saya hitung. Juga - dengan source_locationmenjadi eksperimental di C ++ 20?
einpoklum
Saya setuju bahwa solusi tanpa makro jauh lebih baik daripada dengan makro. source_locationsekarang secara resmi bagian dari C ++ 20. Periksa di sini . Saya tidak bisa menemukan versi kompiler gcc di godbolt.org yang sudah mendukungnya dalam arti non eksperimental. Bisakah Anda jelaskan sedikit lebih banyak pernyataan Anda - Saya hanya bisa menggunakan jumlah baris dalam cakupan yang sama dengan baris yang saya hitung ?
NutCracker
Misalkan saya menempatkan saran Anda dalam suatu fungsi (yaitu baris yang dihitung adalah doa, bukan deklarasi). Ini bekerja - tetapi saya hanya memiliki line_number_startdan line_number_enddalam lingkup itu, di tempat lain. Jika saya menginginkannya di tempat lain, saya harus meneruskannya pada saat run-time - yang mengalahkan tujuannya.
einpoklum
Lihatlah contoh yang disediakan standar di sini . Jika itu argumen default, maka itu masih bagian dari waktu kompilasi, kan?
NutCracker
Ya, tapi itu tidak line_number_endterlihat pada waktu kompilasi di luar cakupannya. Koreksi saya jika saya salah.
einpoklum
7

Untuk kelengkapan: Jika Anda ingin menambahkan MAGIC2setelah setiap baris, Anda dapat menggunakan __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 (pengembalian 3)

Anda dapat membuatnya dapat digunakan kembali dengan menyimpan nilai awal dan akhir __COUNTER__.

Secara keseluruhan ini sangat rumit sekalipun. Anda juga tidak akan dapat menghitung baris yang berisi arahan preprosesor atau diakhiri dengan //komentar. Saya akan menggunakan __LINE__sebagai gantinya, lihat jawaban lainnya.

Max Langhof
sumber
1
mengapa Anda menggunakan static_assert?
idclev 463035818
1
Ini memberi "9" pada file sumber yang saya masukkan, Anda tidak bisa berasumsi __COUNTER__masih nol pada awalnya sebagai header lain, dll. Mungkin menggunakannya.
Fire Lancer
Anda harus menggunakan nilai __COUNTER__dua kali dan mengambil perbedaan
idclev 463035818
1
@ formerlyknownas_463035818 __COUNTER__sendiri tidak akan diizinkan, dan perlu diperluas ke sesuatu atau tidak akan dihitung (Saya tidak dapat mengingat aturan 100% tentang ini).
Fire Lancer
7

Solusi yang agak lebih kuat, memungkinkan untuk penghitung yang berbeda (asalkan mereka tidak mencampurkan, dan tidak ada gunanya __COUNTER__untuk tugas lain):

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

Ini menyembunyikan detail implementasi (meskipun menyembunyikannya di dalam makro ...). Ini adalah generalisasi jawaban @ MaxLanghof. Perhatikan bahwa __COUNTER__mungkin memiliki nilai bukan nol saat kita memulai penghitungan.

Begini cara menggunakannya:

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

Juga, ini valid C - jika preprosesor Anda mendukung __COUNTER__, yaitu.

Bekerja pada GodBolt .

Jika Anda menggunakan C ++, Anda dapat memodifikasi solusi ini untuk tidak mencemari namespace global - dengan menempatkan penghitung di dalamnya namespace macro_based_line_counts { ... }, atau namespace detaildll.)

einpoklum
sumber
5

Berdasarkan komentar Anda, jika Anda ingin menentukan ukuran array (waktu kompilasi) dalam C atau C ++, Anda bisa melakukannya

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

Jika Anda memerlukan sizeof(array)di baris intervening, Anda dapat menggantinya dengan referensi variabel statis (kecuali jika benar-benar harus berupa ekspresi konstanta bilangan bulat) dan kompiler pengoptimalisasi harus memperlakukannya sama saja (menghilangkan kebutuhan variabel statis untuk ditempatkan) dalam kenangan)

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

Sebuah __COUNTER__solusi berbasis (jika ekstensi yang tersedia) sebagai lawan dari __LINE__satu berbasis akan bekerja sama.

constexprs di C ++ harus bekerja dengan baik enum, tetapi enumakan bekerja di plain C juga (solusi saya di atas adalah solusi C polos).

PSkocik
sumber
Ini hanya akan berfungsi jika saya menggunakan penghitungan baris dalam cakupan yang sama dengan baris yang dihitung. IIANM. Catatan saya sedikit mengedit pertanyaan saya untuk menekankan bahwa itu bisa menjadi masalah.
einpoklum
1
@einpoklum __COUNTER__Solusi berbasis memiliki masalah juga: Anda lebih baik berharap makro ajaib Anda adalah satu-satunya pengguna __COUNTER__, setidaknya sebelum Anda selesai menggunakan __COUNTER__. Masalahnya pada dasarnya semua bermuara pada fakta-fakta sederhana yang __COUNTER__/__LINE__merupakan fitur preprocessor dan preprocessor bekerja dalam satu pass, sehingga Anda tidak dapat melakukan backpatch ekspresi konstanta integer nanti berdasarkan pada __COUNTER__/ __LINE__. Satu-satunya cara (dalam C setidaknya) adalah untuk menghindari kebutuhan di tempat pertama, misalnya, dengan menggunakan deklarasi array maju tanpa ukuran (deklarasi array diketik tidak lengkap).
PSkocik
1
Sebagai catatan, \ tidak mempengaruhi __LINE__- jika ada jeda baris, __LINE__meningkat. Contoh 1 , contoh 2 .
Max Langhof
@ MaxLanghof Terima kasih. Tidak menyadarinya. Tetap.
PSkocik