【问题标题】:Why aren't #define'd constants known to debugger?为什么调试器不知道#define'd 常量?
【发布时间】:2015-09-18 10:27:17
【问题描述】:

在调试我的代码时,我经常想知道#define'd 常量的值。但是调试器似乎不知道它们的值。这意味着我必须四处寻找包含路径等才能找到#define 行。有什么技巧可以让这更容易吗?

更新:我不得不将绿色勾号授予 Tony D,因为他对标题问题的详细回答,但我也赞成使用 const 而不是 #define(我还测试了enum 也可以)。最后,使用 F12 找到原来的#define 行是另一个好主意。

【问题讨论】:

  • 在一般情况下可能不是因为预处理定义的结果字符串不一定是可分配的值。
  • 为你的编译器寻找等效的gcc -E
  • 一些编译器会(可选地)在最终的可执行文件中包含宏定义,特别是为了帮助调试器。但是,如果您的编译器没有,那将无济于事。
  • 因为没有“#define'd 常量”之类的东西。所有以# 开头的行都被预处理器解释和删除。像#define PI 3.14159 这样的简单#defines 告诉预处理器用3.14159 替换任何出现的PI,这就是它的作用。带有参数的#defines 以类似的方式工作(它们的用法被替换为使用它们的参数的表达式)。编译器和编译链的其余部分看不到它们,因为它们在预处理后不再存在(这是编译过程的第一步)。
  • 考虑到 #define 宏可以变得多么复杂,我很惊讶没有更多的功能需要帮助调试它们。

标签: c++ debugging visual-studio-2015


【解决方案1】:

对于 Google 的任何 10 分钟,Visual Studio 似乎都不支持这一点。

一些编译器确实会尝试这样做,但它有点脆弱/尽力而为是有原因的......

首先要重新讨论并取消对此的常见但虚假的解释:有一个概念上不同的预处理步骤,它在“正确”编译开始之前用文本替换预处理器宏的使用,但真的有 没有理由 预处理阶段不能将#define#undefine 语句的记录向前传递以包含在其他调试信息中。

的痛点在于,直观且普遍认为的“#define-ing 预处理器常量”与...

const Some_Type some_identifier = some_value;

...因为后者在应用程序的命名空间层次结构中具有特定位置并且确实无法更改,而 a #define 可以是 #undef-ined 和 re-#defined any次数,这样预处理器宏在特定代码行的“值”可能取决于该行是如何包含在翻译单元中的:每个翻译单元中包含同一行该宏可能有不同的值(或没有值)

因此,在调试某些代码时显示宏的“值”是有问题的 - 它只能保证在特定翻译单元的特定行中具有单个值,但程序员通常希望调试器根据源文件中的行来显示和导航程序。

考虑:

使用_x.h

class T ## X {
    void g() { ... }
};

some_app.cc

#define X 2783
#include "use_x.h"
#undef X
#define X 2928
#include "use_x.h"
void f() {
    const int last_x = X;
}

如果您的调试器单步执行上面的f(),则X 可以说是2928,但是如果您单步执行g() 的版本怎么办 - 调试器将很难理解有一些类名和用于创建它的X 的值之间的联系,或者找出以其他方式显示的内容....

如果您将鼠标悬停在宏上而停在其他行(可能从不同的翻译单元链接),则情况会变得更糟 - 调试器可能无法知道您是否对该宏的最后一个值感兴趣您的执行已经通过的宏,或者如果执行继续,它可能具有的下一个值(如果它甚至可以预测可能首先发生的任何分支),或者调试器停止行处的宏的值(如果有)。

因此,一些工具链(编译器/调试信息编码/调试器)只是不接受这种混乱。更好的工具可能会跟踪简单的案例,然后什么都不显示 - 或可能值的列表 - 对于过于复杂而无法分析的案例。显示错误的值比没有更糟糕。


它在调试器中没有帮助,但是当需要检查宏值/替换时,您可以尝试 cl /E(对于 GCC/clang 选项的 -E)为翻译单元提供预处理输出 - 您可以然后看看预处理器做了哪些替换。

