Empfohlene Vorgehensweisen und Beispiele (SAL)

Im Folgenden finden Sie einige Möglichkeiten, um die Quellcodeanmerkungssprache (Source Code Annotation Language, SAL) optimal zu verwenden und einige häufige Probleme zu vermeiden.

_In_

Wenn die Funktion in das Element schreiben soll, verwenden Sie _Inout_ anstelle von _In_. Dies ist in Fällen der automatisierten Konvertierung von älteren Makros in SAL relevant. Vor SAL verwendeten viele Programmierer Makros als Kommentare – Makros, die benannt INwurden, oder OUTIN_OUTVarianten dieser Namen. Obwohl wir empfehlen, diese Makros in SAL zu konvertieren, empfehlen wir Ihnen auch, vorsichtig zu sein, wenn Sie sie konvertieren, da sich der Code möglicherweise geändert hat, da der ursprüngliche Prototyp geschrieben wurde und das alte Makro möglicherweise nicht mehr die Funktionsweise des Codes widerspiegelt. Achten Sie besonders auf das OPTIONAL Kommentarmakro, da es häufig falsch platziert wird , z. B. auf der falschen Seite eines Kommas.

#include <sal.h>

// Incorrect
void Func1(_In_ int *p1)
{
    if (p1 == NULL)
        return;

    *p1 = 1;
}

// Correct
// _Out_opt_ because the function tolerates NULL as a valid argument, i.e.
// no error is returned. If the function didn't check p1 for NULL, then
// _Out_ would be the better choice
void Func2(_Out_opt_ PCHAR p1)
{
    if (p1 == NULL)
        return;

    *p1 = 1;
}

_opt_

Wenn der Aufrufer keinen NULL-Zeiger übergeben darf, verwenden _In_ Oder _Out_ anstelle oder _In_opt_ _Out_opt_. Dies gilt auch für eine Funktion, die ihre Parameter überprüft und einen Fehler zurückgibt, wenn dies NULL nicht der Fall sein sollte. Auch wenn eine Funktion den Parameter auf unerwartete NULL und ordnungsgemäße Rückgabe überprüft, ist eine gute defensive Codierungspraxis, bedeutet dies jedoch nicht, dass die Parameteranmerkung einen optionalen Typ (_*Xxx*_opt_) aufweisen kann.

#include <sal.h>

// Incorrect
void Func1(_Out_opt_ int *p1)
{
    *p = 1;
}

// Correct
void Func2(_Out_ int *p1)
{
    *p = 1;
}

_Pre_defensive_ und _Post_defensive_

Wenn eine Funktion an einer Vertrauensgrenze angezeigt wird, empfiehlt es sich, die _Pre_defensive_ Anmerkung zu verwenden. Der Modifizierer "defensiv" ändert bestimmte Anmerkungen, um anzugeben, dass die Schnittstelle zum Zeitpunkt des Aufrufs streng überprüft werden sollte, aber im Implementierungstext sollte davon ausgegangen werden, dass falsche Parameter übergeben werden können. In diesem Fall wird bei einer Vertrauensgrenze bevorzugt, um anzugeben, _In_ _Pre_defensive_ dass zwar ein Aufrufer einen Fehler erhält, wenn er versucht, zu übergeben NULL, der Funktionstext analysiert wird, als ob der Parameter sein könnte NULL, und alle Versuche, den Zeiger abzuleiten, ohne ihn zuerst zu überprüfen NULL , gekennzeichnet werden. Eine _Post_defensive_ Anmerkung ist auch verfügbar, für die Verwendung in Rückrufen, bei denen die vertrauenswürdige Partei als Aufrufer angenommen wird und der nicht vertrauenswürdige Code der aufgerufene Code ist.

_Out_writes_

Das folgende Beispiel zeigt einen häufigen Missbrauch von _Out_writes_.

#include <sal.h>

// Incorrect
void Func1(_Out_writes_(size) CHAR *pb,
    DWORD size
);

Die Anmerkung _Out_writes_ gibt an, dass Sie über einen Puffer verfügen. Sie weist cb Bytes zu, wobei das erste Byte beim Beenden initialisiert wurde. Diese Anmerkung ist nicht streng falsch und es ist hilfreich, die zugewiesene Größe auszudrücken. Es wird jedoch nicht mitgeteilt, wie viele Elemente die Funktion initialisiert.

Das nächste Beispiel zeigt drei richtige Methoden, um die genaue Größe des initialisierten Teils des Puffers vollständig anzugeben.

#include <sal.h>

// Correct
void Func1(_Out_writes_to_(size, *pCount) CHAR *pb,
    DWORD size,
    PDWORD pCount
);

void Func2(_Out_writes_all_(size) CHAR *pb,
    DWORD size
);

void Func3(_Out_writes_(size) PSTR pb,
    DWORD size
);

_Out_ PSTR

Die Verwendung von _Out_ PSTR ist fast immer falsch. Diese Kombination wird als Ausgabeparameter interpretiert, der auf einen Zeichenpuffer zeigt und der Puffer null-beendet ist.

#include <sal.h>

// Incorrect
void Func1(_Out_ PSTR pFileName, size_t n);

// Correct
void Func2(_Out_writes_(n) PSTR wszFileName, size_t n);

Eine Anmerkung wie _In_ PCSTR ist häufig und nützlich. Sie verweist auf eine Eingabezeichenfolge, die null beendet hat, da die Voraussetzung für _In_ die Erkennung einer null-beendeten Zeichenfolge zulässig ist.

_In_ WCHAR* p

