Compter les lignes du fichier source à l'aide de macros?

15

Est-il possible, en utilisant le préprocesseur C / C ++, de compter les lignes d'un fichier source, dans une macro ou une sorte de valeur disponible au moment de la compilation? Par exemple, puis-je remplacer MAGIC1, MAGIC2et MAGIC3dans ce qui suit, et obtenir la valeur 4 en quelque sorte lors de l'utilisation 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

Remarques:

  • Les extensions spécifiques au compilateur des capacités du préprocesseur sont acceptables mais indésirables.
  • Si cela n'est possible qu'avec l'aide d'une partie du C ++, par opposition à la construction C, c'est également acceptable mais indésirable (c'est-à-dire que j'aimerais quelque chose qui fonctionnerait pour C).
  • Évidemment, cela peut être fait en exécutant le fichier source via un script de processeur externe, mais ce n'est pas ce que je demande.
einpoklum
la source
6
Il y a une macro appelée__LINE__ qui représente le numéro de ligne actuel
ForceBru
2
Cherchez-vous __COUNTER__et / ou BOOST_PP_COUNTER?
KamilCuk
11
Quel est le problème réel que vous devez résoudre? Pourquoi en avez-vous besoin?
Un programmeur du
1
Est- ce que cela aide?
user1810087
1
@PSkocik: Je veux quelque chose que je pourrais utiliser comme constante de temps de compilation, par exemple pour dire int arr[MAGIC4]et obtenir le nombre de lignes dans une section précédemment comptée de mon code.
einpoklum

Réponses:

15

Il y a la __LINE__macro du préprocesseur qui vous donne un entier pour la ligne qui apparaît. Vous pouvez prendre sa valeur sur une ligne, puis sur une ligne ultérieure, et comparer.

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

Si vous souhaitez compter les occurrences de quelque chose plutôt que des lignes source, il __COUNTER__peut s'agir d'une option non standard, prise en charge par certains compilateurs tels que GCC et 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

J'ai pris la valeur initiale de __COUNTER__car elle aurait pu être utilisée précédemment dans le fichier source ou dans certains en-têtes inclus.

En C plutôt qu'en C ++, il y a des limitations sur les variables constantes, donc un enumpourrait être utilisé à la place.

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

Remplacer la const par enum:

enum {BEFORE = __LINE__};
foo();
bar();
baz();
quux();
enum { COUNT = __LINE__ - BEFORE - 1};
enum MyEnum
{
    FOO = COUNT // OK
};
Lancer de feu
la source
Je pense que c'est à peu près le plus proche que vous pouvez obtenir avec juste le préprocesseur. Le préprocesseur est en un seul passage, vous ne pouvez donc pas backpatch une valeur calculée plus tard, mais les références de variables globales fonctionneront et devraient optimiser la même chose. Ils ne fonctionneront tout simplement pas aux expressions de constantes entières, mais il pourrait être possible de structurer le code de sorte que celles-ci ne soient pas nécessaires pour les décomptes.
PSkocik
2
__COUNTER__n'est pas standard en C ou C ++. Si vous savez que cela fonctionne avec des compilateurs particuliers, spécifiez-les.
Peter
@einpoklum non, BEFOREet ce AFTERne sont pas des macros
Alan Birtles
Il y a un problème avec la version non-compteur de cette solution: l'avant et l'après ne sont utilisables que dans la même portée que les lignes sources. Modifié mon extrait de "par exemple" pour refléter qu'il s'agit d'un problème.
einpoklum
1
@ user694733 La vraie question a été balisée [C ++]. Pour les constantes C enum fonctionnent.
Fire Lancer
9

Je sais que la demande de l'OP est d'utiliser des macros, mais je voudrais ajouter une autre façon de faire qui n'implique pas l'utilisation de macros.

C ++ 20 présente la source_locationclasse qui représente certaines informations sur le code source, telles que les noms de fichier, les numéros de ligne et les noms de fonction. Nous pouvons l'utiliser assez facilement dans ce cas.

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

Et l'exemple vivant ici .

