Kontrolní výrazy jazyka C/C++
Příkaz kontrolního výrazu určuje podmínku, kterou očekáváte v určitém okamžiku v programu. Pokud tato podmínka není pravdivá, kontrolní výraz selže, spuštění programu se přeruší a zobrazí se dialogové okno Kontrolní výraz se nezdařilo.
Visual Studio podporuje příkazy kontrolních výrazů jazyka C++, které jsou založené na následujících konstruktorech:
Kontrolní výrazy MFC pro programy MFC.
ATLASSERT pro programy, které používají ATL.
Kontrolní výrazy CRT pro programy, které používají knihovnu runtime jazyka C.
Funkce ASSERT ANSI pro ostatní programy C/C++.
Kontrolní výrazy můžete použít k zachycení chyb logiky, kontrole výsledků operace a testovacích chybových podmínek, které by měly být zpracovány.
V tomto tématu
Kontrolní výrazy v buildech ladění a vydaných verzí
Vedlejší účinky použití kontrolních výrazů
Jak fungují kontrolní výrazy
Když se ladicí program zastaví kvůli kontrolnímu výrazu knihovny mfc nebo C za běhu, pak pokud je zdroj dostupný, ladicí program přejde do bodu ve zdrojovém souboru, kde došlo k kontrolnímu výrazu. Zpráva kontrolního výrazu se zobrazí v okně Výstup i v dialogovém okně Kontrolní výraz se nezdařilo . Pokud chcete zprávu kontrolního výrazu uložit pro budoucí referenci, můžete z okna Výstup zkopírovat do textového okna. Okno Výstup může obsahovat i další chybové zprávy. Tyto zprávy pečlivě prozkoumejte, protože poskytují vodítka k příčině selhání kontrolního výrazu.
Pomocí kontrolních výrazů můžete detekovat chyby během vývoje. Jako pravidlo použijte pro každý předpoklad jeden kontrolní výraz. Pokud například předpokládáte, že argument není NULL, použijte k otestování daného předpokladu kontrolní výraz.
Kontrolní výrazy v buildech ladění a vydaných verzí
Příkazy kontrolního výrazu se kompilují pouze v případě, že _DEBUG
je definován. V opačném případě kompilátor zachází s kontrolními výrazy jako s příkazy null. Příkazy kontrolních výrazů proto v konečném programu vydané verze neukládají žádné režijní náklady ani náklady na výkon a umožňují se vyhnout direktivám using #ifdef
.
Vedlejší účinky použití kontrolních výrazů
Když do kódu přidáte kontrolní výrazy, ujistěte se, že kontrolní výrazy nemají vedlejší účinky. Představte si například následující kontrolní výraz, který upraví nM
hodnotu:
ASSERT(nM++ > 0); // Don't do this!
Vzhledem k tomu, že výraz ASSERT
není vyhodnocen ve verzi vydané verze programu, nM
budou mít různé hodnoty ve verzích Debug a Release. Chcete-li se tomuto problému vyhnout v prostředí MFC, můžete místo něj použít makro VERIFY.ASSERT
VERIFY
vyhodnotí výraz ve všech verzích, ale nekontroluje výsledek ve verzi vydané verze.
Buďte obzvláště opatrní při používání volání funkce v kontrolních příkazech, protože vyhodnocení funkce může mít neočekávané vedlejší účinky.
ASSERT ( myFnctn(0)==1 ) // unsafe if myFnctn has side effects
VERIFY ( myFnctn(0)==1 ) // safe
VERIFY
volání myFnctn
ve verzích Debug i Release, takže je přijatelné použít. Použití VERIFY
však představuje režii nepotřebného volání funkce ve verzi vydané verze.
Kontrolní výrazy CRT
Soubor CRTDBG.H
hlavičky _ASSERTE
_ASSERT
definuje a makra pro kontrolu kontrolních výrazů.
Makro | Výsledek |
---|---|
_ASSERT |
Pokud se zadaný výraz vyhodnotí jako NEPRAVDA, název souboru a číslo _ASSERT řádku . |
_ASSERTE |
Stejné jako _ASSERT , plus řetězcové vyjádření výrazu, který byl asserted. |
_ASSERTE
je výkonnější, protože hlásí výraz asserted, který ukázal, že je NEPRAVDA. To může stačit k identifikaci problému bez odkazu na zdrojový kód. Ladicí verze vaší aplikace však bude obsahovat řetězcovou konstantu pro každý výraz asserted using _ASSERTE
. Pokud používáte mnoho _ASSERTE
maker, tyto řetězcové výrazy zabírají značné množství paměti. Pokud se to ukáže jako problém, použijte _ASSERT
k uložení paměti.
Při _DEBUG
definování _ASSERTE
je makro definováno takto:
#define _ASSERTE(expr) \
do { \
if (!(expr) && (1 == _CrtDbgReport( \
_CRT_ASSERT, __FILE__, __LINE__, #expr))) \
_CrtDbgBreak(); \
} while (0)
Pokud se výraz asserted vyhodnotí jako NEPRAVDA, _CrtDbgReport je volána k hlášení selhání kontrolního výrazu (ve výchozím nastavení pomocí dialogového okna zprávy). Pokud v dialogovém okně zprávy zvolíte Možnost Opakovat , _CrtDbgReport
vrátí hodnotu 1 a _CrtDbgBreak
zavolá ladicí program prostřednictvím DebugBreak
.
Pokud potřebujete dočasně zakázat všechny kontrolní výrazy, použijte _CtrSetReportMode.
Kontrola poškození haldy
Následující příklad používá _CrtCheckMemory ke kontrole poškození haldy:
_ASSERTE(_CrtCheckMemory());
Kontrola platnosti ukazatele
Následující příklad používá _CrtIsValidPointer k ověření, zda je daná oblast paměti platná pro čtení nebo zápis.
_ASSERTE(_CrtIsValidPointer( address, size, TRUE );
Následující příklad používá _CrtIsValidHeapPointer k ověření ukazatele na paměť v místní haldě (halda vytvořená a spravovaná touto instancí knihovny runtime jazyka C – knihovna DLL může mít svou vlastní instanci knihovny, a proto vlastní haldu mimo haldu aplikace). Tento kontrolní výraz zachytává nejen adresy null nebo mimo hranice, ale také ukazatele na statické proměnné, proměnné zásobníku a jakoukoli jinou nelokalní paměť.
_ASSERTE(_CrtIsValidHeapPointer( myData );
Kontrola bloku paměti
Následující příklad používá _CrtIsMemoryBlock k ověření, že blok paměti je v místní haldě a má platný typ bloku.
_ASSERTE(_CrtIsMemoryBlock (myData, size, &requestNumber, &filename, &linenumber));
MFC – kontrolní výrazy
MFC definuje makro ASSERT pro kontrolu kontrolních výrazů. Definuje také MFC ASSERT_VALID
a CObject::AssertValid
metody pro kontrolu vnitřního stavu -odvozeného objektu CObject
.
Pokud se argument makra MFC ASSERT
vyhodnotí jako nula nebo nepravda, makro zastaví provádění programu a upozorní uživatele. V opačném případě bude provádění pokračovat.
Pokud kontrolní výraz selže, zobrazí se v dialogovém okně se zprávou název zdrojového souboru a číslo řádku kontrolního výrazu. Pokud v dialogovém okně zvolíte Možnost Opakovat, volání AfxDebugBreak způsobí přerušení provádění ladicímu programu. V tomto okamžiku můžete prozkoumat zásobník volání a pomocí dalších zařízení ladicího programu zjistit, proč kontrolní výraz selhal. Pokud jste povolili ladění za běhu a ladicí program ještě nebyl spuštěný, dialogové okno může spustit ladicí program.
Následující příklad ukazuje, jak zkontrolovat ASSERT
návratovou hodnotu funkce:
int x = SomeFunc(y);
ASSERT(x >= 0); // Assertion fails if x is negative
Funkci ASSERT s funkcí IsKindOf můžete použít k zajištění kontroly typů argumentů funkce:
ASSERT( pObject1->IsKindOf( RUNTIME_CLASS( CPerson ) ) );
Makro ASSERT
nevygeneruje žádný kód ve verzi vydané verze. Pokud potřebujete výraz vyhodnotit ve verzi vydané verze, použijte místo ASSERT makro VERIFY .
MFC ASSERT_VALID a CObject::AssertValid
CObject ::AssertValid metoda poskytuje kontroly za běhu interního stavu objektu. I když při odvození třídy nepotřebujete přepisovat AssertValid
, můžete tím svoji třídu CObject
usnadnit. AssertValid
by měly provádět kontrolní výrazy pro všechny členské proměnné objektu, aby bylo možné ověřit, zda obsahují platné hodnoty. Měla by například zkontrolovat, že členské proměnné ukazatele nemají hodnotu NULL.
Následující příklad ukazuje, jak deklarovat AssertValid
funkci:
class CPerson : public CObject
{
protected:
CString m_strName;
float m_salary;
public:
#ifdef _DEBUG
// Override
virtual void AssertValid() const;
#endif
// ...
};
Při přepsání AssertValid
volejte verzi AssertValid
základní třídy před provedením vlastních kontrol. Pak pomocí makra ASSERT zkontrolujte členy jedinečné pro vaši odvozenou třídu, jak je znázorněno zde:
#ifdef _DEBUG
void CPerson::AssertValid() const
{
// Call inherited AssertValid first.
CObject::AssertValid();
// Check CPerson members...
// Must have a name.
ASSERT( !m_strName.IsEmpty());
// Must have an income.
ASSERT( m_salary > 0 );
}
#endif
Pokud některé z vašich členských proměnných ukládají objekty, můžete makro použít ASSERT_VALID
k otestování jejich vnitřní platnosti (pokud jejich třídy přepisují AssertValid
).
Představte si například třídu CMyData
, která ukládá CObList v jedné z jejích členských proměnných. Proměnná CObList
, m_DataList
ukládá kolekci CPerson
objektů. Zkrácená deklarace CMyData
vypadá takto:
class CMyData : public CObject
{
// Constructor and other members ...
protected:
CObList* m_pDataList;
// Other declarations ...
public:
#ifdef _DEBUG
// Override:
virtual void AssertValid( ) const;
#endif
// And so on ...
};
Přepsání AssertValid
CMyData
vypadá takto:
#ifdef _DEBUG
void CMyData::AssertValid( ) const
{
// Call inherited AssertValid.
CObject::AssertValid( );
// Check validity of CMyData members.
ASSERT_VALID( m_pDataList );
// ...
}
#endif
CMyData
AssertValid
používá mechanismus k otestování platnosti objektů uložených ve svém datovém členu. Přepsání AssertValid
CMyData
vyvolá ASSERT_VALID
makro pro vlastní m_pDataList členské proměnné.
Testování platnosti se na této úrovni nezastaví, protože třída CObList
také přepisuje AssertValid
. Toto přepsání provádí další testování platnosti v interním stavu seznamu. Proto test platnosti objektu CMyData
vede k dalším testům platnosti pro vnitřní stavy uloženého CObList
objektu seznamu.
S další prací můžete také přidat testy platnosti pro CPerson
objekty uložené v seznamu. Třídu můžete odvodit CPersonList
a CObList
přepsat AssertValid
. V přepsání byste volali CObject::AssertValid
a pak iterovat seznamem a volali AssertValid
na každý CPerson
objekt uložený v seznamu. Třída CPerson
zobrazená na začátku tohoto tématu již přepsána AssertValid
.
Jedná se o výkonný mechanismus při sestavování pro ladění. Když následně sestavíte pro vydání, mechanismus se automaticky vypne.
Omezení AssertValid
Aktivovaný kontrolní výraz označuje, že objekt je rozhodně chybný a spuštění se zastaví. Nedostatek kontrolního výrazu však značí pouze, že nebyl nalezen žádný problém, ale objekt není zaručen, že je dobrý.
Použití kontrolních výrazů
Zachycení chyb logiky
Kontrolní výraz můžete nastavit pro podmínku, která musí být pravdivá podle logiky programu. Kontrolní výraz nemá žádný vliv, pokud nedojde k chybě logiky.
Předpokládejme například, že simulujete molekuly plynu v kontejneru a proměnná numMols
představuje celkový počet molekul. Toto číslo nemůže být menší než nula, takže můžete zahrnout příkaz kontrolního výrazu MFC, například takto:
ASSERT(numMols >= 0);
Nebo můžete zahrnout kontrolní výraz CRT takto:
_ASSERT(numMols >= 0);
Tyto příkazy nedělají nic, pokud váš program funguje správně. Pokud je ale chyba numMols
logiky menší než nula, kontrolní výraz zastaví provádění programu a zobrazí dialogové okno Kontrolní výraz, které selhalo.
Kontrola výsledků
Kontrolní výrazy jsou cenné pro testovací operace, jejichž výsledky nejsou zřejmé z rychlé vizuální kontroly.
Představte si například následující kód, který aktualizuje proměnnou iMols
na základě obsahu propojeného seznamu, na který mols
odkazuje:
/* This code assumes that type has overloaded the != operator
with const char *
It also assumes that H2O is somewhere in that linked list.
Otherwise we'll get an access violation... */
while (mols->type != "H2O")
{
iMols += mols->num;
mols = mols->next;
}
ASSERT(iMols<=numMols); // MFC version
_ASSERT(iMols<=numMols); // CRT version
Počet molekul počítaných iMols
podle musí být vždy menší než nebo roven celkovému počtu molekul, numMols
. Vizuální kontrola smyčky nezobrazuje, že to bude nutně případ, takže příkaz kontrolního výrazu se použije po smyčce k otestování pro danou podmínku.
Hledání neošetřených chyb
Kontrolní výrazy můžete použít k testování chybových podmínek v okamžiku v kódu, kde by se měly zpracovat všechny chyby. V následujícím příkladu vrátí grafická rutina kód chyby nebo nulu pro úspěch.
myErr = myGraphRoutine(a, b);
/* Code to handle errors and
reset myErr if successful */
ASSERT(!myErr); -- MFC version
_ASSERT(!myErr); -- CRT version
Pokud kód pro zpracování chyb funguje správně, měla by být chyba zpracována a myErr
před dosažením kontrolního výrazu by se měla obnovit na nulu. Pokud myErr
má jinou hodnotu, kontrolní výraz selže, program se zastaví a zobrazí se dialogové okno Kontrolní výraz se nezdařilo.
Příkazy kontrolních výrazů však nejsou náhradou za kód pro zpracování chyb. Následující příklad ukazuje příkaz kontrolního výrazu, který může vést k problémům v konečném kódu vydané verze:
myErr = myGraphRoutine(a, b);
/* No Code to handle errors */
ASSERT(!myErr); // Don't do this!
_ASSERT(!myErr); // Don't do this, either!
Tento kód spoléhá na příkaz kontrolního výrazu pro zpracování chybového stavu. V důsledku toho se veškerý kód chyby vrácený myGraphRoutine
v konečném kódu verze neošetřuje.