ChatGPT解决这个技术问题 Extra ChatGPT

为了调试目的,我可以在 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)
@Lucas:我们中的一些人不喜欢与调试器混淆。这种“穷人的断言”有时更清楚,因为它是代码的永久部分,并且是关于计算状态应该是真实的事物的持久文档。
@Lucas:调试器对于长时间运行的程序中的间歇性问题或收集有关部署在客户端站点的软件问题的信息也不太有用。在这些情况下,唯一的选择是让程序尽可能多地记录有关程序状态的信息,以供以后分析。
@Lucas 并且调试器在某些嵌入式系统上无法很好地获取此信息。

J
Julien Hoarau

您应该使用预处理器宏 __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__);

C99 使用 __func__ 而不是 __FUNCTION__,AFAIK 已部分弃用。差异可能会破坏您的代码,因为 __func__ 不能用于 C 的常量字符串连接。
来自 GCC 手册的参考:“__FUNCTION__ 和 __PRETTY_FUNCTION__ 被视为字符串文字;它们可用于初始化 char 数组,并且可以与其他字符串文字连接。GCC 3.4 及更高版本将它们视为变量,如 __func__。在 C++ 中,__FUNCTION__和 __PRETTY_FUNCTION__ 一直是变量。”
有没有办法将行号作为字符串获取,与文件名相同?我希望预处理器给我例如字符串文字“22”而不是整数 22。
@sep332 是的,但是 cpp 是一个奇怪的野兽,所以它必须用宏参数分两步完成。 #define S1(N) #N #define S2(N) S1(N) #define LINESTR S2(__LINE__) 。请参阅c-faq.com/ansi/stringize.html
严格来说,__func__ 不是宏,而是隐式声明的变量。
B
Brian R. Bondy

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

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

所以你的代码是:

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

B
BullyWiiPlaza

您可以使用与 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>
printf 不是 async-signal-safe。所以这个宏不能在信号处理程序中使用。
s
sirain

C++20 提供了一种通过使用 std::source_location 来实现此目的的新方法。这目前可以在 gcc 中以 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 的位置的行号。 You can try it online here.


u
user229044

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


A
Anton

签出 __FILE____LINE__


S
Sanctus2099

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


为什么我被否决了,为什么 mmyers 编辑我的帖子?
@ Sanctus2099:它已被编辑,因为 Markdown 将您的双下划线转换为以粗体显示 FILE 和 LINE(您不检查您的答案的样子吗?)。另一点可能是(至少现在在我看来是这样)您在给出已经正确答案的 1 小时后给出了答案,因此您没有增加任何价值。
双下划线是 bold 的标记语法。为了正确显示双下划线,您必须对它们进行转义(例如:\_\_)或使用反引号将它们标记为 raw code(例如:`__`)。 @mmyers 试图提供帮助,但他只转义了一个下划线,因此您只剩下 italics 的标记语法。不过,我同意这里的投票有点苛刻。
好吧,我没有意识到双下划线将文本变成粗体的事情,我不得不去,没有时间看看我的答案看起来如何。我现在明白了。即使我的回答晚了一个小时,它仍然是一个很好的答案。它没有增加任何价值,但也没有错,所以没有理由反对。这就是你试图帮助的结果......
@Sanctus2099 有些人很快就会投反对票,这就是为什么确保你的答案是正确的很重要。在这种情况下,您发布了一个错误的答案,并且在 4 小时内未对其进行编辑。除了你自己,你没有任何人可以责备。
c
clarkttfu

对于那些可能需要它的人,一个“FILE_LINE”宏可以轻松打印文件和行:

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

C
Community

由于我现在也面临这个问题,并且我无法为提出的不同但有效的问题 here 添加答案,因此我将为以下问题提供一个示例解决方案:仅获取函数所在位置的行号使用模板在 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 标准中,可以为使用模板的函数提供默认模板值。在 C++11 之前,非类型参数的默认值似乎只适用于类模板参数。因此,在 C++11 中,不需要像上面那样重复函数定义。在 C++11 中,具有 const char* 模板参数也是有效的,但不能将它们与 __FILE____func__ 等文字一起使用,如 here 所述。

所以最后,如果你使用 C++ 或 C++11,这可能是一个非常有趣的替代方案,而不是使用宏来获取调用行。


c
chux - Reinstate Monica

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

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

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

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

迂腐的担忧:如果行号超过 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 在调试中,有时代码根本无法按预期工作。与简单的 fputs() 相比,调用 *printf() 之类的复杂函数本身可能会产生问题。