C / C ++行号


109

为了调试目的,我可以在C / C ++编译器中获得行号吗?(某些编译器的标准方式或特定方式)

例如

if(!Logical)
    printf("Not logical value at line number %d \n",LineNumber);
    // How to get LineNumber without writing it by my hand?(dynamic compilation)

17
@卢卡斯:我们中有些人不喜欢与调试器混为一谈。这种“穷人的断言”有时会更清楚,因为它是代码的永久组成部分,并且持久地记录着关于计算状态应正确的事情。
S.Lott

13
@Lucas:调试器对于长时间运行的程序中的间歇性问题或用于收集有关客户端站点上部署的软件中的问题的信息的用处也不大。在这些情况下,唯一的选择是程序记录尽可能多的有关程序状态的信息,以供以后分析。
KeithB 2010年

1
@Lucas并且调试器在某些嵌入式系统上无法很好地获取此信息。
乔治·斯托克2015年

Answers:


179

您应该使用预处理宏__LINE____FILE__。它们是预定义的宏,并且是C / C ++标准的一部分。在预处理期间,它们分别被包含代表当前行号的整数的常量字符串和当前文件名替换。

其他预处理器变量:

  • __func__:函数名(这是C99的一部分,并非所有C ++编译器都支持它)
  • __DATE__ :形式为“ Mmm dd yyyy”的字符串
  • __TIME__ :形式为“ hh:mm:ss”的字符串

您的代码将是:

if(!Logical)
  printf("Not logical value at line number %d in file %s\n", __LINE__, __FILE__);

2
C99使用__func__而不是__FUNCTION__,而AFAIK已被部分弃用。差异可能会破坏您的代码,因为__func__不能用于C的常量字符串连接。
约瑟夫·昆西

1
来自GCC手册的引用:“ __FUNCTION__和__PRETTY_FUNCTION__被视为字符串文字;它们可以用于初始化char数组,并且可以与其他字符串文字串联。GCC3.4及更高版本将它们视为变量,例如__func__。在C ++中,__ FUNCTION__和__PRETTY_FUNCTION__始终是变量。”
约瑟夫·昆西

有没有一种方法可以将行号作为字符串获取,与文件名相同?我希望预处理器给我例如字符串文字“ 22”,而不是整数
22。– sep332 2012年

1
@ sep332是的,但是cpp是一个奇怪的野兽,因此必须使用宏参数分两步完成。#define S1(N) #N #define S2(N) S1(N) #define LINESTR S2(__LINE__)。参见c-faq.com/ansi/stringize.html
Rasmus Kaj

1
严格地说,__func__它不是一个宏,它是一个隐式声明的变量。
HolyBlackCat

64

作为C ++标准的一部分,存在一些可以使用的预定义宏。C ++标准的第16.8节定义了__LINE__宏。

__LINE__当前源行的行号(十进制常数)。
__FILE__源文件的假定名称(字符串文字)。
__DATE__源文件的翻译日期(字符串文字...)
__TIME__源文件的翻译时间(字符串文字...)
__STDC__是否__STDC__已预定义
__cplusplus名称__cplusplus在何时定义为值199711L编译C ++转换单元

因此您的代码将是:

if(!Logical)
  printf("Not logical value at line number %d \n",__LINE__);

19

您可以使用行为与printf()相同的宏,除了它还包含调试信息(例如函数名,类和行号)之外:

#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)

这些宏的行为应与printf()相同,同时包括类似java stacktrace的信息。这是一个主要示例:

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

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

结果如下:

main(main.cpp:11)在exampleMethod()之前...
exampleMethod(main.cpp:7)printf()语法:string = foobar,int = 42
main(main.cpp:13)成功!


对于c开发,您可以将其更改#include<stdio.h>
phyatt '16

11

使用__LINE__(即双下划线LINE双下划线),预处理器会将其替换为遇到该行的行号。



5

C ++ 20提供了一种通过使用std :: source_location实现此目的的新方法。目前可以在gcc和clang中std::experimental::source_location使用#include <experimental/source_location>

