C++ 数字和运算符

本文介绍在 Windows 调试工具中使用 C++ 表达式语法。

调试器接受两种不同类型的数值表达式:C++ 表达式和 Microsoft 宏汇编程序 (MASM) 表达式。 这些表达式中的每一个都遵循自己的输入和输出语法规则。

有关何时使用每种语法类型的详细信息,请参阅 Evaluating expressions? evaluate expression 命令。

C++ 表达式分析器支持所有形式的 C++ 表达式语法。 语法包括所有数据类型,包括指针、浮点数和数组,以及所有 C++ 一元运算符和二进制运算符。

调试器中的 WatchLocals 窗口始终使用 C++ 表达式计算器。

在以下示例中,?? evaluate C++ 表达式 命令显示指令指针寄存器的值。

0:000> ?? @eip
unsigned int 0x771e1a02

可以使用 C++ sizeof 函数来确定结构的大小。

0:000> ?? (sizeof(_TEB))
unsigned int 0x1000

将表达式计算器设置为 C++

使用 .expr 选择表达式计算器可以查看默认的表达式计算器,并将其更改为 C++。

0:000> .expr
Current expression evaluator: MASM - Microsoft Assembler expressions
0:000> .expr /s c++
Current expression evaluator: C++ - C++ source expressions

更改默认表达式计算器后,可以使用 ? evaluate expression 命令显示 C++ 表达式。 以下示例显示指令指针寄存器的值。

0:000> ? @eip
Evaluate expression: 1998461442 = 771e1a02

若要了解有关 @eip 寄存器引用的详细信息,请参阅寄存器语法

在本例中,十六进制值 0xD 被添加到 eip 寄存器中。

0:000> ? @eip + 0xD
Evaluate expression: 1998461455 = 771e1a0f

C++ 表达式中的寄存器和伪寄存器

可以在 C++ 表达式中使用寄存器和伪寄存器。 @ 符号必须添加在寄存器或伪寄存器之前。

表达式计算器会自动执行正确的强制转换。 实际寄存器和整数值伪寄存器被强制转换为 ULONG64。 所有地址都强制转换为 PUCHAR$thread 被强制转换为 ETHREAD*$proc 被强制转换为 EPROCESS*$teb 被强制转换为 TEB*$peb 被强制转换为 PEB*

此示例显示 TEB。

0:000>  ?? @$teb
struct _TEB * 0x004ec000
   +0x000 NtTib            : _NT_TIB
   +0x01c EnvironmentPointer : (null) 
   +0x020 ClientId         : _CLIENT_ID
   +0x028 ActiveRpcHandle  : (null) 
   +0x02c ThreadLocalStoragePointer : 0x004ec02c Void
   +0x030 ProcessEnvironmentBlock : 0x004e9000 _PEB
   +0x034 LastErrorValue   : 0xbb
   +0x038 CountOfOwnedCriticalSections : 0

不能通过赋值或副作用运算符更改寄存器或伪寄存器。 必须使用 r registers 命令更改这些值。

以下示例将伪寄存器设置为值 5,然后显示。

0:000> r $t0 = 5

0:000> ?? @$t0
unsigned int64 5

有关寄存器和伪寄存器的详细信息,请参阅寄存器语法伪寄存器语法

C++ 表达式中的数字

C++ 表达式中的数字被解释为十进制数字,除非以其他方式指定。 若要指定十六进制整数,请在数字之前添加 0x。 若要指定八进制整数,请在数字之前添加 0(零)。

默认调试器基数不会影响输入 C++ 表达式的方式。 不能直接输入二进制数,除非在 C++ 表达式中嵌套 MASM 表达式。

支持输入十六进制的 64 位值,格式为 xxxxxxxx`xxxxxxxx。 也可以省略重音 (`)。 这两种格式都生成相同的值。

可以将 LUI64 后缀与整数值一起使用。 创建的数字的实际大小取决于后缀和输入的数字。 有关此解释的详细信息,请参阅 C++ 语言参考。

