Quelldateizeilen mit Makros zählen?

15

Ist es mit dem C / C ++ - Präprozessor möglich, Zeilen innerhalb einer Quelldatei entweder in ein Makro oder in einen zur Kompilierungszeit verfügbaren Wert zu zählen? Zum Beispiel kann ich ersetzen MAGIC1, MAGIC2und MAGIC3in den folgenden, und den Wert 4 irgendwie erhält bei der Verwendung 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

Anmerkungen:

  • Compilerspezifische Erweiterungen der Funktionen des Präprozessors sind akzeptabel, aber unerwünscht.
  • Wenn dies nur mit Hilfe einiger C ++ - Konstrukte möglich ist, im Gegensatz zu C, ist dies ebenfalls akzeptabel, aber unerwünscht (dh ich möchte etwas, das für C funktioniert).
  • Natürlich kann dies durch Ausführen der Quelldatei über ein externes Prozessorskript erfolgen, aber das ist nicht das, was ich frage.
einpoklum
quelle
6
Es gibt ein Makro namens__LINE__ , das die aktuelle Zeilennummer darstellt
ForceBru
2
Suchen __COUNTER__und / oder BOOST_PP_COUNTER?
KamilCuk
11
Was ist das eigentliche Problem, das Sie lösen müssen? Warum brauchst du das?
Ein Programmierer
1
Hilft das ?
user1810087
1
@PSkocik: Ich möchte etwas, das ich als Konstante für die Kompilierungszeit verwenden kann, z. B. int arr[MAGIC4]um die Anzahl der Zeilen in einem zuvor gezählten Abschnitt meines Codes zu sagen und abzurufen .
Einpoklum

Antworten:

15

Es gibt das __LINE__Präprozessor-Makro, das Ihnen eine Ganzzahl für die Zeile gibt, auf der angezeigt wird. Sie können den Wert in einer Zeile und später in einer Zeile angeben und vergleichen.

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

Wenn Sie das Auftreten von etwas anstelle __COUNTER__von Quellzeilen zählen möchten, ist dies möglicherweise eine nicht standardmäßige Option, die von einigen Compilern wie GCC und MSVC unterstützt wird.

#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

Ich habe den Anfangswert von genommen, __COUNTER__weil er möglicherweise zuvor in der Quelldatei oder in einem enthaltenen Header verwendet wurde.

In C und nicht in C ++ gibt es Einschränkungen für konstante Variablen, daher enumkann stattdessen eine verwendet werden.

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

Ersetzen der Konstante durch enum:

enum {BEFORE = __LINE__};
foo();
bar();
baz();
quux();
enum { COUNT = __LINE__ - BEFORE - 1};
enum MyEnum
{
    FOO = COUNT // OK
};
Fire Lancer
quelle
Ich denke, dies ist ungefähr das Nächste, was Sie nur mit dem Präprozessor erreichen können. Der Präprozessor ist ein Durchgang, daher können Sie einen später berechneten Wert nicht zurückpatchen, aber Referenzen mit globalen Variablen funktionieren und sollten diesen optimieren. Sie funktionieren bei Ausdrücken mit ganzzahligen Konstanten einfach nicht, aber es ist möglicherweise möglich, den Code so zu strukturieren, dass diese für die Zählung nicht benötigt werden.
PSkocik
2
__COUNTER__ist in C oder C ++ nicht Standard. Wenn Sie wissen, dass es mit bestimmten Compilern funktioniert, geben Sie diese an.
Peter
@einpoklum nein, BEFOREund AFTERsind keine Makros
Alan Birtles
Bei der Nicht-Zähler-Version dieser Lösung gibt es ein Problem: Das Vorher und Nachher kann nur im gleichen Bereich wie die Quellzeilen verwendet werden. Ich habe mein "eg" -Schnipsel bearbeitet, um zu reflektieren, dass dies ein Problem ist.
Einpoklum
1
@ user694733 Wahre Frage wurde mit [C ++] markiert. Für C enum funktionieren Konstanten.
Fire Lancer
9

Ich weiß, dass die Anforderung des OP die Verwendung von Makros ist, aber ich möchte eine andere Möglichkeit hinzufügen, bei der keine Makros verwendet werden.

In C ++ 20 wird die source_locationKlasse eingeführt, die bestimmte Informationen zum Quellcode darstellt, z. B. Dateinamen, Zeilennummern und Funktionsnamen. Wir können das in diesem Fall ziemlich einfach verwenden.

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

Und anschauliches Beispiel hier .

Nussknacker
quelle
Ohne Makros ist noch besser als mit Makros. Bei diesem Ansatz kann ich die Zeilenanzahl jedoch nur im selben Bereich wie die von mir gezählten Zeilen verwenden. Auch - mit source_locationexperimentieren in C ++ 20?
Einpoklum
Ich stimme zu, dass eine Lösung ohne Makros weitaus besser ist als mit Makros. source_locationist jetzt offiziell Teil von C ++ 20. Überprüfen Sie hier . Ich konnte die Version des gcc-Compilers auf godbolt.org einfach nicht finden , die sie bereits im nicht experimentellen Sinne unterstützt. Können Sie bitte Ihre Aussage etwas näher erläutern ? Ich kann die Zeilenanzahl nur im selben Bereich verwenden wie die Zeilen, die ich gezählt habe .
NutCracker
Angenommen, ich habe Ihren Vorschlag in eine Funktion eingefügt (dh die gezählten Zeilen sind Aufrufe, keine Deklarationen). Es funktioniert - aber ich habe nur line_number_startund line_number_endin diesem Bereich nirgendwo anders. Wenn ich es woanders haben will, muss ich es zur Laufzeit weitergeben - was den Zweck zunichte macht.
Einpoklum
Schauen Sie sich das Beispiel an, das Standard hier bereitstellt . Wenn es sich um ein Standardargument handelt, ist es immer noch Teil der Kompilierungszeit, oder?
NutCracker
Ja, aber das wird line_number_endzur Kompilierungszeit außerhalb seines Geltungsbereichs nicht sichtbar. Korrigiere mich, wenn ich falsch liege.
Einpoklum
7