诸如此类的宏的问题__LINE__在于,如果要创建例如一个日志记录功能,该功能将输出当前行号以及一条消息,则必须始终将其__LINE__作为函数参数传递,因为它在调用站点处进行了扩展。像这样:

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

将始终输出函数声明的行,而不是log实际从中调用的行。另一方面,std::source_location您可以这样写:

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

在此,loc使用指向log被调用位置的行号进行初始化。 您可以在这里在线尝试。


4

尝试__FILE____LINE__
您可能还会发现__DATE____TIME__有用。
尽管除非必须在客户端上调试程序并因此需要记录这些信息,否则应使用常规调试。


为什么我对此一票否决,为什么mmyers编辑我的帖子?
Sanctus2099,2010年

@ Sanctus2099:它是经过编辑的,因为Markdown转换了双下划线以粗体显示FILE和LINE(您不检查答案的样子吗?)。另一点可能是(至少现在看来是这样),您在给出正确答案后1小时给出了答案,因此您没有增加任何价值。
菲利克斯·克林

双下划线是粗体的标记语法。为了正确显示双下划线,必须对它们进行转义(例如:\ _ \ _)或使用反引号将其标记为raw code(例如:__)。@mmyers试图提供帮助,但是他只跳过了下划线之一,因此您留下了斜体的标记语法。不过,我同意,这里的选票有点苛刻。
马特·B。

好的,我没有意识到将双下划线将文本变成粗体的事情,因此我不得不走了,没有时间去看我的答案。我现在明白了。即使我的回答迟到了一个小时,它仍然是一个很好的答案。它没有增加任何价值,但也没有错,因此没有理由投票。那就是您尝试提供帮助时得到的...
Sanctus2099,2010年

2
@ Sanctus2099有些人很快就投了反对票,这就是为什么确保您的答案正确很重要的原因。在这种情况下,您发布了错误的答案,并在4个小时内未对其进行编辑。除了你自己,你没有人要责备。
meagar

1

由于我现在也面临这个问题,并且无法在此处提出另一个不同但也有效的问题的答案,因此,我将为以下问题提供示例解决方案:仅获取已调用函数的行号使用模板的C ++。

背景:在C ++中,可以将非类型整数值用作模板参数。这与数据类型作为模板参数的典型用法不同。因此,想法是将此类整数值用于函数调用。

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

输出:

该函数已在行号0处调用

该函数已在行号16处调用

这里要提到的一件事是,在C ++ 11 Standard中,可以为使用模板的函数提供默认模板值。在C ++ 11之前的版本中,非类型参数的默认值似乎仅适用于类模板参数。因此,在C ++ 11中,将不需要具有上述重复的函数定义。在C ++ 11中,具有const char *模板参数也有效,但无法将其与如__FILE__此处__func__提到的文字一起使用

因此,最后,如果您使用的是C ++或C ++ 11,这可能是一个比使用宏来获取调用行更有趣的选择。


1

使用__LINE__,但是它的类型是什么?

LINE当前源行的假定行号(在当前源文件中)(整数常量)。

作为一个整数常数,代码通常可以假定值为__LINE__ <= INT_MAX,因此类型为 int

要使用C打印,printf()需要匹配的说明符:"%d"。在使用C ++的C ++中,这要少得多cout

值得关注的问题:如果行号超过INT_MAX1(某种程度上可以用16位来想象int),则希望编译器会发出警告。例:

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

或者,代码可以强制更广泛的类型来阻止此类警告。

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__));

避免 printf()

为了避免所有整数限制:stringify。代码可以直接打印而无需printf()调用:避免错误处理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当然,拥有如此大文件的编程习惯很差,但也许机器生成的代码可能过高。

2在调试中,有时代码根本无法按预期工作。*printf()与简单函数相比,调用复杂函数本身可能会引发问题fputs()


1

对于可能需要它的用户,可以使用“ FILE_LINE”宏轻松打印文件和行:

#define STRINGIZING(x) #x
#define STR(x) STRINGIZING(x)
#define FILE_LINE __FILE__ ":" STR(__LINE__)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.