程序员的知识教程库

网站首页 > 教程分享 正文

C++预定义宏(__LINE__、__FILE__、...)用法完整总结

henian88 2024-10-18 06:01:22 教程分享 8 ℃ 0 评论

C++语言标准预定义了一些宏,比如常用的__LINE__、__FILE__、__cplusplus等等,它们可以在所有的翻译单元直接使用而无需进行任何声明,但需要包含定义它们的头文件。

我们知道,C++代码调试的方法有很多种,比如使用gdb调试工具查看堆栈信息,使用std::cout、printf或日志打印等方式查看程序的执行路径和位置,再或者借助IDE的调试功能直接进行打断点运行等。

有时候我们会在程序中加一些断言信息,以帮助排除不应该出现的异常情况,一旦出现就说明程序执行异常。

程序中可能加了很多这种断言语句,如果能在断言失败时知道是哪个文件的哪一行出了问题就能快速帮助我们定位问题。

或者我们需要输出一些信息,且我们想知道这些信息是哪个函数输出出来的。

又或者我们想根据当前的C++版本来选择性执行不同的分支以便能够利用高版本的一些新特性。

以上这些都跟我们接下来要介绍的C++预定义宏有关。

1. assert和NDEBUG调试宏

预处理器宏assert和NDEBUG是一对相关的C++预处理器变量。在C++中,预处理器变量可以决定C++编译器是否对程序进行编译。

assert宏定义在<cassert>(C++头文件写法)或<asserth.h>(C头文件写法)头文件中,它的使用方式如下:

assert(expression)

其中expression是一个表达式,如果表达式为真,则该语句什么也不做;否则,向标准错误输出(std::cerr)打印断言失败信息(诊断信息,具体内容取决于实现)并终止程序(调用std::abort)。

诊断信息中包含该assert的函数名(__func__),以及包含该assert的源代码文件名(__FILE__)和行号(__LINE__),这三个双下划线开头的变量就是C++的预处理器宏,稍后我们具体介绍。

需要说明的是,由于assert是一个预处理器宏名字,它归预处理器而非编译器来管理,所以我们无需提供using声明就可以直接使用它,即直接使用assert而不是std::assert。

值得注意的是,NDEBUG宏未在标准库中定义。如果我们在包含<cassert>或<assert.h>的头文件中定义了NDEBUG宏,那么,在宏生效位置之后出现的assert(expression);语句将不做任何事情,无论assert的表达式是否为真。

2. C++标准预定义宏

C/C++语言标准中预定义了一些宏,这些宏在C++中是全局可见的。它们都是以双下划线开头,且绝大多数也以双下划线结尾。它们不可被取消定义(undef)或重新定义(redefine)。

下表列出的是C++语言标准支持的在所有翻译单元可直接使用的预定义宏。

编号

宏名

描述

1

__cplusplus

正在使用的C++标准的版本,取值如下:

  • 199711L (until C++11)
  • 201103L (C++11)
  • 201402L (C++14)
  • 201703L (C++17)
  • 202002L (C++20)
  • 202302L (C++23)

2

__FILE__

当前文件的名字,是一个C风格字符串常量,可以通过#line指令修改

3

__LINE__

当前程序所在文件的行号,是一个十进制整数,可以通过#line指令修改

4

__DATA__

日期,是一个C风格字符串常量,格式:"Mmm dd yyyy"。如果天数小于10,用一个空格填补

5

__TIME__

时间,是一个C风格字符串常量,格式:"hh:mm:ss"

6

__STDC_HOSTED__

如果编译器的目标系统环境中包含完整的标准C库,那么其值为1;如果编译器的目标系统环境不包含完整的标准C库(如嵌入式OS或OS内核),则其值为0

7

__STDC__

值由具体的编译器实现定义,通常值为1,表示编译器符合ISO标准C

8

__STDC_VERSION__

表示当前编译器所遵循的C语言标准版本。格式:"yyyymmL",取值如:199901L表示编译器遵循的是C99标准(1999年1月发布)

