C / C ++ - Zeilennummer

109

Kann ich zum Zwecke des Debuggens die Zeilennummer in C / C ++ - Compilern abrufen ? (Standardmethode oder spezielle Methoden für bestimmte Compiler)

z.B

if(!Logical)
    printf("Not logical value at line number %d \n",LineNumber);
    // How to get LineNumber without writing it by my hand?(dynamic compilation)
Betamoo
quelle
17
@ Lucas: Einige von uns ziehen es vor, sich nicht mit Debuggern anzulegen. Diese Art der "Behauptung einer armen Person" ist manchmal klarer, weil sie ein fester Bestandteil des Codes ist und eine dauerhafte Dokumentation von Dingen enthält, die über den Stand der Berechnung zutreffen sollten.
S.Lott
13
@Lucas: Debugger sind auch weniger nützlich für zeitweise auftretende Probleme in Programmen mit langer Laufzeit oder zum Sammeln von Informationen über Probleme in Software, die auf Client-Standorten bereitgestellt wird. In diesen Fällen besteht die einzige Möglichkeit darin, dass das Programm so viele Informationen wie möglich über den Status des Programms für eine spätere Analyse protokolliert.
KeithB
1
@Lucas Und Debugger funktionieren auf einigen eingebetteten Systemen nicht so gut, um diese Informationen zu erhalten.
George Stocker

Antworten:

179

Sie sollten das Präprozessor-Makro __LINE__und verwenden __FILE__. Sie sind vordefinierte Makros und Teil des C / C ++ - Standards. Während der Vorverarbeitung werden sie jeweils durch eine konstante Zeichenfolge ersetzt, die eine Ganzzahl enthält, die die aktuelle Zeilennummer darstellt, sowie durch den aktuellen Dateinamen.

Andere Präprozessorvariablen:

  • __func__: Funktionsname (dies ist Teil von C99 , wird nicht von allen C ++ - Compilern unterstützt)
  • __DATE__ : eine Zeichenfolge der Form "Mmm tt jjjj"
  • __TIME__ : eine Zeichenfolge der Form "hh: mm: ss"

Ihr Code lautet:

if(!Logical)
  printf("Not logical value at line number %d in file %s\n", __LINE__, __FILE__);
Julien Hoarau
quelle
2
C99 verwendet __func__ anstelle von __FUNCTION__, was AFAIK teilweise veraltet ist. Der Unterschied kann Ihren Code beschädigen, da __func__ nicht für die konstante Verkettung von C-Zeichenfolgen verwendet werden kann.
Joseph Quinsey
1
Referenz aus dem GCC-Handbuch: "__FUNCTION__ und __PRETTY_FUNCTION__ wurden als Zeichenfolgenliterale behandelt; sie konnten zum Initialisieren von Zeichenarrays verwendet und mit anderen Zeichenfolgenliteralen verkettet werden. GCC 3.4 und später als Variablen wie __func__. In C ++ __FUNCTION__ und __PRETTY_FUNCTION__ waren schon immer Variablen. "
Joseph Quinsey
Gibt es eine Möglichkeit, die Zeilennummer als Zeichenfolge abzurufen, genau wie der Dateiname? Ich möchte, dass der Präprozessor mir zB das String-Literal "22" anstelle der Ganzzahl 22 gibt.
Sep332
1
@ sep332 Ja, aber das cpp ist ein seltsames Tier, daher muss es in zwei Schritten mit Makroargumenten durchgeführt werden. #define S1(N) #N #define S2(N) S1(N) #define LINESTR S2(__LINE__). Siehe c-faq.com/ansi/stringize.html
Rasmus Kaj
1
Streng genommen __func__handelt es sich nicht um ein Makro, sondern um eine implizit deklarierte Variable.
HolyBlackCat
64

Als Teil des C ++ - Standards gibt es einige vordefinierte Makros, die Sie verwenden können. Abschnitt 16.8 des C ++ - Standards definiert unter anderem das __LINE__Makro.

__LINE__: Die Zeilennummer der aktuellen Quellzeile (eine Dezimalkonstante).
__FILE__: Der vermutete Name der Quelldatei (ein Zeichenfolgenliteral).
__DATE__: Das Datum der Übersetzung der Quelldatei (ein Zeichenfolgenliteral ...)
__TIME__: Der Zeitpunkt der Übersetzung der Quelldatei (ein Zeichenfolgenliteral ...)
__STDC__: Ob __STDC__vordefiniert ist
__cplusplus: Der Name __cpluspluswird auf den Wert 199711L definiert, wenn Kompilieren einer C ++ - Übersetzungseinheit

Ihr Code wäre also:

if(!Logical)
  printf("Not logical value at line number %d \n",__LINE__);
Brian R. Bondy
quelle
19