【讨论】:

  • 只想强调第 3 段的重点:尽管标准将预处理和构建定义为单独的抽象翻译阶段,但这并不一定会影响实际编译器实现的设计。编译器可以做它想做的事,包括在适当的地方将类似变量的宏提升为实变量——如果这样设计的话。大多数都不是。
  • 我一直想要的一件事是一个多列调试器(可以调整大小或启用/禁用的列),它将在一列中显示正在生成的源代码(但可能由优化器重写),在一列中显示相应的预处理原始源代码(可能重新格式化以在序列点添加换行符),在一列中显示原始源代码。
【解决方案2】:

宏在编译前由预处理器评估和解析。不仅仅是编译后的程序不知道它们的值:编译后的程序根本不包含它们的踪迹。您的调试器无需检查任何内容。

把宏当成代码生成工具,一切都会变得清晰。

某些编译器确实有一个标志,您可以设置它来保留各种预处理器定义的值,并将它们与您的可执行文件一起列在一个特殊区域中,以用于调试目的。不过,我不知道如何在 VS 中实现这一点。我会改为使用only运行预处理器的相关开关,然后检查结果。

【讨论】:

  • @black 祝你好运,尝试将这种智慧应用于任何不是从头开始的事情。
  • 感谢版主完全毫无意义地删除了我对@black 的回复。做得好。我所说的大致是,黑色是错误的;这不是我的回答的底线,我也不相信这是真的。
  • 我的评论一开始是这种行为的逻辑后果,但同样,这只是所做的评论。这是此问答之后 的一步。无论如何,@LightnessRacesinOrbit,我会删除它。
【解决方案3】:

'#Define'd 常量对于调试器来说是未知的,因为它们在编译之前被预处理并且它们在代码中的出现被替换为值。

如果您想要一个常量值,为什么不使用 const?这样您就可以在调试器中看到该值,并强制编译器检查您错误地尝试更改代码中的值的任何地方。 #define 没有这种安全性。

【讨论】:

    【解决方案4】:

    关于“Visual Studio”,#define X 3 在预处理过程中被解析,X 的所有外观都被替换为 3。因此,当您将鼠标移到它上面时,调试器不会显示它的值,就像在任何其他具有“硬编码”值的语句中一样。

    int res = y + 3;
    

    如果您将鼠标移到 3 上,它不会在浮动窗口中显示 3。

    【讨论】:

      【解决方案5】:

      他们不为人知的原因已经得到解答。我真的感受到了你的痛苦。这就是为什么对于我开始的每个项目,我实际上都会跟踪包含路径,并且在编译之前,我 grep 路径以在列出定义的文本文件中获取定义。我在调试时一直在手边。我猜这是一个技巧:)

      补充:也许真的很有帮助..我希望:) 我自己不再使用 Visual Studio,所以我不能 100% 确定 2015 年是否有可能,但是: - 在配置属性 -> C / C++ -> 预处理器中将文件预处理设置为是。 - Visual Studio 用于生成一个 .i 文件,我希望 2015 仍然如此,下次编译程序时,因为这是预处理器的输出。 - 打开这个.i文件,你至少可以看到你的宏的实际扩展值是多少,并且在你调试的时候也可以在监视窗口中输入这个值。

      这只会花费您一次额外的编译,但可以帮助您第二次,节省您宝贵的时间,您可以花在文件夹潜水以外的其他事情上 :)

      【讨论】:

        【解决方案6】:

        选择你感兴趣的宏,在你想知道它的值的那一行。

        按 F12。

        这将跳转到它的定义(编译器可以解决的最佳问题:给定的代码行可能对同一个#define 有多个定义!)。

        【讨论】:

          【解决方案7】:
          #define one 1
          

          这只是文本替换。甚至编译器也不知道代码中这个“1”的背后是什么。

          在编译/链接阶段之前由预处理器评估宏。

          更多关于编译步骤: How does the compilation/linking process work?

          【讨论】:

            猜你喜欢
            • 2012-10-02
            • 2013-04-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多