对 __LINE__ 指令的一致性有任何保证吗?

Posted

技术标签:

【中文标题】对 __LINE__ 指令的一致性有任何保证吗?【英文标题】:Are there any guarantees about consistency of __LINE__ directives? 【发布时间】:2019-10-21 05:40:23 【问题描述】:

GCC 9 最近在某些情况下改变了__LINE__ 指令的行为。下面的程序说明了这种变化:

#include <stdio.h>
#define expand() __LINE__
int main() 
  printf("%d\n",expand(
                ));
  return 0;

由于宏 expand()(扩展为 __LINE__)跨越多行,GCC 最高 8.3(和 Clang 最高 8.0)考虑扩展的最后一行的编号,打印 5。但 GCC 9 考虑 first 行,并打印 4。

(天箭链接:https://godbolt.org/z/3Nk2al)

C11 标准对__LINE__ 的确切行为不是很精确,除了:

6.10.8 预定义的宏名称

以下子条款中列出的预定义宏的值(__FILE____LINE__ 除外)在整个翻译单元中保持不变。

(...)

6.8.10.1 强制宏

以下宏名称应由实现定义:

(...)

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

我假设这意味着确切的值是实现定义的,因此不能期望它的值在不同的编译器版本或不同的编译器中保持不变。或者在标准的其他地方是否有关于这种效果的一些论据?

例如,是否有人认为当前源代码行的假定行号应该是稳定的,只要源代码本身没有改变?

【问题讨论】:

有趣。我过去使用__LINE__ 的方式对这种变化不敏感(我有时需要人类可读性(并且人类很灵活)并保证报告的行号具有正确的序列关系 i> (应在此更改下保留))。它对你有什么影响吗? 查看预处理器输出(使用-E 选项)以查看宏替换的结果。使用我正在使用的编译器,printf 以单行结束,包括尾随 ); @dmckee 如果它有实际用例,那将不是language-lawyer 问题;-) @dmckee 我们有一些存储预处理的 C 代码的测试预言机,Hiredis 等软件包含几个跨越多行的asserts。由于这一变化,使用旧 GCC 版本或使用 Clang 的开发人员将获得不同的预言机,这就是我注意到的差异。 【参考方案1】:

虽然从特定指令中查找行号的一般情况很难,即 GDB 试图从一些崩溃的代码中找到行号,但 printf __LINE__ 指令相对简单,因为编译器生成数字作为特定位置的静态变量。

C11 标准本身只是说当你在宏中时行号不应该改变,即__LINE__ 应该反映宏扩展后你在程序中的位置,而不是宏的代码行位于。这允许您通过创建一个打印行号然后调用您的函数的宏来执行诸如提供函数被调用者的行号之类的事情。例如:

#define f(x) ( printf("called f(x) at line=%d\n", __LINE__); f__real(x); )

至于所显示的确切行号,这取决于编译器并且不属于任何标准。 IE。您的示例可以重写为:

int main() 
  printf("%d\n", 
         __LINE__);

在这种情况下,有效的响应可能是 2 或 3。

如果您尝试动态确定行号,则可以通过系统回溯库提供。即 linux 上的 backtrace() 。

【讨论】:

以上是关于对 __LINE__ 指令的一致性有任何保证吗?的主要内容,如果未能解决你的问题,请参考以下文章

MySQL_高级部分

架构设计笔记_20_结束即开始

PowerPC 上有屏蔽混合指令吗?

Django:交易向导和自动识别码

x86 asm 指令集:任何 _searchable_ 离线参考?

我可以信任调用 PHP __destruct() 方法吗?