Sie können ein Makro mit demselben Verhalten wie printf () verwenden , außer dass es auch Debug-Informationen wie Funktionsname, Klasse und Zeilennummer enthält:

#include <cstdio>  //needed for printf
#define print(a, args...) printf("%s(%s:%d) " a,  __func__,__FILE__, __LINE__, ##args)
#define println(a, args...) print(a "\n", ##args)

Diese Makros sollten sich identisch mit printf () verhalten und Java-Stacktrace-ähnliche Informationen enthalten. Hier ist ein Hauptbeispiel:

void exampleMethod() {
    println("printf() syntax: string = %s, int = %d", "foobar", 42);
}

int main(int argc, char** argv) {
    print("Before exampleMethod()...\n");
    exampleMethod();
    println("Success!");
}

Was zu folgender Ausgabe führt:

main (main.cpp: 11) Vor exampleMethod () ...
exampleMethod (main.cpp: 7) printf () -Syntax: string = foobar, int = 42
main (main.cpp: 13) Erfolg!

Einige Java-Programmierer
quelle
für c Entwicklung würden Sie das #includeto<stdio.h>
phyatt
11

Verwenden Sie __LINE__(das ist doppelter Unterstrich LINE doppelter Unterstrich), der Präprozessor ersetzt ihn durch die Zeilennummer, auf die er stößt.

meagar
quelle
9

Kasse __FILE__und __LINE__Makros

Anton
quelle
5

C ++ 20 bietet eine neue Möglichkeit, dies mithilfe von std :: source_location zu erreichen . Dies ist derzeit in gcc an clang wie std::experimental::source_locationbei zugänglich #include <experimental/source_location>.

Das Problem bei Makros wie __LINE__ist, dass Sie, wenn Sie beispielsweise eine Protokollierungsfunktion erstellen möchten, die die aktuelle Zeilennummer zusammen mit einer Nachricht ausgibt, immer __LINE__als Funktionsargument übergeben müssen, da diese am Aufrufstandort erweitert wird. Etwas wie das:

void log(const std::string msg) {
    std::cout << __LINE__ << " " << msg << std::endl;
}

Gibt immer die Zeile der Funktionsdeklaration aus und nicht die Zeile, in der log tatsächlich aufgerufen wurde. Auf der anderen Seite können std::source_locationSie mit so etwas schreiben:

#include <experimental/source_location>
using std::experimental::source_location;

void log(const std::string msg, const source_location loc = source_location::current())
{
    std::cout << loc.line() << " " << msg << std::endl;
}

Hier locwird mit der Zeilennummer initialisiert, die auf den Ort zeigt, an dem logaufgerufen wurde. Sie können es hier online ausprobieren.

Sirain
quelle
4

Versuchen Sie __FILE__und __LINE__.
Sie könnten auch finden __DATE__und __TIME__nützlich.
Wenn Sie jedoch kein Programm auf der Clientseite debuggen und daher diese Informationen protokollieren müssen, sollten Sie das normale Debugging verwenden.

Sanctus2099
quelle
Warum wurde ich dafür abgelehnt und warum haben mmyers meinen Beitrag bearbeitet?
Sanctus2099
@ Sanctus2099: Es wurde bearbeitet, weil Markdown Ihre doppelten Unterstriche so transformiert hat, dass FILE und LINE in Fettdruck angezeigt werden (überprüfen Sie nicht, wie Ihre Antwort aussieht?). Ein weiterer Punkt könnte sein (zumindest sieht es jetzt so aus), dass Sie 1 Stunde nach der Abgabe einer bereits korrekten Antwort eine Antwort gegeben haben, sodass Sie keinen Wert hinzugefügt haben.
Felix Kling
Doppelter Unterstrich ist die Markup-Syntax für Fettdruck . Um doppelte Unterstriche richtig anzuzeigen, müssen Sie sie maskieren raw code(wie folgt: \ _ \ _) oder Backticks verwenden, um sie als (wie folgt: `__`) zu markieren . @mmyers versuchte zu helfen, aber er entkam nur einem der Unterstriche, und so blieb Ihnen die Markup-Syntax für Kursivschrift . Downvotes sind hier allerdings etwas hart, da stimme ich zu.
Matt B.
Okay, ich wusste nicht, dass doppelte Unterstriche Fett in Fettdruck verwandeln, und ich musste gehen und hatte keine Zeit zu sehen, wie meine Antwort aussah. Ich verstehe jetzt. Auch wenn meine Antwort eine Stunde zu spät war, war sie dennoch eine gute Antwort. Es hat keinen Mehrwert gebracht, aber es war auch nicht falsch, also kein Grund für eine Ablehnung. Das bekommen Sie, wenn Sie versuchen zu helfen ...
Sanctus2099
2
@ Sanctus2099 Einige Leute stimmen schnell ab. Deshalb ist es wichtig, dass Ihre Antwort korrekt ist. In diesem Fall haben Sie eine falsche Antwort gepostet und diese 4 Stunden lang unbearbeitet gelassen. Sie haben niemanden zu beschuldigen außer sich selbst.
Meagar
1

