Visual Studio 調試器包括表達式計算器,當您在**“快速監視”對話框、“監視”窗口或“即時”窗口中輸入表達式時,這些計算器可以對其進行計算。 這些表達式計算器還可以在“斷點”**窗口和調試器中的許多其他位置使用。
常見的表達式計算器功能
試器中常見的各種表達式計算器功能,這些功能僅因語言不同而不同。
隱式變量
在 Visual Basic 和 C# 中,通過使用表達式計算器可以創建隱式變量。 這些隱式變量永遠不會超出范圍,可以作為任何其他變量一樣處理。在 C# 中,通過在表達式計算器中聲明隱式變量可創建隱式變量。 例如,您可以在**“即時”**窗口中輸入下列 C# 代碼:
int b = 100;
在**“即時”窗口中執行此代碼時,新的隱式變量將顯示在“局部變量”**窗口中,其變量名稱前有一個 $ 符號,在本例中,為 $b。
在 Visual Basic 中,不能在表達式計算器中聲明隱式變量。 但是,如果在 Visual Basic 表達式計算器中使用未聲明的變量,將會自動創建隱式變量。 在 Visual Basic 中,隱式變量不會列在**“局部變量”**窗口中。
斷點
如果使用“即時”窗口計算包含斷點的 Visual Basic 或 C# 方法或函數,將命中該斷點並在**“調用堆棧”**上顯示一個新框架。 下面是一個 C# 示例:
class Program { static void Main(string[] args) { // Breakpoint here: int a = 20; } }
如果在注釋指示的位置設置一個斷點,然后按 F5 編譯並執行該程序,將以常規方式命中該斷點。 如果現在通過將 Program.Main(null) 鍵入**“即時”窗口來計算 Main 方法,該斷點將再次被命中,“調用堆棧”**上將有一個該方法的項。
在“監視”窗口中計算
為避免出現不該有的副作用,調試器每次步進時都不會自動計算函數或方法。 相反,您可以通過一個圖標來手動更新結果。 該圖標顯示在**“值”**列中。 這樣就可以手動計算調用。
對象標識
此功能對 Visual Basic 不可用。某些應用程序會創建一個類的許多實例。 在這些應用程序中,通過使用標識符來區分類的給定實例通常十分有用。 例如,如果類的特定實例的行為與預期不同或者已將特定實例多次插入應僅包含它一次的集合中,這么做就很有用。
本機對象標識
調試非托管代碼時,可以通過地址來唯一地標識對象。 這么做很重要,原因有兩點:
-
只需使用對象的地址即可跟蹤對象。 這包括使用地址執行以下操作的能力:
-
查看位於該地址的對象的值。
-
檢查是否相等。 通常情況下,您可以像使用對象變量本身那樣使用對象的地址。
-
-
您可以使用對象(實例)的地址在該特定實例中的方法上設置斷點。
例如,假定您有一個對象,它是 CMyType 類的實例,地址為 0xcccccccc。 您可以在該實例的 aMethod 方法上指定一個函數斷點,如下所示:
((CMyType *) 0xcccccccc)->aMethod
托管對象標識
對於托管代碼,不能使用對象的地址來標識該對象。 相反,需要使用由公共語言運行時 (CLR) 調試服務生成的並與該對象關聯的整數(稱為對象 ID)。 該數字是一個由 CLR 調試服務生成的正整數。 對象 ID 值除唯一地標識對象外,別無他用。
對象句柄顯示為可變長度的十進制整數,數字后跟一個井號 (#),且不帶任何前導零,例如 5#。 句柄顯示在不同調試器數據窗口中的**“值”**列中。
若要為變量創建對象 ID,請右擊該變量,然后選擇**“創建對象 ID”。 調試器將顯示一個后跟井號 (#) 的數字,如 123#。 若要刪除對象 ID,請右擊該變量,然后選擇“刪除對象 ID”**。
命中斷點時,可以在**“監視”**窗口中鍵入變量的句柄。 調試器顯示對象 ID 的值,您可以展開並檢查它,就像對任何其他變量一樣。
您可以使用對象 ID 在特定實例的方法上設置斷點。 例如,假定您有一個對象,它是 CMyType 類的一個實例,該實例的對象 ID 為 5#。 類 CMyType 包含一個方法 aMethod。 您可以在實例 5# 的 aMethod 方法上設置一個函數斷點,如下所示:
((CMyType) 5#).aMethod
還可以在斷點條件中使用對象 ID。 下面的示例演示如何在條件中測試對象 ID。
this == 5#
本機 C++ 中的表達式
格式說明符
在**“監視”窗口或“快速監視”**對話框中調試本機代碼時,您會使用格式說明符更改值的顯示格式。
(大多數格式說明符僅適用於本機代碼,但是 Visual C# 包含有限的一組格式說明符)。 (有關信息,請參閱 C# 中的格式說明符。)
您還可以在即時窗口、命令窗口甚至是源窗口中使用格式說明符。 如果將光標懸停在這些窗口中的表達式上,結果將在數據提示中顯示。 數據提示將在數據提示的顯示內容中反映格式說明符。
例子:
假設 nVar 是整數變量,並且“監視”窗口顯示其包含值 0x0065。 若要看到表示為字符而不是整數的值,請在“名稱”列,在變量名之后添加字符格式說明符 c:
nVar,c
“值”列現在不顯示整數值 0x0065,而顯示字符值 101 'e'。
如果要將格式說明符應用於數組元素或對象成員,必須將其直接應用於每個元素或成員。 不能將其整體應用於數組或對象。 例如,假設有數組 nArray,並且想看字符格式的前四個元素。 應在**“監視”**窗口輸入下列表達式:
nArray[0],c
nArray[1],c
nArray[2],c
nArray[3],c
下表說明調試器可識別的格式說明符。
| 說明符 |
Format |
表達式 |
顯示的值 |
|---|---|---|---|
| d,i |
signed 十進制整數 |
0xF000F065, d |
-268373915 |
| u |
unsigned 十進制整數 |
0x0065, u |
101 |
| o |
unsigned 八進制整數 |
0xF065, o |
0170145 |
| x,X |
十六進制整數 |
61541, x |
0x0000f065 |
| l,h |
用於 d、i、u、o、x、X 的 long 或 short 前綴 |
00406042,hx |
0x0c22 |
| f |
signed 浮點型 |
(3./2.), f |
1.500000 |
| e |
signed 科學計數法 |
(3./2.), e |
1.500000e+000 |
| g |
signed 浮點型或 signed 科學計數法,顯示其中較短的數 |
(3./2.), g |
1.5 |
| c |
單個字符 |
0x0065, c |
101 'e' |
| s |
String |
0x0012fde8, s |
"Hello world" |
| su |
Unicode 字符串 |
0x0012fde8, su |
"Hello world" |
| s8 |
UTF-8 字符串 |
0x0012fde8, s8 |
"Hello world" |
| hr |
HRESULT 或 Win32 錯誤代碼。 (調試器自動將 HRESULT 解碼,因此這些情況下不需要該說明符。) |
0x00000000L, hr |
S_OK |
| wc |
窗口類標志。 |
0x00000040, wc |
WC_DEFAULTCHAR |
| wm |
Windows 消息數字 |
0x0010, wm |
WM_CLOSE |
| ! |
原始格式,忽略任何數據類型視圖自定義項 |
i ! |
4 |
下表包含用於內存位置的格式化符號。 可以使用帶有計算為位置的任何值或表達式的內存位置說明符。
| 符號 |
Format |
表達式 |
顯示的值 |
|---|---|---|---|
| ma |
64 個 ASCII 字符 |
ptr, ma |
0x0012ffac .4...0...".0W&.......1W&.0.:W..1...."..1.JO&.1.2.."..1...0y....1 |
| m |
以十六進制表示的 16 個字節,后跟 16 個 ASCII 字符 |
ptr, m |
0x0012ffac B3 34 CB 00 84 30 94 80 FF 22 8A 30 57 26 00 00 .4...0...".0W&.. |
| mb |
以十六進制表示的 16 個字節,后跟 16 個 ASCII 字符 |
ptr, mb |
0x0012ffac B3 34 CB 00 84 30 94 80 FF 22 8A 30 57 26 00 00 .4...0...".0W&.. |
| mw |
8 個字 |
ptr, mw |
0x0012ffac 34B3 00CB 3084 8094 22FF 308A 2657 0000 |
| md |
4 個雙倍長字 |
ptr, md |
0x0012ffac 00CB34B3 80943084 308A22FF 00002657 |
| mq |
2 個四倍長字 |
ptr, mq |
0x0012ffac 7ffdf00000000000 5f441a790012fdd4 |
| mu |
2 字節字符 (Unicode) |
ptr, mu |
0x0012fc60 8478 77f4 ffff ffff 0000 0000 0000 0000 |
數組形式的指針大小說明符
如果對於要以數組形式查看的對象,有一個指向它的指針,則可以使用一個整數來指定數組元素的數量:
ptr,10
上下文運算符
上下文運算符是由本機調試器提供的附加運算符。 調試本機代碼時,可使用上下文運算符限定斷點位置、變量名稱或表達式:
-
{[函數],[源],[模塊] } location
-
{[函數],[源],[模塊] } variable_name
-
{[函數],[源],[模塊] } 表達式
大括號可以包含函數名、源文件路徑和模塊(可執行文件或 DLL)路徑的任意組合。 上下文運算符對於某些目的很有用,如指定來自外部范圍的、但被本地名稱隱藏的名稱。
例子:
在 EXAMPLE.CPP 的 301 行設置斷點:
{,EXAMPLE.CPP,}@301
如果省略 函數 或 模塊,則不能省略兩個逗號。 因此,下面的語法無效:
{File.c, File.exe} @143 // Invalid syntax
但是,如果將 源 和 模塊 都省略掉,則可以省略逗號。 下面的語法有效:
{Fun} @143
如果 源 或 模塊 路徑包括逗號、嵌入空格或大括號,則必須在路徑兩邊使用引號,以便上下文分析器能夠正確識別該字符串。 單引號被視為 Windows 文件名的一部分,因此必須使用雙引號。 例如,
{,"a long, long, name.c", } .143
當表達式計算器遇到表達式中的符號時,它按下列順序搜索該符號:
-
詞法范圍向外,從當前塊開始(括在大括號中的一系列語句),然后從該封閉塊繼續向外。 當前塊是包含當前位置(指令指針地址)的代碼。
-
函數范圍。 當前函數。
-
類范圍,如果當前位置在 C++ 成員函數內。 類范圍包含所有的基類。 表達式計算器使用正常域控制規則。
-
當前模塊。
-
全局符號。
-
其他模塊。
-
程序中的公共符號。
使用上下文運算符,可以指定搜索的起始點並跳過當前位置。 不能指定類,但是可以指定類的成員函數,並讓表達式計算器向外搜索。
不支持的運算符和附加運算符
調試器接受大多數 Microsoft 和 ANSI C/C++ 表達式;
在本機 C++ 中,調試器表達式不支持下列運算符:
-
逗號運算符:
Expr1 , Expr2
-
條件運算符:
Expr1 ? Expr2 : Expr3
在本機 C++ 中,調試器表達式不支持下列附加運算符:
-
指定符號的上下文的上下文運算符 ({ })。
-
用於訪問內存的內存運算符(BY、WO 和 DW)。 在所有運算符中,內存運算符的優先級最低。 內存運算符主要在調試匯編語言代碼時有用。
本機 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)
Inheritance
當使用調試器顯示具有虛擬基類的類對象時,為每個繼承路徑顯示虛擬基類的成員,即使只存儲了那些成員中的一個實例也是如此。
虛函數調用將被表達式計算器正確地處理。 例如,假定類 CEmployee 定義虛擬函數 computePay,該函數在從 CEmployee 繼承的類中重新定義。 可以通過指向 CEmployee 的指針調用 computePay,並執行正確的函數:
empPtr->computePay()
可以將指向派生類對象的指針轉換為指向基類對象的指針。 不允許反向轉換。
內部和內聯函數
調試器表達式不能調用內部或內聯函數,除非該函數至少作為正常函數出現一次。
數值常數
調試器表達式可以使用八進制、十六進制或十進制格式的整數常數。 默認情況下,調試器需要十進制常數。 此設置可以在**“調試”選項卡的“常規”**頁上更改。
可以使用前綴或后綴符號表示另一基中的數字。 下表顯示了可以使用的格式。
| 語法 |
示例(十進制 100) |
基數 |
|---|---|---|
| digits |
100 或 64 |
十進制或十六進制,具體取決於當前的設置。 |
| 0digits |
0144 |
八進制(以 8 為基數) |
| 0ndigits |
0n100 |
十進制(以 10 為基數) |
| 0xdigits |
0x64 |
十六進制(以 16 為基數) |
| digitsh |
64h |
十六進制(以 16 為基數) |
運算符函數
調試器表達式可以隱式或顯式地調用類的運算符函數。 例如,假設 myFraction 和 yourFraction 都是定義 operator+ 的類的實例。 可以使用下列表達式顯示這兩個對象的和:
myFraction + yourFraction
如果將運算符函數定義為友元函數,則可以使用與調用成員函數相同的語法隱式調用它,或者顯式調用它,如:
operator+( myFraction, yourFraction )
與一般函數一樣,不能調用帶有要求轉換(涉及到對象構造)的參數的運算符函數。
調試器不支持同時具有常量和非常量版本的重載運算符。 帶有 const 和非 const 版本的重載運算符常用在標准模板庫中。
重載
如果存在精確匹配或者匹配不要求涉及對象構造的轉換,則調試器表達式可以調用重載函數。 例如,如果函數 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 語句創建的類型。
匯編語言表達式
調試器可以正確計算匯編語言表達式,但有某些限制。 用於某些匯編語言表達式的語法不同於用於匯編語言開發系統(如 Microsoft Macro Assembler (MASM))中的語法。
內存運算符
內存運算符是返回直接內存操作結果的一元運算符。 這些運算符主要用於調試匯編語言代碼。
{BY | WO | DW} address
BY 運算符返回包含地址處的第一個字節的短整型。 該運算符模擬 BYTE PTR。
WO 運算符返回包含地址處的字的值(兩個字節)的短整型。 該運算符模擬 Microsoft Macro Assembler 的 WORD PTR 操作。 DW 運算符返回包含地址處的前四個字節的值的長整型。 此運算符模擬 DWORD PTR。
用在一些示例中的 x 格式說明符導致結果以十六進制顯示。
示例
-
顯示位於變量 sum 地址處的第一個字節:
BY sum
-
顯示位於變量 new_set 地址處的第一個字:
WO new_set
-
顯示位於 sum 地址處的雙倍長字:
DW sum
-
顯示位移為 6 的 EBP 寄存器指向的字節:
BY ebp+6,x
-
顯示堆棧指針指向的字(壓入堆棧的最后一個字):
WO esp,x
-
顯示 ESI 寄存器指向的雙倍長字:
DW esi,x
寄存器間接尋址
調試器不識別指示寄存器所指向的內存位置的方括號 ([ ])。 相反,請使用 BY、WO 和 DW 運算符引用相應的字節、字或雙字的值。
| MASM 表達式 |
調試器表達式 |
C++ 表達式 |
|---|---|---|
| BYTE PTR [bx] |
BY ebx |
*(unsigned char) ebx |
| WORD PTR [bp] |
WO ebp |
*(unsigned short *) ebp |
| DWORD PTR [bp] |
DW ebp |
*(unsigned long *) ebp |
帶置換的寄存器間接尋址
若要執行帶位移的基本、索引的或基本索引的間接尋址模式操作,請使用帶加法運算符的 BY、WO 和 DW 運算符。
| MASM 表達式 |
調試器表達式 |
|---|---|
| BYTE PTR [edi+6] |
BY edi+6 |
| BYTE PTR Test[ebx] |
BY &Test+ebx |
| WORD PTR [esi][ebp+6] |
WO esi+ebp+6 |
| DWORD PTR [ebx][esi] |
DW ebx+esi |
變量地址
使用 C address-of 運算符 (&),而不是 MASM OFFSET 運算符。
| MASM 表達式 |
調試器表達式 |
|---|---|
| OFFSET Var |
&Var |
PTR 運算符
將 address-of 運算符 (&) 與類型強制轉換或 BY、WO 和 DW 運算符結合使用,以替換匯編語言 PTR 運算符。
| MASM 表達式 |
調試器表達式 |
|---|---|
| BYTE PTR Var |
BY &Var |
| *(unsigned char*) |
&Var |
| WORD PTR Var |
WO &Var |
| DWORD PTR Var |
DW &Var |
| *(unsigned long*) |
&Var |
匯編語言字符串
在變量名之后添加字符串格式說明符 ,s。
| MASM 表達式 |
調試器表達式 |
|---|---|
| String |
String,s |
因為 C 字符串以空字符 (ASCII 0) 結尾,因此當請求字符串顯示時,調試器顯示從變量的第一個子節直到內存中下一個空字節之間的所有字符。 如果打算調試匯編語言程序,並且要在**“監視”窗口中查看字符串,應當用一個空字符分隔字符串變量。 一種查看以空終止或未終止的字符串的簡便方法是使用“內存”**窗口。
數組和結構元素
用 address-of 運算符 (&) 做數組名稱的前綴並添加所需的偏移量。 偏移量可以是表達式、數字、寄存器名稱或變量。
下面的示例說明如何為字節、字和雙倍長字的數組執行該操作。
| MASM 表達式 |
調試器表達式 |
|---|---|
| String[12] |
BY &String+12*(&String+12) |
| aWords[bx+di] |
WO &aWords+bx+di*(unsigned*)(&aWords+bx+di) |
| aDWords[bx+4] |
DW &aDWords+bx+4*(unsigned long*)(&aDWords+bx+4) |
