Contare le righe del file di origine utilizzando le macro?


15

È possibile, usando il preprocessore C / C ++, contare le righe all'interno di un file sorgente, in una macro o in qualche valore di tempo di compilazione disponibile? Ad esempio, posso sostituire MAGIC1, MAGIC2e MAGIC3di seguito, e ottenere il valore 4 in qualche modo durante l'utilizzo 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

Appunti:

  • Le estensioni specifiche del compilatore alle capacità del preprocessore sono accettabili ma indesiderabili.
  • Se questo è possibile solo con l'aiuto di alcuni costrutti di C ++, al contrario di C, questo è anche accettabile ma indesiderabile (cioè vorrei qualcosa che avrebbe funzionato per C).
  • Ovviamente questo può essere fatto eseguendo il file sorgente tramite alcuni script del processore esterno, ma non è quello che sto chiedendo.

6
C'è una macro chiamata__LINE__ che rappresenta il numero di riga corrente
ForceBru

2
Stai cercando __COUNTER__e / o BOOST_PP_COUNTER?
KamilCuk

11
Qual è il problema reale che devi risolvere? Perchè ti serve?
Qualche programmatore amico

1
Fa questo aiuto?
user1810087

1
@PSkocik: Voglio qualcosa che potrei usare come costante di compilazione, ad esempio per dire int arr[MAGIC4]e ottenere il numero di righe in una sezione del mio codice precedentemente conteggiata.
einpoklum,

Risposte:


15

C'è la __LINE__macro del preprocessore che ti dà un numero intero per la linea appare. Potresti prendere il suo valore su una riga, quindi su una riga successiva e confrontarla.

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

Se si desidera contare le occorrenze di qualcosa piuttosto che delle linee di origine, __COUNTER__potrebbe essere un'opzione non standard, supportata da alcuni compilatori come GCC e 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

Ho preso il valore iniziale di __COUNTER__perché potrebbe essere stato usato in precedenza nel file di origine o in qualche intestazione inclusa.

In C piuttosto che in C ++ ci sono limitazioni sulle variabili costanti, quindi si enumpotrebbe usare invece un.

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

Sostituzione della const con enum:

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

Penso che questo sia il più vicino possibile con il solo preprocessore. Il preprocessore è a passaggio singolo, quindi non è possibile eseguire il backpatch di un valore calcolato successivamente, ma i riferimenti a variabili globali funzioneranno e dovrebbero ottimizzare lo stesso. Semplicemente non funzioneranno con espressioni di costanti intere, ma potrebbe essere possibile strutturare il codice in modo che non siano necessarie per i conteggi.
PSkocik,

2
__COUNTER__non è standard in C o C ++. Se sai che funziona con compilatori particolari, specificali.
Peter,

@einpoklum no, BEFOREe AFTERnon sono macro
Alan Birtles

C'è un problema con la versione non-counter di questa soluzione: il prima e il dopo sono utilizzabili solo nello stesso ambito delle linee di origine. Ho modificato il mio frammento "ad esempio" per riflettere che si tratta di un problema.
einpoklum,

1
@ user694733 La vera domanda è stata taggata [C ++]. Per le costanti di C enum funzionano.
Fire Lancer

9

So che la richiesta del PO è di usare le macro, ma vorrei aggiungere un altro modo di fare ciò che non comporta l'uso delle macro.

C ++ 20 introduce la source_locationclasse che rappresenta determinate informazioni sul codice sorgente, come nomi di file, numeri di riga e nomi di funzioni. Possiamo usarlo abbastanza facilmente in questo caso.

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

E vivi l'esempio qui .


Senza le macro è ancora meglio che con le macro. Tuttavia, con questo approccio, posso usare solo il conteggio delle linee nello stesso ambito delle linee che ho contato. Inoltre - con source_locationessere sperimentali in C ++ 20?
einpoklum,

Concordo sul fatto che la soluzione senza macro è di gran lunga migliore rispetto alle macro. source_locationè ora ufficialmente parte di C ++ 20. Controllare qui . Non riuscivo a trovare la versione del compilatore gcc su godbolt.org che la supporta già in senso non sperimentale. Puoi per favore spiegare un po 'di più la tua affermazione: posso usare solo il conteggio delle righe nello stesso ambito delle righe che ho contato ?
Schiaccianoci

