Potwierdzenia C/C++
Instrukcja asercji określa warunek, który ma być spełniony w punkcie programu. Jeśli ten warunek nie jest spełniony, potwierdzenie zakończy się niepowodzeniem, wykonanie programu zostanie przerwane, a zostanie wyświetlone okno dialogowe Asercji nie powiodło się.
Program Visual Studio obsługuje instrukcje asercji języka C++, które są oparte na następujących konstrukcjach:
Asercji MFC dla programów MFC.
ATLASSERT dla programów korzystających z ATL.
Asercji CRT dla programów korzystających z biblioteki języka C w czasie wykonywania.
Funkcja asercyjności ANSI dla innych programów C/C++.
Za pomocą asercji można przechwytywać błędy logiki, sprawdzać wyniki operacji i testować warunki błędu, które powinny zostać obsłużone.
W tym temacie
Asercji w kompilacjach debugowania i wydania
Skutki uboczne używania asercji
Jak działają aseracje
Gdy debuger zatrzyma się z powodu asercji biblioteki MFC lub C w czasie wykonywania, jeśli źródło jest dostępne, debuger przechodzi do punktu w pliku źródłowym, w którym wystąpiła aseracja. Komunikat asercji jest wyświetlany zarówno w oknie Dane wyjściowe, jak i w oknie dialogowym Asercji nie powiodło się . Możesz skopiować komunikat asercji z okna Dane wyjściowe do okna tekstowego, jeśli chcesz zapisać go na potrzeby przyszłego odwołania. Okno Dane wyjściowe może również zawierać inne komunikaty o błędach. Dokładnie zbadaj te komunikaty, ponieważ dostarczają wskazówek dotyczących przyczyny niepowodzenia asercji.
Używanie asercji do wykrywania błędów podczas opracowywania. W zasadzie należy użyć jednej asercji dla każdego założenia. Jeśli na przykład przyjęto założenie, że argument nie ma wartości NULL, użyj asercji, aby przetestować to założenie.
Asercji w kompilacjach debugowania i wydania
Instrukcje asercji kompilowane tylko wtedy, gdy _DEBUG
jest zdefiniowane. W przeciwnym razie kompilator traktuje asercji jako instrukcje null. W związku z tym oświadczenia asercji nie nakładają żadnych kosztów związanych z nadmiernym obciążeniem ani wydajnością w ostatnim programie wydań i pozwalają uniknąć używania #ifdef
dyrektyw.
Skutki uboczne używania asercji
Po dodaniu asercji do kodu upewnij się, że asercji nie mają skutków ubocznych. Rozważmy na przykład następujące potwierdzenie, które modyfikuje nM
wartość:
ASSERT(nM++ > 0); // Don't do this!
ASSERT
Ponieważ wyrażenie nie jest oceniane w wersji wydania programu, nM
będą miały różne wartości w wersjach debugowania i wydania. Aby uniknąć tego problemu w MFC, możesz użyć makra VERIFY zamiast ASSERT
. VERIFY
oblicza wyrażenie we wszystkich wersjach, ale nie sprawdza wyniku w wersji wydania.
Należy szczególnie uważać na używanie wywołań funkcji w instrukcjach asercji, ponieważ ocena funkcji może mieć nieoczekiwane skutki uboczne.
ASSERT ( myFnctn(0)==1 ) // unsafe if myFnctn has side effects
VERIFY ( myFnctn(0)==1 ) // safe
VERIFY
wywołania myFnctn
zarówno w wersjach debugowania, jak i wydania, dlatego dopuszczalne jest użycie. Jednak użycie VERIFY
wymusza narzut na niepotrzebne wywołanie funkcji w wersji wydania.
Asercji CRT
Plik nagłówka CRTDBG.H
definiuje makra _ASSERT
i _ASSERTE
do sprawdzania asercji.
Makro | Result |
---|---|
_ASSERT |
Jeśli określone wyrażenie zwróci wartość FALSE, nazwa pliku i numer wiersza ._ASSERT |
_ASSERTE |
Taki sam jak _ASSERT , oraz ciąg reprezentujący wyrażenie, które zostało asertywne. |
_ASSERTE
jest bardziej zaawansowany, ponieważ zgłasza wyrażenie asertywne, które okazało się fałszywe. Może to wystarczyć, aby zidentyfikować problem bez odwoływania się do kodu źródłowego. Jednak wersja debugowania aplikacji będzie zawierać stałą ciągu dla każdego wyrażenia asertywnego przy użyciu polecenia _ASSERTE
. Jeśli używasz wielu _ASSERTE
makr, te wyrażenia ciągu zajmują znaczną ilość pamięci. Jeśli okaże się, że jest to problem, użyj polecenia _ASSERT
, aby zapisać pamięć.
Po _DEBUG
zdefiniowaniu _ASSERTE
makro jest definiowane w następujący sposób:
#define _ASSERTE(expr) \
do { \
if (!(expr) && (1 == _CrtDbgReport( \
_CRT_ASSERT, __FILE__, __LINE__, #expr))) \
_CrtDbgBreak(); \
} while (0)
Jeśli wyrażenie asertywne daje wartość FALSE, _CrtDbgReport jest wywoływana w celu zgłoszenia niepowodzenia asercji (domyślnie przy użyciu okna dialogowego komunikatu). Jeśli wybierzesz pozycję Ponów próbę w oknie dialogowym komunikatu, _CrtDbgReport
zwraca wartość 1 i _CrtDbgBreak
wywołuje debuger za pomocą polecenia DebugBreak
.
Jeśli musisz tymczasowo wyłączyć wszystkie asercji, użyj _CtrSetReportMode.
Sprawdzanie uszkodzeń stert
W poniższym przykładzie użyto _CrtCheckMemory do sprawdzenia uszkodzenia sterta:
_ASSERTE(_CrtCheckMemory());
Sprawdzanie ważności wskaźnika
W poniższym przykładzie użyto _CrtIsValidPointer , aby sprawdzić, czy dany zakres pamięci jest prawidłowy do odczytu lub zapisu.
_ASSERTE(_CrtIsValidPointer( address, size, TRUE );
W poniższym przykładzie użyto _CrtIsValidHeapPointer , aby zweryfikować wskaźnik wskazujący pamięć w lokalnej stercie (sterta utworzona i zarządzana przez to wystąpienie biblioteki czasu wykonywania języka C — biblioteka DLL może mieć własne wystąpienie biblioteki, a tym samym własną stertę, poza stertą aplikacji). To twierdzenie przechwytuje nie tylko adresy o wartości null lub poza granicami, ale także wskaźniki do zmiennych statycznych, zmiennych stosu i innej nielokanej pamięci.
_ASSERTE(_CrtIsValidHeapPointer( myData );
Sprawdzanie bloku pamięci
W poniższym przykładzie użyto _CrtIsMemoryBlock w celu sprawdzenia, czy blok pamięci znajduje się w stercie lokalnym i ma prawidłowy typ bloku.
_ASSERTE(_CrtIsMemoryBlock (myData, size, &requestNumber, &filename, &linenumber));
Asercji MFC
MFC definiuje makro ASSERT na potrzeby sprawdzania asercji . Definiuje MFC ASSERT_VALID
również metody i CObject::AssertValid
do sprawdzania stanu wewnętrznego obiektu pochodnego CObject
.
Jeśli argument makra MFC ASSERT
zwróci wartość zero lub fałsz, makro zatrzymuje wykonywanie programu i ostrzega użytkownika. W przeciwnym razie wykonanie będzie kontynuowane.
Gdy potwierdzenie zakończy się niepowodzeniem, w oknie dialogowym komunikatu zostanie wyświetlona nazwa pliku źródłowego i numer wiersza potwierdzenia. W przypadku wybrania opcji Ponów próbę w oknie dialogowym wywołanie elementu AfxDebugBreak powoduje przerwanie wykonywania w debugerze. W tym momencie możesz zbadać stos wywołań i użyć innych obiektów debugera, aby określić, dlaczego aseracja nie powiodła się. Jeśli włączono debugowanie just in time, a debuger nie był jeszcze uruchomiony, okno dialogowe może uruchomić debuger.
W poniższym przykładzie pokazano, jak użyć ASSERT
polecenia , aby sprawdzić wartość zwracaną funkcji:
int x = SomeFunc(y);
ASSERT(x >= 0); // Assertion fails if x is negative
Funkcja ASSERT z funkcją IsKindOf umożliwia sprawdzanie typów argumentów funkcji:
ASSERT( pObject1->IsKindOf( RUNTIME_CLASS( CPerson ) ) );
Makro ASSERT
nie generuje kodu w wersji wydania. Jeśli musisz ocenić wyrażenie w wersji wydania, użyj makra VERIFY zamiast ASSERT.
MFC ASSERT_VALID i CObject::AssertValid
Metoda CObject::AssertValid umożliwia sprawdzanie stanu wewnętrznego obiektu w czasie wykonywania. Chociaż nie jest wymagane przesłonięcia AssertValid
podczas tworzenia klasy z CObject
klasy , możesz zwiększyć niezawodność klasy, wykonując tę czynności. AssertValid
należy wykonać asercji na wszystkich zmiennych członkowskich obiektu, aby sprawdzić, czy zawierają prawidłowe wartości. Na przykład należy sprawdzić, czy zmienne składowe wskaźnika nie mają wartości NULL.
W poniższym przykładzie pokazano, jak zadeklarować AssertValid
funkcję:
class CPerson : public CObject
{
protected:
CString m_strName;
float m_salary;
public:
#ifdef _DEBUG
// Override
virtual void AssertValid() const;
#endif
// ...
};
Po zastąpieniu AssertValid
wywołaj wersję klasy bazowej AssertValid
przed wykonaniem własnych testów. Następnie użyj makra ASSERT, aby sprawdzić elementy członkowskie unikatowe dla klasy pochodnej, jak pokazano poniżej:
#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
Jeśli którykolwiek ze zmiennych składowych przechowuje obiekty, możesz użyć ASSERT_VALID
makra do przetestowania ich wewnętrznej ważności (jeśli ich klasy zastępują AssertValid
).
Rozważmy na przykład klasę , która przechowuje obiekt CMyData
CObList w jednej ze zmiennych składowych. Zmienna CObList
, m_DataList
przechowuje kolekcję CPerson
obiektów. Skrócona deklaracja CMyData
nazwy wygląda następująco:
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 ...
};
Przesłonięcia AssertValid
w pliku CMyData
wyglądają następująco:
#ifdef _DEBUG
void CMyData::AssertValid( ) const
{
// Call inherited AssertValid.
CObject::AssertValid( );
// Check validity of CMyData members.
ASSERT_VALID( m_pDataList );
// ...
}
#endif
CMyData
AssertValid
używa mechanizmu do testowania ważności obiektów przechowywanych w składowych danych. Zastąpienie AssertValid
CMyData
wywołuje ASSERT_VALID
makro dla własnej zmiennej składowej m_pDataList.
Testowanie ważności nie kończy się na tym poziomie, ponieważ klasa CObList
zastępuje również AssertValid
wartość . To zastąpienie wykonuje dodatkowe testy ważności na wewnętrznym stanie listy. W związku z tym test ważności obiektu CMyData
prowadzi do dodatkowych testów ważności dla wewnętrznych stanów przechowywanych CObList
obiektów listy.
Dzięki większej pracę można również dodać testy ważności dla CPerson
obiektów przechowywanych na liście. Można utworzyć klasę CPersonList
z CObList
klasy i zastąpić AssertValid
element . W przesłonięci wywołasz metodę CObject::AssertValid
, a następnie wykonasz iterację po liście, wywołując wywołanie AssertValid
każdego CPerson
obiektu przechowywanego na liście. Klasa CPerson
pokazana na początku tego tematu już zastępuje AssertValid
element .
Jest to zaawansowany mechanizm podczas kompilowania na potrzeby debugowania. Po zakończeniu kompilacji na potrzeby wydania mechanizm jest wyłączany automatycznie.
Ograniczenia elementu AssertValid
Wyzwolona asercji wskazuje, że obiekt jest zdecydowanie zły, a wykonanie zostanie zatrzymane. Jednak brak potwierdzenia wskazuje tylko, że nie znaleziono żadnego problemu, ale obiekt nie ma gwarancji, że jest dobry.
Używanie asercji
Przechwytywanie błędów logiki
Możesz ustawić asercji na warunku, który musi być spełniony zgodnie z logiką programu. Potwierdzenie nie ma wpływu, chyba że wystąpi błąd logiki.
Załóżmy na przykład, że symulujesz cząsteczki gazu w pojemniku, a zmienna numMols
reprezentuje całkowitą liczbę cząsteczek. Ta liczba nie może być mniejsza niż zero, dlatego możesz dołączyć następującą instrukcję asercji MFC:
ASSERT(numMols >= 0);
Możesz też dołączyć asercji CRT w następujący sposób:
_ASSERT(numMols >= 0);
Te instrukcje nie robią nic, jeśli program działa poprawnie. Jeśli błąd logiki powoduje numMols
, że wartość jest mniejsza niż zero, jednak asercji zatrzymuje wykonywanie programu i wyświetla okno dialogowe Asercji nie powiodło się.
Sprawdzanie wyników
Potwierdzenia są cenne w przypadku operacji testowania, których wyniki nie są oczywiste z szybkiej inspekcji wizualnej.
Rozważmy na przykład następujący kod, który aktualizuje zmienną iMols
na podstawie zawartości połączonej listy wskazywanej przez :mols
/* 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
Liczba cząsteczek liczone iMols
przez musi być zawsze mniejsza lub równa całkowitej liczbie cząsteczek. numMols
Inspekcja wizualna pętli nie pokazuje, że będzie to konieczne, więc instrukcja asercji jest używana po pętli do testowania tego warunku.
Znajdowanie nieobsługiwane błędy
Asercji można używać do testowania warunków błędów w punkcie kodu, w którym powinny zostać obsłużone wszelkie błędy. W poniższym przykładzie rutyna graficzna zwraca kod błędu lub zero dla powodzenia.
myErr = myGraphRoutine(a, b);
/* Code to handle errors and
reset myErr if successful */
ASSERT(!myErr); -- MFC version
_ASSERT(!myErr); -- CRT version
Jeśli kod obsługi błędów działa prawidłowo, powinien zostać obsłużony myErr
i zresetowany do zera przed osiągnięciem asercji. Jeśli myErr
ma inną wartość, asercji zakończy się niepowodzeniem, program zostanie zatrzymany, a zostanie wyświetlone okno dialogowe Asercji nie powiodło się.
Instrukcje asercji nie są jednak zastępowane kodem obsługi błędów. Poniższy przykład przedstawia instrukcję asercji, która może prowadzić do problemów w końcowym kodzie wydania:
myErr = myGraphRoutine(a, b);
/* No Code to handle errors */
ASSERT(!myErr); // Don't do this!
_ASSERT(!myErr); // Don't do this, either!
Ten kod opiera się na instrukcji asercji do obsługi warunku błędu. W związku z tym każdy kod błędu zwrócony przez myGraphRoutine
usługę będzie nieobsługiwany w końcowym kodzie wydania.