Zliczać wiersze pliku źródłowego za pomocą makr?

15

Czy za pomocą preprocesora C / C ++ można policzyć wiersze w pliku źródłowym do makra lub innej wartości dostępnej w czasie kompilacji? Np. Czy mogę zamienić MAGIC1, MAGIC2i MAGIC3poniżej, i jakoś uzyskać wartość 4 podczas używania 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

Uwagi:

  • Specyficzne dla kompilatora rozszerzenia możliwości preprocesora są dopuszczalne, ale niepożądane.
  • Jeśli jest to możliwe tylko przy pomocy części C ++, w przeciwieństwie do C, konstruuj, jest to również dopuszczalne, ale niepożądane (tzn. Chciałbym coś, co działałoby dla C).
  • Oczywiście można to zrobić, uruchamiając plik źródłowy za pomocą zewnętrznego skryptu procesora, ale nie o to pytam.
einpoklum
źródło
6
Jest wywołane makro,__LINE__ które reprezentuje bieżący numer linii
ForceBru
2
Czy szukasz __COUNTER__i / lub BOOST_PP_COUNTER?
KamilCuk
11
Jaki jest faktyczny problem, który musisz rozwiązać? Dlaczego tego potrzebujesz?
Jakiś programista koleś
1
Czy to pomaga?
user1810087
1
@PSkocik: Chcę czegoś, czego mógłbym użyć jako stałej czasowej kompilacji, np. Do wypowiedzenia int arr[MAGIC4]i uzyskania liczby wierszy w pewnej wcześniej policzonej części mojego kodu.
einpoklum

Odpowiedzi:

15

Istnieje __LINE__makro preprocesora, które podaje liczbę całkowitą, na której pojawi się linia. Możesz wziąć jego wartość w pewnym wierszu, a następnie w późniejszym wierszu i porównać.

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

Jeśli chcesz policzyć wystąpienia czegoś, a nie linii źródłowych, __COUNTER__może być niestandardową opcją obsługiwaną przez niektóre kompilatory, takie jak GCC i 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

Wziąłem wartość początkową, __COUNTER__ponieważ mogła być wcześniej użyta w pliku źródłowym lub w dołączonym nagłówku.

W C zamiast w C ++ istnieją ograniczenia zmiennych stałych, więc enumzamiast tego można zastosować

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

Zamiana stałej na enum:

enum {BEFORE = __LINE__};
foo();
bar();
baz();
quux();
enum { COUNT = __LINE__ - BEFORE - 1};
enum MyEnum
{
    FOO = COUNT // OK
};
Lansjer ognia
źródło
Myślę, że jest to najbliższe, jakie można uzyskać tylko z preprocesorem. Preprocesor jest jednoprzebiegowy, więc nie można powtórzyć później obliczonej wartości, ale odwołania do zmiennych globalnych będą działać i powinny to samo zoptymalizować. Po prostu nie będą działać na wyrażeniach stałych całkowitoliczbowych, ale może być możliwe takie skonstruowanie kodu, aby nie były potrzebne do zliczania.
PSkocik
2
__COUNTER__nie jest standardem w C lub C ++. Jeśli wiesz, że działa z określonymi kompilatorami, określ je.
Peter
@einpoklum nie, BEFOREi AFTERnie są makrami
Alan Birtles
Występuje problem z nieliczną wersją tego rozwiązania: przed i po można używać tylko w tym samym zakresie, co linie źródłowe. Zredagowałem mój „np.” Fragment, aby odzwierciedlić, że jest to problem.
einpoklum
1
@ user694733 Prawdziwe pytanie zostało oznaczone [C ++]. Dla C enum działają stałe.
Fire Lancer
9

Wiem, że żądanie OP dotyczy użycia makr, ale chciałbym dodać inny sposób, który nie wymaga użycia makr.

C ++ 20 wprowadza source_locationklasę reprezentującą pewne informacje o kodzie źródłowym, takie jak nazwy plików, numery linii i nazwy funkcji. W tym przypadku możemy to łatwo wykorzystać.

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

I przykład na żywo tutaj .

Orzechówka
źródło
Bez makr jest nawet lepiej niż z makrami. Jednak - przy takim podejściu mogę używać tylko liczby wierszy w tym samym zakresie, co linie, które policzyłem. Również - source_locationbyć eksperymentalnym w C ++ 20?
einpoklum
Zgadzam się, że rozwiązanie bez makr jest znacznie lepsze niż z makrami. source_locationjest teraz oficjalnie częścią C ++ 20. Sprawdź tutaj . Po prostu nie mogłem znaleźć wersji kompilatora gcc na godbolt.org, która już go obsługuje w sensie eksperymentalnym. Czy możesz wyjaśnić nieco więcej swoje oświadczenie - mogę używać tylko liczby wierszy w tym samym zakresie, co linie, które policzyłem ?
NutCracker
Załóżmy, że umieściłem twoją sugestię w funkcji (tj. Zliczone wiersze to wywołania, a nie deklaracje). To działa - ale tylko line_number_starti line_number_endw tym zakresie, nigdzie indziej. Jeśli chcę go gdzie indziej, muszę przekazać go w czasie wykonywania - co nie pozwala na osiągnięcie celu.
einpoklum
Spójrz na przykład podany tutaj przez standard . Jeśli to domyślny argument, to nadal jest on częścią czasu kompilacji, prawda?
NutCracker
Tak, ale nie jest to line_number_endwidoczne w czasie kompilacji poza jego zasięgiem. Popraw mnie, jeśli się mylę.
einpoklum
7

