本机 C++ 中的表达式

调试器接受大多数 Microsoft 和 ANSI C/C++ 表达式。 调试器还提供内部函数和上下文运算符,以便更安全、更方便地计算表达式。 本主题还描述了有关 C++ 表达式的限制,需要注意以下几点:

不能将上下文运算符或大多数格式说明符用在代码中,或用于脚本或托管代码表达式。 它们特定于本机 C++ 表达式计算器。

本节内容

使用调试器内部函数来维护状态

使用上下文运算符来指定符号

本机 C++ 表达式的限制

  • 访问控制

  • 不明确的引用

  • 匿名命名空间

  • 构造函数、析构函数和转换

  • 继承

  • 内联和编译器内部函数

  • 数值常量

  • 运算符函数

  • 重载

  • 优先级

  • 符号格式

  • 类型强制转换

使用调试器内部函数来维护状态

利用调试器内部函数,您可以调用表达式中的某些 C/C++ 函数而不更改应用程序的状态。

调试器内部函数:

  • 一定是安全的:执行调试器内部函数将不会中断将调试的进程。

  • 在任何表达式中都是被允许的,甚至在不允许副作用和函数计算的方案中也是如此。

  • 在无法进行正则函数调用的方案(例如,调试小型转储)中是可行的。

此外,可利用调试器内部函数更方便地计算表达式。 例如,strncmp(str, “asd”) 比 str[0] == ‘a’ && str[1] == ‘s’ && str[2] == ‘d’ 更易于写入断点条件中。)

区域

内部函数

字符串长度

strlen、wcslen、strnlen、wcsnlen

字符串比较

strcmp、wcscmp、stricmp、_stricmp、_strcmpi、wcsicmp、_wcscmpi、_wcsnicmp、strncmp、wcsncmp、strnicmp、wcsnicmp

字符串搜索

strchr、wcschr、strstr、wcsstr

Win32

GetLastError()、TlsGetValue()

Windows 8

WindowsGetStringLen()、WindowsGetStringRawBuffer()

这些函数要求将调试的进程运行于 Windows 8 上。 调试从 Windows 8 设备生成的转储文件还要求 Visual Studio 计算机运行的是 Windows 8。 但是,如果远程调试 Windows 8 设备,则 Visual Studio 计算机可运行 Windows 7。

杂项

__log2

返回以 2 为底指定整数的对数,并舍入到最接近的较小整数。

使用上下文运算符来指定符号

上下文运算符是由本机调试器提供的附加运算符。 调试本机代码时,可使用上下文运算符来限定断点位置、变量名称或表达式。 上下文运算符对于某些目的很有用,例如,指定来自外部范围的但被本地名称隐藏的名称。

语法

{,,[module] } expression

  • module 是模块的名称。 您可以使用完整路径来消除同名模块之间的歧义。

  • expression 是解析为有效目标(如 module 中的函数名、变量名或指针地址)的任何有效的 C++ 表达式。

大括号必须包含两个逗号和模块(可执行文件或 DLL)名称或完整路径。

例如,在 EXAMPLE.dll 的 SomeFunction 函数处设置断点:

{,,EXAMPLE.dll}SomeFunction

如果 module 路径包括逗号、嵌入空格或大括号,则必须在路径两边使用引号,以便上下文分析器能够正确识别该字符串。 单引号被视为 Windows 文件名的一部分,因此必须使用双引号。 例如,

{,"a long, long, library name.dll", } g_Var

当表达式计算器遇到表达式中的符号时,它按下列顺序搜索该符号:

  1. 词法范围向外,从当前块开始(括在大括号中的一系列语句),然后从该封闭块继续向外。 当前块是包含当前位置(指令指针地址)的代码。

  2. 函数范围。 当前函数。

  3. 类范围(如果当前位置在 C++ 成员函数内)。 类范围包含所有基类。 表达式计算器使用常规域控制规则。

  4. 当前模块中的全局符号。

  5. 当前程序中的公共符号。

使用上下文运算符,可以指定搜索的起始模块并绕过当前位置。

本机 C++ 表达式的限制

在调试器窗口中输入 C/C++ 表达式时,以下常规限制将适用:

访问控制

调试器可以访问所有类成员,而不用考虑访问控制。 可以检查任何类对象成员,包括基类和嵌入成员对象。

不明确的引用

如果调试器表达式引用不明确的成员名称,则必须使用类名称来限定它。 例如,如果 CObject 是 CClass 的实例,后者从 AClass 和 BClass 中继承名为 expense 的成员函数,则 CObject.expense 是不明确的。 可以按如下方式解析多义性:

CObject.BClass::expense

若要解析多义性,表达式计算器可应用有关成员名称的常规域控制规则。

匿名命名空间

本机 C++ 表达式计算器不支持匿名命名空间。 例如,假定您具有下列代码:

#include "stdafx.h"

namespace mars 
{ 
    namespace
    {
        int test = 0; 
    } 

} 


int main() 
{ 
    // Adding a watch on test does not work. 
    mars::test++; 
    return 0; 
} 