Casse Noisette
la source
Sans macros, c'est encore mieux qu'avec des macros. Cependant - avec cette approche, je ne peux utiliser le nombre de lignes que dans la même portée que les lignes que j'ai comptées. Aussi - avec source_locationêtre expérimental en C ++ 20?
einpoklum
Je suis d'accord que la solution sans macros est bien meilleure qu'avec des macros. source_locationfait désormais officiellement partie de C ++ 20. Vérifiez ici . Je n'ai tout simplement pas pu trouver la version du compilateur gcc sur godbolt.org qui le supporte déjà dans un sens non expérimental. Pouvez-vous expliquer un peu plus votre déclaration - je ne peux utiliser le nombre de lignes que dans la même portée que les lignes que j'ai comptées ?
NutCracker
Supposons que je mette votre suggestion dans une fonction (c'est-à-dire que les lignes comptées sont des invocations, pas des déclarations). Cela fonctionne - mais je n'ai line_number_startet line_number_enddans cette portée, nulle part ailleurs. Si je le veux ailleurs, je dois le passer à l'exécution - ce qui va à l'encontre du but.
einpoklum
Jetez un œil à l'exemple fourni par la norme ici . Si c'est l'argument par défaut, alors il fait toujours partie de la compilation, non?
NutCracker
Oui, mais cela ne fait pas line_number_end visible au moment de la compilation en dehors de sa portée. Corrige moi si je me trompe.
einpoklum
7

Pour être complet: si vous êtes prêt à ajouter MAGIC2après chaque ligne, vous pouvez utiliser __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 (renvoie 3)

Vous pouvez le rendre réutilisable en stockant les valeurs de début et de fin de __COUNTER__.

Dans l'ensemble, c'est cependant très lourd. Vous ne pourrez pas non plus compter les lignes qui contiennent des directives de préprocesseur ou qui se terminent par des //commentaires. J'utiliserais à la __LINE__place, voir l'autre réponse.

Max Langhof
la source
1
pourquoi utilisez-vous static_assert?
idclev 463035818
1
Cela a donné "9" dans le fichier source dans lequel je l'ai déposé, vous ne pouvez pas supposer qu'il __COUNTER__est toujours nul au départ car d'autres en-têtes, etc. pourraient l'utiliser.
Fire Lancer
vous devez utiliser la valeur de __COUNTER__deux fois et prendre la différence
idclev 463035818
1
@ formerlyknownas_463035818 __COUNTER__seul ne serait pas autorisé, et il doit être étendu à quelque chose ou cela ne comptera pas (je ne me souviens pas des règles à 100% à ce sujet).
Fire Lancer
7

Une solution un peu plus robuste, permettant différents compteurs (tant qu'ils ne se mélangent pas, et il n'y a pas d'utilisation de __COUNTER__ pour d'autres tâches):

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

Cela masque les détails de l'implémentation (bien qu'il les cache à l'intérieur des macros ...). C'est une généralisation de la réponse de @ MaxLanghof. Notez que __COUNTER__peut avoir une valeur non nulle lorsque nous commençons un comptage.

Voici comment il est utilisé:

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

En outre, il s'agit d'un C valide - si votre préprocesseur le prend en charge __COUNTER__, c'est-à-dire.

Fonctionne sur GodBolt .

Si vous utilisez C ++, vous pouvez modifier cette solution pour ne même pas polluer l'espace de noms global - en plaçant les compteurs à l'intérieur namespace macro_based_line_counts { ... }, ou namespace detailetc.)

einpoklum
la source
5

Sur la base de votre commentaire, si vous souhaitez spécifier une taille de tableau (au moment de la compilation) en C ou C ++, vous pouvez faire

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

Si vous sizeof(array)en avez besoin dans les lignes intermédiaires, vous pouvez la remplacer par une référence de variable statique (sauf si elle doit absolument être une expression constante entière) et un compilateur d'optimisation devrait la traiter de la même manière (éliminer le besoin de placer la variable statique en mémoire)

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

Une __COUNTER__solution basée sur le système (si cette extension est disponible) par opposition à une solution basée sur __LINE__le même fonctionnement.

constexprs en C ++ devrait fonctionner aussi bien enum, mais enumfonctionnera également en C simple (ma solution ci-dessus est une solution en C simple).

PSkocik
la source
Cela ne fonctionnera que si mon utilisation du nombre de lignes est dans la même portée que les lignes comptées. IIANM. Remarque J'ai légèrement modifié ma question pour souligner que cela pourrait être un problème.
einpoklum
1
@einpoklum Une __COUNTER__solution basée a aussi des problèmes: vous feriez mieux d'espérer que votre macro magique est le seul utilisateur de __COUNTER__, au moins avant que vous n'ayez fini d'utiliser __COUNTER__. Le problème se résume essentiellement aux faits simples qui __COUNTER__/__LINE__sont des fonctionnalités du préprocesseur et le préprocesseur fonctionne en une seule passe, vous ne pouvez donc pas backpatch une expression constante entière plus tard sur la base de __COUNTER__/ __LINE__. La seule façon (en C au moins) est d'éviter la nécessité en premier lieu, par exemple, en utilisant des déclarations de tableau vers l'avant sans taille (déclarations de tableau incomplètement typées).
PSkocik
1
Pour mémoire, le \ n'affecte pas __LINE__- s'il y a un saut de ligne, __LINE__augmente. Exemple 1 , exemple 2 .
Max Langhof
@MaxLanghof Merci. Je ne m'en étais pas rendu compte. Fixé.
PSkocik