_In_ WCHAR* p gibt an, dass ein Eingabezeiger p vorhanden ist, der auf ein Zeichen zeigt. In den meisten Fällen ist dies jedoch wahrscheinlich nicht die beabsichtigte Spezifikation. Was wahrscheinlich beabsichtigt ist, ist die Spezifikation eines null-beendeten Arrays; verwenden Sie _In_ PWSTRdazu .

#include <sal.h>

// Incorrect
void Func1(_In_ WCHAR* wszFileName);

// Correct
void Func2(_In_ PWSTR wszFileName);

Die ordnungsgemäße Spezifikation der Nullentsetzung fehlt, ist üblich. Verwenden Sie die entsprechende STR Version, um den Typ zu ersetzen, wie im folgenden Beispiel gezeigt.

#include <sal.h>
#include <string.h>

// Incorrect
BOOL StrEquals1(_In_ PCHAR p1, _In_ PCHAR p2)
{
    return strcmp(p1, p2) == 0;
}

// Correct
BOOL StrEquals2(_In_ PSTR p1, _In_ PSTR p2)
{
    return strcmp(p1, p2) == 0;
}

_Out_range_

Wenn der Parameter ein Zeiger ist und Sie den Bereich des Werts des Elements ausdrücken möchten, auf das der Zeiger verweist, verwenden Sie _Deref_out_range_ anstelle von _Out_range_. Im folgenden Beispiel wird der Bereich von *pcbFilled ausgedrückt, nicht pcbFilled.

#include <sal.h>

// Incorrect
void Func1(
    _Out_writes_bytes_to_(cbSize, *pcbFilled) BYTE *pb,
    DWORD cbSize,
    _Out_range_(0, cbSize) DWORD *pcbFilled
);

// Correct
void Func2(
    _Out_writes_bytes_to_(cbSize, *pcbFilled) BYTE *pb,
    DWORD cbSize,
    _Deref_out_range_(0, cbSize) _Out_ DWORD *pcbFilled
);

_Deref_out_range_(0, cbSize) ist nicht unbedingt für einige Tools erforderlich, da sie abgeleitet _Out_writes_to_(cbSize,*pcbFilled)werden können, aber sie wird hier zur Vollständigkeit angezeigt.

Falscher Kontext in _When_

Ein weiterer häufiger Fehler ist die Verwendung der Nachzustandsbewertung für Vorbedingungen. Im folgenden Beispiel _Requires_lock_held_ ist eine Voraussetzung.

#include <sal.h>

// Incorrect
_When_(return == 0, _Requires_lock_held_(p->cs))
int Func1(_In_ MyData *p, int flag);

// Correct
_When_(flag == 0, _Requires_lock_held_(p->cs))
int Func2(_In_ MyData *p, int flag);

Der Ausdruck return bezieht sich auf einen Poststatuswert, der im Vorabzustand nicht verfügbar ist.

TRUE in _Success_

Wenn die Funktion erfolgreich ausgeführt wird, wenn der Rückgabewert ungleich Null ist, verwenden Sie return != 0 anstelle von return == TRUE. Nonzero bedeutet nicht unbedingt die Äquivalenz mit dem tatsächlichen Wert, den der Compiler bereitstellt TRUE. Der Parameter ist _Success_ ein Ausdruck, und die folgenden Ausdrücke werden als gleichwertig ausgewertet: return != 0, , return != false, return != FALSE, und return ohne Parameter oder Vergleiche.

// Incorrect
_Success_(return == TRUE) _Acquires_lock_(*lpCriticalSection)
BOOL WINAPI TryEnterCriticalSection(
  _Inout_ LPCRITICAL_SECTION lpCriticalSection
);

// Correct
_Success_(return != 0) _Acquires_lock_(*lpCriticalSection)
BOOL WINAPI TryEnterCriticalSection(
  _Inout_ LPCRITICAL_SECTION lpCriticalSection
);

Referenzvariable

Bei einer Referenzvariablen verwendete die vorherige Version von SAL den impliziten Zeiger als Anmerkungsziel und erforderte das Hinzufügen von __deref Anmerkungen, die einer Referenzvariablen zugeordnet sind. Diese Version verwendet das Objekt selbst und erfordert _Deref_nicht.

#include <sal.h>

// Incorrect
void Func1(
    _Out_writes_bytes_all_(cbSize) BYTE *pb,
    _Deref_ _Out_range_(0, 2) _Out_ DWORD &cbSize
);

// Correct
void Func2(
    _Out_writes_bytes_all_(cbSize) BYTE *pb,
    _Out_range_(0, 2) _Out_ DWORD &cbSize
);

Anmerkungen zu Rückgabewerten

Das folgende Beispiel zeigt ein häufiges Problem bei Rückgabewertanmerkungen.

#include <sal.h>

// Incorrect
_Out_opt_ void *MightReturnNullPtr1();

// Correct
_Ret_maybenull_ void *MightReturnNullPtr2();

In diesem Beispiel wird angegeben, _Out_opt_ dass der Zeiger als Teil der Vorbedingung sein NULL kann. Voraussetzungen können jedoch nicht auf den Rückgabewert angewendet werden. In diesem Fall ist _Ret_maybenull_die richtige Anmerkung .

Siehe auch

Verwenden von SAL-Anmerkungen zur Reduzierung von C/C++-Codefehlern
Einführung in SAL
Kommentieren von Funktionsparametern und Rückgabewerten
Annotieren des Funktionsverhaltens
Kommentieren von Strukturen und Klassen
Kommentieren des Sperrverhaltens
Angeben, wann und wo eine Anmerkung angewendet wird
Systeminterne Funktionen