9

__STDCPP_THREADS__

如果编译器支持C++标准库,那么其值为1

10

__STDC_ISO_10646__

表示编译器是否支持某个版本的ISO/IEC 10646标准。ISO/IEC 10646标准定义了一个用于表示世界上所有书写系统字符的通用字符集,即Unicode。它是一个整数常量,表示支持的ISO/IEC 10646的最新的版本修订,格式:"yyyymmL"

上表列出了C++语言标准所预定义的不依赖任何头文件的大多数宏(其中后4个是可选的,即编译器可以不必支持),还有部分是C++23引入的或者不常用的就没在这里列出。

下面来看代码示例(在Ubuntu20.04 LTS, GCC9.4中,下面注释部分代码表示对应的宏未定义)。

// main.cpp
// 执行命令:g++ -std=c++17 -o main main.cpp && ./main
#include <iostream>


int main() {
    std::cout << "__cplusplus: " << __cplusplus << std::endl; // 201703
    std::cout << "__FILE__: " << __FILE__ << std::endl; // main.cpp
    std::cout << "__LINE__: " << __LINE__ << std::endl; // 8
    std::cout << "__DATE__: " << __DATE__ << std::endl; // Jun 16 2024
    std::cout << "__TIME__: " << __TIME__ << std::endl; // 00:14:09
    std::cout << "__STDC_HOSTED__: " << __STDC_HOSTED__ << std::endl; // 1
    std::cout << "__STDC__: " << __STDC__ << std::endl; // 1
    std::cout << "__STDC_ISO_10646__: " << __STDC_ISO_10646__ << std::endl; // 201706
    // std::cout << "__STDC_VERSION__: " << __STDC_VERSION__ << std::endl;
    // std::cout << "__STDCPP_THREAD__: " << __STDCPP_THREADS__ << std::endl;
    // std::cout << "__STDC_MB_MIGHT_NEQ_WC__: " << __STDC_MB_MIGHT_NEQ_WC__ << std::endl;
    // std::cout << "__STDCPP_STRICT_POINTER_SAFETY__: " << __STDCPP_STRICT_POINTER_SAFETY__ << std::endl;
    
    return 0;
}

除了C++语言标准定义的这些宏外,其实很多编译器也扩展了一些宏。

3. 编译器扩展宏

很多编译器都支持__func__、__FUNCTION__这两个宏。这两个宏也非常有用,它们都是用来打印当前执行的函数名。__PRETTY_FUNCTION__也表示当前执行的函数,但会给出包括返回值和形参列表在内的更完整的函数signature。

__func__是在函数的定义之后隐式地定义的一个标识符,因此不能将它作为函数参数的默认值。在C++11及之后,也可以将其用在类或结构体中,以表示类名或结构体名。

#include <iostream>
#include <string>


class MyCls {
  public:
    MyCls() { cls_name = __func__; }
    MyCls(const std::string& name): cls_name(name) {}
    void print_name() { std::cout << cls_name << std::endl; }
  private:
    std::string cls_name;
};


int main(int argc, char* argv[]) {
    std::cout << "__func__: " << __func__ << std::endl; // main
    std::cout << "__FUNCTION__: " << __FUNCTION__ << std::endl; // main
    std::cout << "__PRETTY_FUNCTION__: " << __PRETTY_FUNCTION__ << std::endl; // int main(int, char**)


    MyCls cls1;
    MyCls cls2("Test_Class");
    cls1.print_name(); // MyCls
    cls2.print_name(); // Test_Class
    
    return 0;
}

总结一下,本文主要介绍了C++中不依赖任何头文件的语言层面定义的宏、部分编译器扩展宏以及assert和NDEBUG两个用于代码调试的宏,通过了解并使用它们可以为我们提供很多有用的定位或调试信息,尤其是在大型项目中打印WARNING和ERROR级别的日志时往往会给出日志所在文件、行号等信息,这些都是借助我们上面介绍的宏来实现的。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表