在本示例中,用来监视符号 test 的唯一方式是使用修饰名:

(int*)?test@?A0xccd06570@mars@@3HA

构造函数、析构函数和转换

不能使用调用临时对象的构造的表达式显式或隐式地为对象调用构造函数或析构函数。 例如,以下表达式显式调用构造函数并生成错误消息:

Date( 2, 3, 1985 )

如果转换的目标是类,则不能调用转换函数。 这种转换涉及对象的构造。 例如,如果 myFraction 是 CFraction 的实例,后者定义了转换函数运算符 FixedPoint,则以下表达式会导致错误:

(FixedPoint)myFraction

但是,如果转换的目标是内置类型,则可以调用转换函数。 如果 CFraction 定义转换函数 operator float,则以下表达式在调试器中是合法的:

(float)myFraction

可以调用返回对象或声明局部对象的函数。

不能调用 new 或 delete 运算符。 以下表达式在调试器中不能运行:

new Date(2,3,1985)

继承

在使用调试器显示具有虚拟基类的类对象时,为每个继承路径显示虚拟基类的成员,即使仅存储了那些成员中的一个实例。

表达式计算器将适当处理虚函数。 例如,假定类 CEmployee 定义虚函数 computePay,后者在继承自 CEmployee 的类中进行了重新定义。 可以通过指向 CEmployee 的指针调用 computePay,并执行正确的函数:

empPtr->computePay()

可以将指向派生类对象的指针转换为指向基类对象的指针。 可以将指向基类对象的指针转换为指向派生类对象的指针(继承为虚拟时除外)。

内联和编译器内部函数

调试器表达式不能调用编译器内部函数或内联函数,除非该函数至少作为常规函数出现一次。

数值常量

调试器表达式可以使用八进制、十六进制或十进制格式的整数常量。 默认情况下,调试器需要十进制常量。 可以在**“调试”选项卡的“常规”**页上更改此设置。

可以使用前缀或后缀符号表示另一个基中的数字。 下表显示了可使用的形式。

语法

示例(十进制 100)

Base

digits

100 或 64

十进制或十六进制,具体取决于当前设置。

0 digits

0144

八进制(以 8 为基数)

0n digits

0n100

十进制(以 10 为基数)

0x digits

0x64

十六进制(以 16 为基数)

digits h

64h

十六进制(以 16 为基数)

运算符函数

调试器表达式可以为类隐式或显式地调用运算符函数。 例如,假设 myFraction 和 yourFraction 都是定义 operator+ 的类的实例。 可以使用以下表达式显示这两个对象的和:

myFraction + yourFraction

如果将运算符函数定义为友元函数,则可以对成员函数使用相同的语法来隐式调用它,或者显式调用它,如下所示:

operator+( myFraction, yourFraction )

与普通函数类似,不能调用带要求转换(涉及对象构造)的参数的运算符函数。

调试器不支持同时具有常量和非常量版本的重载运算符。 带有常量和非常量版本的重载运算符常用于标准模板库中。

重载

如果存在完全匹配或者匹配不要求涉及对象构造的转换,则调试器表达式可以调用重载函数。 例如,如果 calc 函数将 CFraction 对象用作参数,并且 CFraction 类定义一个接受整数的单参数构造函数,则以下表达式将导致错误:

calc( 23 )

即使存在将整数转换为 calc 期望的 CFraction 对象的合法转换,但这样的转换涉及到对象的创建,因此不受支持。

优先级

在调试器表达式中,C++ 范围运算符 (::) 的优先级低于其在源代码中的优先级。 在 C++ 源代码中,该运算符具有最高的优先级。 在调试器中,其优先级介于基与后缀运算符(->、++、--)和一元运算符(!、&、* 及其他)的优先级之间。

符号格式

您采用源代码中使用的同一形式输入包含符号的调试器表达式,前提是符号在使用完全调试信息(/Zi/ZI)编译的模块中。 如果输入的表达式包含公共符号(即在库中或者在用 /Zd 编译的模块中可以找到的符号),则必须使用符号的修饰名(即在对象代码中使用的形式)。 有关详细信息,请参阅 /Z7、/Zd、/Zi、/ZI(调试信息格式)

可使用 LINK /MAP 选项获取所有修饰和未修饰形式的名称的列表。 有关详细信息,请参阅 /MAP(生成映射文件)

名称修饰是用于强制类型安全链接的机制。 这意味着,只有拼写、大小写、调用约定和类型完全匹配的名称和引用才会链接在一起。

用 C 调用约定声明(使用 _cdecl 关键字显式或隐式声明)的名称以下划线 (_) 开头。 例如,函数 main 可显示为 _main。 声明为 _fastcall 的名称以 @ 符号开头。

对于 C++,修饰名对符号类型以及调用约定进行编码。 这种形式的名称会很长且难以读取。 该名称以至少一个问号 (?) 开头。 对于 C++ 函数,修饰包括函数范围、函数参数的类型和函数返回类型。

类型强制转换

如果转换为某个类型,调试器必须知道该类型。 在程序中必须有该类型的另一个对象。 不支持使用 typedef 语句创建的类型。