Supponiamo che io inserisca il tuo suggerimento in una funzione (cioè le righe contate sono invocazioni, non dichiarazioni). Funziona - ma ho solo line_number_starte line_number_endin quell'ambito, nessun altro. Se lo voglio altrove, devo passarlo in fase di esecuzione, il che vanifica lo scopo.
einpoklum,

Dai un'occhiata all'esempio fornito dallo standard qui . Se è un argomento predefinito, allora fa ancora parte del tempo di compilazione, giusto?
Schiaccianoci

Sì, ma ciò non rende line_number_endvisibile in fase di compilazione al di fuori del suo ambito. Correggimi se sbaglio.
einpoklum,

7

Per completezza: se si desidera aggiungere MAGIC2dopo ogni riga, è possibile utilizzare__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 (ritorni 3)

Puoi renderlo riutilizzabile memorizzando i valori di inizio e fine di __COUNTER__.

Nel complesso, questo è davvero ingombrante. Inoltre, non sarai in grado di contare le righe che contengono le direttive del preprocessore o che terminano con i //commenti. Userei __LINE__invece, vedere l'altra risposta.


1
perchè usi static_assert?
idclev 463035818,

1
Questo ha dato "9" nel file di origine in cui l'ho lasciato cadere, non si può presumere __COUNTER__che inizialmente sia ancora zero poiché altre intestazioni, ecc. Potrebbero usarlo.
Fire Lancer

devi usare il valore di __COUNTER__due volte e prendere la differenza
idclev 463035818

1
@ exlynownas_463035818 __COUNTER__da solo non sarebbe permesso, e deve espandersi in qualcosa o non conta (non ricordo le regole al 100% su questo).
Fire Lancer

7

Una soluzione un po 'più robusta, che consente contatori diversi (purché non si mescolino e non sia utile __COUNTER__per altre attività):

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

Questo nasconde i dettagli di implementazione (anche se li nasconde all'interno di macro ...). È una generalizzazione della risposta di @ MaxLanghof. Nota che __COUNTER__potrebbe avere un valore diverso da zero quando iniziamo un conteggio.

Ecco come viene utilizzato:

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

Inoltre, questo è valido C - se il tuo preprocessore supporta __COUNTER__, cioè.

Funziona su GodBolt .

Se si utilizza C ++, è possibile modificare questa soluzione per non inquinare nemmeno lo spazio dei nomi globale, posizionando i contatori all'interno namespace macro_based_line_counts { ... }o namespace detailecc.)


5

In base al tuo commento, se vuoi specificare una dimensione di array (in fase di compilazione) in C o C ++, puoi farlo

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

Se è necessario sizeof(array)nelle righe intermedie, è possibile sostituirlo con un riferimento alla variabile statica (a meno che non sia assolutamente necessario che sia un'espressione costante intera) e un compilatore di ottimizzazione dovrebbe trattarlo allo stesso modo (eliminare la necessità di posizionare la variabile statica in memoria)

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

Una __COUNTER__soluzione basata (se tale estensione è disponibile) anziché una __LINE__basata funzionerà allo stesso modo.

constexprs in C ++ dovrebbe funzionare altrettanto bene enum, ma enumfunzionerà anche in C semplice (la mia soluzione sopra è una soluzione in C semplice).


Questo funzionerà solo se il mio uso del conteggio delle righe è nello stesso ambito delle righe contate. IIANM. Nota: ho modificato leggermente la mia domanda per sottolineare che potrebbe essere un problema.
einpoklum,

1
@einpoklum Anche una __COUNTER__soluzione basata ha problemi: speri che la tua macro magica sia l'unico utente __COUNTER__, almeno prima che tu abbia finito di usarla __COUNTER__. Fondamentalmente, il problema dipende tutto dai semplici fatti che __COUNTER__/__LINE__sono funzioni del preprocessore e il preprocessore funziona in un unico passaggio, quindi non è possibile eseguire il backpatch di un'espressione costante intera in seguito in base a __COUNTER__/ __LINE__. L'unico modo (almeno in C) è di evitare la necessità in primo luogo, ad esempio, utilizzando dichiarazioni di array forward senza dimensioni (dichiarazioni di array tipizzate in modo incompleto).
PSkocik,

1
Per la cronaca, \ ciò non influisce __LINE__- se c'è un'interruzione di linea, __LINE__aumenta. Esempio 1 , esempio 2 .
Max Langhof

@MaxLanghof Grazie. Non me ne sono reso conto. Fisso.
PSkocik
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.