Der Vollständigkeit halber: Wenn Sie MAGIC2nach jeder Zeile hinzufügen möchten, können Sie Folgendes verwenden __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 (kehrt zurück 3)

Sie können es wiederverwendbar machen, indem Sie die Start- und Endwerte von speichern __COUNTER__.

Insgesamt ist dies allerdings sehr umständlich. Sie können auch keine Zeilen zählen, die Präprozessoranweisungen enthalten oder mit //Kommentaren enden . Ich würde __LINE__stattdessen verwenden, siehe die andere Antwort.

Max Langhof
quelle
1
Warum verwenden Sie static_assert?
idclev 463035818
1
Dies ergab "9" in der Quelldatei, in die ich es abgelegt habe. Sie können nicht davon ausgehen, dass __COUNTER__es anfangs immer noch Null ist, da andere Header usw. es möglicherweise verwenden.
Fire Lancer
Sie müssen den Wert __COUNTER__zweimal verwenden und die Differenz nehmen
idclev 463035818
1
@ ehemalsknownas_463035818 __COUNTER__alleine wäre nicht erlaubt und es muss auf etwas erweitert werden oder es zählt nicht (ich kann mich nicht zu 100% an die Regeln erinnern).
Fire Lancer
7

Eine etwas robustere Lösung, die unterschiedliche Zähler zulässt (solange sie sich nicht vermischen und __COUNTER__für andere Aufgaben nicht verwendet werden können):

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

Dadurch werden die Implementierungsdetails ausgeblendet (obwohl sie in Makros ausgeblendet sind ...). Es ist eine Verallgemeinerung der Antwort von @ MaxLanghof. Beachten Sie, dass der __COUNTER__Wert ungleich Null sein kann, wenn wir eine Zählung starten.

So wird es verwendet:

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

Dies ist auch C gültig - wenn Ihr Präprozessor dies unterstützt __COUNTER__.

Arbeitet an GodBolt .

Wenn Sie C ++ verwenden, können Sie diese Lösung modifizieren , nicht einmal den globalen Namensraum verschmutzen - durch die Zähler Platzierung innerhalb namespace macro_based_line_counts { ... }oder namespace detailetc.)

einpoklum
quelle
5

Basierend auf Ihrem Kommentar können Sie eine Arraygröße (zur Kompilierungszeit) in C oder C ++ angeben

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

Wenn Sie sizeof(array)in den dazwischenliegenden Zeilen benötigen , können Sie es durch eine statische Variablenreferenz ersetzen (es sei denn, es muss unbedingt ein ganzzahliger konstanter Ausdruck sein), und ein optimierender Compiler sollte es genauso behandeln (die statische Variable muss nicht platziert werden) in Erinnerung)

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

Eine __COUNTER__Lösung auf Basis (falls diese Erweiterung verfügbar ist) im Gegensatz zu einer Lösung auf __LINE__Basis funktioniert genauso.

constexprs in C ++ sollte genauso gut funktionieren wie enum, enumfunktioniert aber auch in einfachem C (meine obige Lösung ist eine einfache C-Lösung).

PSkocik
quelle
Dies funktioniert nur, wenn meine Verwendung der Zeilenanzahl im selben Bereich liegt wie die gezählten Zeilen. IIANM. Hinweis: Ich habe meine Frage leicht bearbeitet, um hervorzuheben, dass dies ein Problem sein könnte.
Einpoklum
1
@einpoklum Eine __COUNTER__basierte Lösung hat auch Probleme: Sie hoffen besser, dass Ihr magisches Makro der einzige Benutzer von ist __COUNTER__, zumindest bevor Sie mit der Verwendung von fertig sind __COUNTER__. Das Problem __COUNTER__/__LINE__beruht im Wesentlichen auf den einfachen Fakten, die Präprozessorfunktionen sind, und der Präprozessor arbeitet in einem Durchgang, sodass Sie einen ganzzahligen konstanten Ausdruck später nicht basierend auf __COUNTER__/ zurückpatchen können __LINE__. Die einzige Möglichkeit (zumindest in C) besteht darin, die Notwendigkeit zu vermeiden, z. B. indem Forward-Array-Deklarationen ohne Größe verwendet werden (unvollständig typisierte Array-Deklarationen).
PSkocik
1
Für den Datensatz \ wirkt sich das nicht aus __LINE__- wenn es einen Zeilenumbruch gibt, __LINE__erhöht sich. Beispiel 1 , Beispiel 2 .
Max Langhof
@ MaxLanghof Danke. Ich habe das nicht bemerkt. Fest.
PSkocik