Dla kompletności: jeśli chcesz dodać MAGIC2po każdej linii, możesz użyć __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 (zwraca3 )

Możesz ustawić go jako wielokrotnego użytku, przechowując wartości początkowe i końcowe __COUNTER__ .

Ogólnie jest to jednak bardzo uciążliwe. Nie będziesz też mógł liczyć wierszy zawierających dyrektywy preprocesora lub zakończonych //komentarzami. Użyłbym __LINE__zamiast tego, zobacz inną odpowiedź.

Max Langhof
źródło
1
dlaczego używacie static_assert?
idclev 463035818
1
To dało „9” w pliku źródłowym, do którego go upuściłem, nie możesz założyć, że __COUNTER__początkowo jest to zero, ponieważ inne nagłówki itp. Mogą go użyć.
Fire Lancer
musisz użyć wartości __COUNTER__dwa razy i wziąć różnicę
idclev 463035818
1
@ previouslyknownas_463035818 __COUNTER__sam w sobie nie byłby dozwolony i musi zostać rozwinięty do czegoś, inaczej nie będzie się liczył (nie pamiętam reguł w 100% na ten temat).
Fire Lancer
7

Trochę bardziej niezawodne rozwiązanie, pozwalające na różne liczniki (o ile nie łączą się i nie można ich używać __COUNTER__do innych zadań):

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

Ukrywa to szczegóły implementacji (chociaż ukrywa je w makrach ...). Jest to uogólnienie odpowiedzi @ MaxLanghof. Zauważ, że __COUNTER__może mieć niezerową wartość, gdy zaczynamy odliczanie.

Oto jak jest używany:

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

Jest to również poprawne C - jeśli twój preprocesor obsługuje __COUNTER__, to znaczy.

Działa na GodBolt .

Jeśli używasz C ++, można zmodyfikować to rozwiązanie nawet nie zanieczyszczać globalnej przestrzeni nazw - poprzez umieszczenie liczniki wewnątrz namespace macro_based_line_counts { ... }lub namespace detailitd.)

einpoklum
źródło
5

Na podstawie komentarza, jeśli chcesz określić rozmiar tablicy (czas kompilacji) w C lub C ++, możesz to zrobić

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

Jeśli potrzebujesz sizeof(array)w interweniujących liniach, możesz zastąpić je odwołaniem do zmiennej statycznej (chyba że absolutnie musi to być wyrażenie całkowite stałe), a kompilator optymalizujący powinien traktować go tak samo (eliminując potrzebę umieszczania zmiennej statycznej w pamięci)

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

Rozwiązanie __COUNTER__oparte na bazie (jeśli to rozszerzenie jest dostępne), w przeciwieństwie do rozszerzenia __LINE__opartego na zasadach , będzie działać tak samo.

constexprs w C ++ powinien działać równie dobrze enum, ale enumbędzie również działał w zwykłym C (moje powyższe rozwiązanie jest zwykłym C).

PSkocik
źródło
Działa to tylko wtedy, gdy moje użycie licznika linii ma taki sam zakres jak liczone linie. IIANM. Uwaga Lekko zredagowałem moje pytanie, aby podkreślić, że może to stanowić problem.
einpoklum
1
@einpoklum Rozwiązanie __COUNTER__oparte na problemach również ma problemy: lepiej mieć nadzieję, że twoje magiczne makro jest jedynym użytkownikiem __COUNTER__, przynajmniej zanim skończysz używać __COUNTER__. Problem w zasadzie sprowadza się do prostych faktów, które __COUNTER__/__LINE__są funkcjami preprocesora, a preprocesor działa w jednym przebiegu, więc nie można później zastąpić wyrażenia stałego liczbą całkowitą na podstawie __COUNTER__/ __LINE__. Jedynym sposobem (przynajmniej w C) jest uniknięcie takiej potrzeby, np. Poprzez zastosowanie deklaracji tablicy bez rozmiaru (niepełne deklaracje typu tablicy).
PSkocik
1
Dla przypomnienia, \ nie wpływa __LINE__- jeśli dojdzie do przerwania linii, __LINE__wzrasta. Przykład 1 , przykład 2 .
Max Langhof
@MaxLanghof Thanks. Nie zdawałem sobie z tego sprawy. Naprawiony.
PSkocik