Da ich jetzt auch mit diesem Problem konfrontiert bin und keine Antwort auf eine andere, aber auch gültige Frage hinzufügen kann, die hier gestellt wird , werde ich eine Beispiellösung für das Problem bereitstellen: nur die Zeilennummer abrufen, in der die Funktion aufgerufen wurde C ++ mit Vorlagen.

Hintergrund: In C ++ können nicht ganzzahlige Ganzzahlwerte als Vorlagenargument verwendet werden. Dies unterscheidet sich von der typischen Verwendung von Datentypen als Vorlagenargumente. Die Idee ist also, solche ganzzahligen Werte für einen Funktionsaufruf zu verwenden.

#include <iostream>

class Test{
    public:
        template<unsigned int L>
        int test(){
            std::cout << "the function has been called at line number: " << L << std::endl;
            return 0;
        }
        int test(){ return this->test<0>(); }
};

int main(int argc, char **argv){
    Test t;
    t.test();
    t.test<__LINE__>();
    return 0;
}

Ausgabe:

Die Funktion wurde unter der Zeilennummer 0 aufgerufen

Die Funktion wurde unter der Zeilennummer 16 aufgerufen

Hier ist zu erwähnen, dass es in C ++ 11 Standard möglich ist, Standardvorlagenwerte für Funktionen mithilfe von Vorlagen anzugeben. In Pre C ++ 11 scheinen Standardwerte für Argumente ohne Typ nur für Klassenvorlagenargumente zu funktionieren. Daher wäre es in C ++ 11 nicht erforderlich, doppelte Funktionsdefinitionen wie oben zu haben. In C ++ 11 ist es auch gültig, const char * -Vorlagenargumente zu haben, aber es ist nicht möglich, sie mit Literalen wie __FILE__oder __func__wie hier erwähnt zu verwenden .

Wenn Sie also C ++ oder C ++ 11 verwenden, ist dies möglicherweise eine sehr interessante Alternative als die Verwendung von Makros, um die aufrufende Leitung zu erhalten.

John Doe
quelle
1

Verwenden Sie __LINE__, aber was ist sein Typ?

LINE Die angenommene Zeilennummer (innerhalb der aktuellen Quelldatei) der aktuellen Quellzeile (eine Ganzzahlkonstante).

Als Ganzzahlkonstante kann Code häufig annehmen, dass der Wert __LINE__ <= INT_MAXund damit der Typ ist int.

Zum Drucken in C wird printf()der passende Bezeichner benötigt : "%d". Dies ist in C ++ ein weitaus geringeres Problem cout.

Pedantisches Problem: Wenn die Zeilennummer INT_MAX1 überschreitet (mit 16 Bit etwas denkbar int), wird der Compiler hoffentlich eine Warnung ausgeben. Beispiel:

format '%d' expects argument of type 'int', but argument 2 has type 'long int' [-Wformat=]

Alternativ könnte Code breitere Typen dazu zwingen, solchen Warnungen zuvorzukommen.

printf("Not logical value at line number %ld\n", (long) __LINE__);
//or
#include <stdint.h>
printf("Not logical value at line number %jd\n", INTMAX_C(__LINE__));

Vermeiden printf()

So vermeiden Sie alle Einschränkungen für Ganzzahlen: stringify . Code kann direkt ohne printf()Anruf gedruckt werden : eine nette Sache, die Sie bei der Fehlerbehandlung vermeiden sollten 2 .

#define xstr(a) str(a)
#define str(a) #a

fprintf(stderr, "Not logical value at line number %s\n", xstr(__LINE__));
fputs("Not logical value at line number " xstr(__LINE__) "\n", stderr);

1 Sicherlich schlechte Programmierpraxis, um eine so große Datei zu haben, aber vielleicht kann maschinengenerierter Code hoch gehen.

2 Beim Debuggen funktioniert Code manchmal einfach nicht wie erhofft. Das Aufrufen komplexer Funktionen wie *printf()kann selbst Probleme verursachen, im Gegensatz zu einfachen fputs().

chux - Monica wieder einsetzen
quelle
1

Für diejenigen, die es möglicherweise benötigen, ein "FILE_LINE" -Makro zum einfachen Drucken von Dateien und Zeilen:

#define STRINGIZING(x) #x
#define STR(x) STRINGIZING(x)
#define FILE_LINE __FILE__ ":" STR(__LINE__)
clarkttfu
quelle