C++ 表达式计算器的 output 保留 C++ 表达式规则指定的数据类型。 但是,如果将此表达式用作命令的参数,则始终会进行强制转换。 例如,当整数值用作命令参数中的地址时,不必将它们强制转换为指针。 如果表达式的值无法有效强制转换为整数或指针,则会发生语法错误。

对于某些 output,可以使用 0n(十进制)前缀;但是对于 C++ 表达式输入,则不能使用。

C++ 表达式中的字符和字符串

可以通过用单引号 (') 将字符括起来来输入字符。 允许使用标准 C++ 转义字符。

以通过用双引号 (") 将字符串文字括起来来输入字符串文字。 以在这样的字符串中使用 \" 作为转义序列。 但是,字符串对表达式计算器没有意义。

C++ 表达式中的符号

在 C++ 表达式中,每个符号都根据其类型进行解释。 根据符号所指的内容,它可能被解释为整数、数据结构、函数指针或任何其他数据类型。 如果在 C++ 表达式中使用与 C++ 数据类型不对应的符号(如未修改的模块名称),则会发生语法错误。

只有在符号名称之前添加模块名称和感叹号,才能在符号名称中使用重音符 (`) 或撇号 (')。 在模板名称后添加 < 和 > 分隔符时,可以在这些分隔符之间添加空格。

如果符号可能有歧义,可以在符号前添加模块名和感叹号 (!) 或仅添加感叹号。 若要指定符号是本地符号,请省略模块名称,并在符号名称之前包括美元符号和感叹号 ($!)。 有关符号识别的详细信息,请参阅符号语法和符号匹配

C++ 表达式中的结构

C++ 表达式计算器将伪寄存器强制转换为其适当的类型。 例如,$teb 被强制转换为 TEB*

0:000> ??  @$teb
struct _TEB * 0x004ec000
   +0x000 NtTib            : _NT_TIB
   +0x01c EnvironmentPointer : (null) 
   +0x020 ClientId         : _CLIENT_ID
   +0x028 ActiveRpcHandle  : (null) 
   +0x02c ThreadLocalStoragePointer : 0x004ec02c Void
   +0x030 ProcessEnvironmentBlock : 0x004e9000 _PEB
   +0x034 LastErrorValue   : 0xbb
   +0x038 CountOfOwnedCriticalSections : 0

以下示例显示 TEB 结构中的进程 ID,显示指向所引用结构成员的指针的使用。

0:000> ??  @$teb->ClientId.UniqueProcess
void * 0x0000059c

C++ 表达式中的运算符

可以使用圆括号代替优先级规则。

如果将 C++ 表达式的一部分括在括号中,并在表达式之前添加两个 at 符号 (@@),则会根据 MASM 表达式规则来解释该表达式。 不能在两个 at 符号和左括号之间添加空格。 此表达式的最终值作为 ULONG64 值传递给 C++ 表达式计算器。 还可以使用 @@c++( ... )@@masm( ... ) 指定表达式计算器。

在 C++ 语言中,数据类型的表示与往常一样。 表示数组 ([ ])、指针成员 (->)、UDT 成员 (.) 和类成员 (::) 的符号都可以识别。 支持所有算术运算符,包括赋值运算符和副作用运算符。 但是,不能使用 newdeletethrow 运算符,并且实际上无法调用函数。

支持指针运算,偏移量缩放正确。 请注意,不能向函数指针添加偏移量。 如果必须向函数指针添加偏移量,请先将偏移量强制转换为字符指针。

与 C++ 中一样,如果使用具有无效数据类型的运算符,则会发生语法错误。 调试器的 C++ 表达式分析器使用的规则比大多数 C++ 编译器稍微宽松一些,但所有主要规则都是强制执行的。 例如,不能移动非整数值。

可以使用以下运算符。 每个单元格中的运算符优先于较低单元格中的操作符。 同一单元格中的运算符具有相同的优先级,并且从左到右进行分析。

与 C++ 一样,当表达式的值已知时,表达式求值结束。 此结尾使你能够有效地使用表达式,例如 ?? myPtr && *myPtr

引用和类型强制转换

运算符 含义
表达式 // 注释 忽略所有后续文本
Class :: Member 类的成员
Class ::~Member 类的成员(析构函数)
:: Name 全局
结构字段 结构中的字段
Pointer ->Field 引用结构中的字段
Name [integer] 数组下标
LValue ++ 增量(计算后)
LValue -- 减量(计算后)
dynamic_cast<type>(Value) 类型强制转换(始终执行)
static_cast<type>(Value) 类型强制转换(始终执行)
reinterpret_cast<type>(Value) 类型强制转换(始终执行)
const_cast<type>(Value) 类型强制转换(始终执行)

值操作

运算符 含义
(type) Value 类型强制转换(始终执行)
sizeof 表达式的大小
sizeof( type ) 数据类型的大小
++ LValue 增量(计算前)
-- LValue 减量(计算前)
~ 位补充
! 非(布尔值)
一元减
+ 一元加
& LValue 数据类型的地址
Dereference
结构指针 指向结构成员的指针
Pointer -> * Pointer 指向所引用结构的成员的指针

算术

运算符 含义
Value Value 乘法
Value / Value 分部
Value % Value 取模
Value + Value 附加内容
Value - Value
Value<<Value 按位左移
Value>>Value 按位右移
Value<Value 小于(比较值)
Value<= Value 小于或等于(比较值)
Value>Value 大于(比较值)
Value>= Value 大于或等于(比较值)
Value == Value 等于(比较值)
Value != Value 不等于(比较值)
Value & Value 位与
Value ^ Value 按位 XOR(异或)
Value | Value 按位“或”
&& 逻辑与
Value || Value 逻辑或

以下示例假设伪寄存器的设置如图所示。

0:000> r $t0 = 0
0:000> r $t1 = 1
0:000> r $t2 = 2
0:000> ?? @$t1 + @$t2
unsigned int64 3
0:000> ?? @$t2/@$t1
unsigned int64 2
0:000> ?? @$t2|@$t1
unsigned int64 3

分配

运算符 含义
LValue = Value 分配
LValue *= Value 相乘并赋值
LValue /= Value 除并赋值
LValue %= Value 取模并赋值
LValue += Value 添加并赋值
LValue -= Value 相减并赋值
LValue<<= Value 向左移动并赋值
LValue>>= Value 向右移动并赋值
LValue &= AND 和赋值
LValue |= Value OR 和赋值
LValue ^= Value XOR 和赋值

计算

运算符 含义
Value ? Value : Value 条件计算
Value , Value 计算所有值,然后丢弃除最右边的值以外的所有值

C++ 表达式中的宏

可以在 C++ 表达式中使用宏。 必须在宏之前添加数字符号 (#)。

可使用以下宏。 这些宏与同名的 Microsoft Windows 宏具有相同的定义。 Windows 宏在 Winnt.h 中定义。

返回值
#CONTAINING_RECORD(Address, Type, Field) 给定结构的类型和结构中字段的地址,返回结构实例的基址。
#FIELD_OFFSET(Type, Field) 返回已知结构类型中命名字段的字节偏移量。
#RTL_CONTAINS_FIELD(Struct, Size, Field) 指示给定字节大小是否包括所需字段。
#RTL_FIELD_SIZE(Type, Field) 返回已知类型结构中字段的大小,而不需要字段的类型。
#RTL_NUMBER_OF(Array) 返回静态大小数组中的元素数。
#RTL_SIZEOF_THROUGH_FIELD(Type, Field) 返回已知类型的结构的大小,包括指定字段。

此示例显示使用 #FIELD_OFFSET 宏计算结构中字段的字节偏移量。

0:000> ?? #FIELD_OFFSET(_PEB, BeingDebugged)
long 0n2

另请参阅

MASM 表达式与 C++ 表达式

?? 计算 C++ 表达式

? 计算表达式

.expr 选择表达式计算器

符号扩展

混合表达式示例