Einführung in SAL
Die Microsoft-Quellcodeanmerkungssprache (SAL) stellt einen Satz von Anmerkungen, die Sie verwenden können, um zu beschreiben, wie eine Funktion seine Parameter verwendet, Annahmen, die sie über sie macht und dadurch, dass sie macht, wenn sie beendet.Die Anmerkungen werden in der Headerdatei <sal.h> definiert.Visual Studio-Codeanalyse für C++ SAL-Anmerkungen verwendet, um die Analyse von Funktionen zu ändern.Weitere Informationen zu SAL 2.0 für Windows-Treiberentwicklung, finden Sie unter SAL 2.0 Anmerkungen für Windows-Treiber.
Systemintern stellen C und C++ nur eingeschränkte Möglichkeiten für Entwickler zur konsistent Eilabsicht und zur Invarianz bereit.Mit SAL-Anmerkungen verwenden, können Sie Features ausführlich beschreiben, damit Entwickler, die sie nutzen, besser verstehen, wie diese verwendet werden.
Was ist SAL und warum sollten Sie es verwenden?
Einfach angegeben, ist SAL Dies ist eine kostengünstige Möglichkeit, den Compiler den Code für Sie überprüfen lassen.
SAL kann Code wertvoller
SAL kann Ihnen helfen, den Codeentwurf verständlich zu machen, Menschen und für Codeanalysetools.Das folgende Beispiel, das C Laufzeitfeature memcpy anzeigt:
void * memcpy(
void *dest,
const void *src,
size_t count
);
Können Sie mitteilen, was diese Funktion wird?Wenn eine Funktion implementiert oder aufgerufen wird, müssen bestimmte Eigenschaften beibehalten werden, um Programmkorrektheit sicherzustellen.Derzeit mit einer Deklaration wie im Beispiel berücksichtigen, kennen Sie nicht, was sie sind.Ohne SAL-Anmerkungen würden Sie auf Dokumentation erstellen oder Kommentare Code müssen.Hier ist, was die MSDN-Dokumentation für memcpy besagt:
"Kopienzählbytes src zu DEST.Wenn die Quelle und das Ziel überschneiden, wird das Verhalten von memcpy nicht definiert.Verwendung memmove, um überlappender Bereichen zu behandeln.Sicherheitshinweis: überprüfen, ob der Zielpuffer dieselbe Größe oder größer als der Quellpuffer ist.Weitere Informationen finden Sie unter die Vermeiden von Pufferüberläufen."
Die Dokumentation enthält ein paar Informationen, die vorschlagen, dass der Code bestimmte Eigenschaften verwalten muss, um Programmkorrektheit sicherzustellen:
memcpy kopiert count von Bytes aus dem Quellpuffer dem Zielpuffer.
Der Zielpuffer muss wie der Quellpuffer mindestens so groß sein.
Der Compiler kann jedoch die Dokumentation oder die informellen Kommentare nicht lesen.Er weiß nicht, dass eine Beziehung zwischen den zwei Puffern und count gibt, und es kann nicht über eine Beziehung auch effektiv schätzen.SAL konnte mehr Klarheit über die Eigenschaften und die Implementierung der Funktion bereitstellen, wie hier gezeigt:
void * memcpy(
_Out_writes_bytes_all_(count) void *dest,
_In_reads_bytes_(count) const void *src,
size_t count
);
Beachten Sie, dass diese Anmerkungen den Informationen in der MSDN-Dokumentation ähneln, aber sie sind präziser und sie folgen einem semantischen Muster.Wenn Sie diesen Code lesen, können Sie die Eigenschaften dieser Funktion schnell verstehen und wie Pufferüberlaufsicherheitsfragen vermieden werden.Verbessern Sie sogar, die semantischen Muster, die SAL kann die Effizienz und die Effektivität von automatisierten Codeanalysetools in der Frühphase Suche möglicher Fehler verbessern bereitstellt.Stellen Sie sich vor, dass jemand diese verwanzte Implementierung von wmemcpy schreibt:
wchar_t * wmemcpy(
_Out_writes_all_(count) wchar_t *dest,
_In_reads_(count) const wchar_t *src,
size_t count)
{
size_t i;
for (i = 0; i <= count; i++) { // BUG: off-by-one error
dest[i] = src[i];
}
return dest;
}
Diese Implementierung enthält ein Common aus-durch-ein Fehler.Glücklicherweise geschlossen hat der Codeautor das SALZpuffergrößenanmerkungs--eincodeanalysetool konnte den Fehler abfangen ein, indem er diese Funktion allein analysierte.
SAL Grundlagen
SAL definiert vier grundlegende Arten Parameter, die vom Verwendungsmuster kategorisiert werden.
Kategorie |
Parameter-Anmerkung |
Beschreibung |
---|---|---|
Eingabe der aufgerufenen Funktion |
_In_ |
Daten werden der aufgerufenen Funktion übergeben, und werden als schreibgeschützt behandelt. |
Eingabe der aufgerufenen Funktion und die Ausgabe an den Aufrufer |
_Inout_ |
Verwendbare Daten werden an die Funktion übergebene und möglicherweise geändert werden. |
Ausgabe an den Aufrufer |
_Out_ |
Der Aufrufer stellt nur Leerzeichen für die aufgerufene Funktion bereit, um zu schreiben.Die aufgerufene Funktion schreibt Daten in diesen Speicher. |
Ausgabe des Zeigers auf Aufrufer |
_Outptr_ |
Wie Ausgabe an Aufrufer.Der Wert, der durch die aufgerufene Funktion zurückgegeben wird, ist ein Zeiger. |
Diese vier grundlegenden Anmerkungen können expliziter gemacht werden auf verschiedene Arten.Standardmäßig Zeiger muss mit Anmerkungen, den Parameter angenommen wird, dass REQUIRED-sie zu sein, ungleich null sein, damit die Funktion folgt.Die am verwendetste Variante der grundlegenden Anmerkungen gibt an, dass ein Zeiger, den Parameter, OPTIONAL-wenn er NULL ist, ist die Funktion, noch ausführen kann, mit, seine Aufgaben auszuführen.
In dieser Tabelle, wie zwischen den erforderlichen und optionalen Parametern unterscheidet:
Parameter sind erforderlich |
Parameter sind optional |
|
---|---|---|
Eingabe der aufgerufenen Funktion |
_In_ |
_In_opt_ |
Eingabe der aufgerufenen Funktion und die Ausgabe an den Aufrufer |
_Inout_ |
_Inout_opt_ |
Ausgabe an den Aufrufer |
_Out_ |
_Out_opt_ |
Ausgabe des Zeigers auf Aufrufer |
_Outptr_ |
_Outptr_opt_ |
Diese Anmerkungen helfen, mögliche nicht initialisierte Werte und ungültige NULL-Zeiger-Verwendung in einer formalen und genauen Weise zu identifizieren.Das Übergeben von NULL auf einen erforderlichen Parameter kann zu einem Absturz, oder sie kann "Links" den Fehlercode, zurückgegeben werden.Jede Methode, die Funktion kann nicht entsprechen, mit, seine Aufgaben auszuführen.
SAL Beispiele
Dieser Abschnitt enthält Codebeispiele für die grundlegenden SAL-Anmerkungen an.
Mithilfe von Visual Studio-Codeanalyse-Tools, von Fehlern zu suchen
In den Beispielen ist das Visual Studio-Codeanalysetool zusammen mit SAL-Anmerkungen verwendet, um Codefehler zu suchen.Im Folgenden wird gezeigt, wie das ausführt.
So führen Sie Visual Studio-Codeanalysetools und SAL verwenden
In Visual Studio ein C++-Projekt, die SAL-Anmerkungen enthält.
Klicken Sie auf der Menüleiste wählen Sie Build, Codeanalyse für Lösung ausführen aus.
Betrachten Sie das _In_ Beispiel in diesem Abschnitt.Wenn Sie die Codeanalyse darauf ausführen, wird die Warnung angezeigt:
Ungültiger Parameterwert C6387"halb Liter" konnte "0 " sein: Dies entspricht nicht der Funktionsspezifikation für die Funktion "InCallee".
Beispiel: Die _In_ Anmerkung
Die _In_ Anmerkung gibt die an:
Der Parameter muss gültig sein und nicht geändert werden.
Die Funktion liest nur aus dem Puffer mit einem - Element.
Der Aufrufer muss den Puffer bereitstellen und ihn initialisieren.
_In_ gibt "schreibgeschütztes" an.Ein häufiger Fehler ist, _In_ an einen Parameter anzuwenden, der die _Inout_ Anmerkung stattdessen haben soll.
_In_ ist zulässig, jedoch ignoriert durch den Analyzer auf NichtZeigerskalaren.
void InCallee(_In_ int *pInt)
{
int i = *pInt;
}
void GoodInCaller()
{
int *pInt = new int;
*pInt = 5;
InCallee(pInt);
delete pInt;
}
void BadInCaller()
{
int *pInt = NULL;
InCallee(pInt); // pInt should not be NULL
}
Wenn Sie Visual Studio-Codeanalyse auf diesem Beispiel verwenden, sucht es, dass die Aufrufer einen Nicht-NULL-Zeiger zu einem initialisierten Puffer für pInt übergeben.In diesem Fall kann pInt Zeiger nicht NULL sein.
Beispiel: Die _In_opt_ Anmerkung
_In_opt_ entspricht _In_, außer dass dem Eingabeparameter ist zulässig, um NULL sein und daher sollte die Funktion für dieses überprüfen.
void GoodInOptCallee(_In_opt_ int *pInt)
{
if(pInt != NULL) {
int i = *pInt;
}
}
void BadInOptCallee(_In_opt_ int *pInt)
{
int i = *pInt; // Dereferencing NULL pointer ‘pInt’
}
void InOptCaller()
{
int *pInt = NULL;
GoodInOptCallee(pInt);
BadInOptCallee(pInt);
}
Visual Studio-Codeanalyse überprüft die Funktionsüberprüfungen für NULL, bevor sie auf den Puffer zugreift.
Beispiel: Die _Out_ Anmerkung
_Out_ unterstützt in dem ein häufiges Szenario ein Nicht-NULL-Zeiger, der einem Elementpuffer zeigt, übergeben wird und die Funktion initialisiert das Element.Der Aufrufer muss den Puffer nicht vor dem Aufruf initialisieren; die aufgerufene Funktion verspricht, es zu initialisieren, bevor eine Rückgabe erfolgt.
void GoodOutCallee(_Out_ int *pInt)
{
*pInt = 5;
}
void BadOutCallee(_Out_ int *pInt)
{
// Did not initialize pInt buffer before returning!
}
void OutCaller()
{
int *pInt = new int;
GoodOutCallee(pInt);
BadOutCallee(pInt);
delete pInt;
}
Visual Studio-Codeanalyse-Tool überprüft, dass der Aufrufer einen Nicht-NULL-Zeiger auf einen Puffer für pInt wird und dass der Puffer von der Funktion initialisiert wird, bevor er zurückgibt.
Beispiel: Die _Out_opt_ Anmerkung
_Out_opt_ entspricht _Out_, außer dass dem Parameter wird ermöglicht, NULL sein und daher sollte die Funktion für dieses überprüfen.
void GoodOutOptCallee(_Out_opt_ int *pInt)
{
if (pInt != NULL) {
*pInt = 5;
}
}
void BadOutOptCallee(_Out_opt_ int *pInt)
{
*pInt = 5; // Dereferencing NULL pointer ‘pInt’
}
void OutOptCaller()
{
int *pInt = NULL;
GoodOutOptCallee(pInt);
BadOutOptCallee(pInt);
}
Visual Studio-Codeanalyse überprüft dass dieses Funktionsüberprüfungen für NULL, bevor pInt dereferenziert wird, und wenn pInt nicht NULL ist, das der Puffer von der Funktion initialisiert wird, bevor er zurückgibt.
Beispiel: Die _Inout_ Anmerkung
_Inout_ wird verwendet, um einen Zeigerparameter Kommentieren, der möglicherweise von der Funktion geändert wird.Der Zeiger muss auf den gültigen initialisierten Daten vor dem Aufruf zeigen, und wenn es ändert, muss es einen gültigen Wert bei Rückgabe noch haben.Die Anmerkung gibt an, dass die Funktion möglicherweise frei von lesen und den Einelementpuffer schreibt.Der Aufrufer muss den Puffer bereitstellen und ihn initialisieren.
Hinweis |
---|
Wie _Out_ muss _Inout_ auf einen änderbaren Wert gelten. |
void InOutCallee(_Inout_ int *pInt)
{
int i = *pInt;
*pInt = 6;
}
void InOutCaller()
{
int *pInt = new int;
*pInt = 5;
InOutCallee(pInt);
delete pInt;
}
void BadInOutCaller()
{
int *pInt = NULL;
InOutCallee(pInt); // ‘pInt’ should not be NULL
}
Visual Studio-Codeanalyse überprüft, dass Aufrufer einen Nicht-NULL-Zeiger zu einem initialisierten Puffer für pInt übergeben und dass, vor Rückgabe, pInt noch ist NULL, und der Puffer initialisiert wird.
Beispiel: Die _Inout_opt_ Anmerkung
_Inout_opt_ entspricht _Inout_, außer dass dem Eingabeparameter ist zulässig, um NULL sein und daher sollte die Funktion für dieses überprüfen.
void GoodInOutOptCallee(_Inout_opt_ int *pInt)
{
if(pInt != NULL) {
int i = *pInt;
*pInt = 6;
}
}
void BadInOutOptCallee(_Inout_opt_ int *pInt)
{
int i = *pInt; // Dereferencing NULL pointer ‘pInt’
*pInt = 6;
}
void InOutOptCaller()
{
int *pInt = NULL;
GoodInOutOptCallee(pInt);
BadInOutOptCallee(pInt);
}
Visual Studio-Codeanalyse überprüft dass dieses Funktionsüberprüfungen für NULL, bevor sie auf den Puffer zugreift, und wenn pInt nicht NULL ist, dass der Puffer von der Funktion initialisiert wird, bevor er zurückgibt.
Beispiel: Die _Outptr_ Anmerkung
_Outptr_ wird verwendet, um einen Parameter Anmerkungen hinzuzufügen, der vorgesehen ist, um einen Zeiger zurückzugeben.Der - Parameter sollte selbst nicht NULL und gibt der aufgerufenen Funktion sein ein Nicht-NULL-Zeiger vornimmt und Punkte dieses Zeigers auf initialisierten Daten.
void GoodOutPtrCallee(_Outptr_ int **pInt)
{
int *pInt2 = new int;
*pInt2 = 5;
*pInt = pInt2;
}
void BadOutPtrCallee(_Outptr_ int **pInt)
{
int *pInt2 = new int;
// Did not initialize pInt buffer before returning!
*pInt = pInt2;
}
void OutPtrCaller()
{
int *pInt = NULL;
GoodOutPtrCallee(&pInt);
BadOutPtrCallee(&pInt);
}
Visual Studio-Codeanalyse überprüft, dass der Aufrufer einen Nicht-NULL-Zeiger für *pInt wird und dass der Puffer von der Funktion initialisiert wird, bevor er zurückgibt.
Beispiel: Die _Outptr_opt_ Anmerkung
_Outptr_opt_ entspricht _Outptr_, dass der Parameter ist OPTIONAL--danrufer kann in einen NULL-Zeiger für den - Parameter übergeben.
void GoodOutPtrOptCallee(_Outptr_opt_ int **pInt)
{
int *pInt2 = new int;
*pInt2 = 6;
if(pInt != NULL) {
*pInt = pInt2;
}
}
void BadOutPtrOptCallee(_Outptr_opt_ int **pInt)
{
int *pInt2 = new int;
*pInt2 = 6;
*pInt = pInt2; // Dereferencing NULL pointer ‘pInt’
}
void OutPtrOptCaller()
{
int **ppInt = NULL;
GoodOutPtrOptCallee(ppInt);
BadOutPtrOptCallee(ppInt);
}
Visual Studio-Codeanalyse überprüft dass dieses Funktionsüberprüfungen für NULL, bevor *pInt dereferenziert wird, und dass der Puffer von der Funktion initialisiert wird, bevor er zurückgibt.
Beispiel: Die _Success_ Anmerkung in Verbindung mit _Out_
Anmerkungen können für die meisten Objekte angewendet werden.Insbesondere können Sie eine ganze Funktion kommentieren.Eine der diese Eigenschaften einer Funktion ist, dass sie folgen oder fehlschlagen kann.Aber wie die Zuordnung zwischen einem Puffer und seiner Größe, kann C/C++ Funktionserfolg oder Fehler ausdrücken.Wie mit der _Success_ Anmerkung verwenden, können Sie bestimmen, welcher Erfolg für eine Funktion aussieht.Der Parameter für die _Success_ Anmerkung ist nur ein Ausdruck, der angibt, ob es zutrifft, dass die Funktion erfolgreich ausgeführt wurde.Der Ausdruck kann aller sein, den der Anmerkungsparser behandeln kann.Die Auswirkungen der Anmerkungen, nachdem die Funktion nur anwendbar sind, wenn die Funktion folgt.Dieses Beispiel zeigt, wie _Success_ auf _Out_ interagiert, um das Richtige durchzuführen.Sie können das - Schlüsselwort return verwenden, um den Rückgabewert darzustellen.
_Success_(return != false) // Can also be stated as _Success_(return)
bool GetValue(_Out_ int *pInt, bool flag)
{
if(flag) {
*pInt = 5;
return true;
} else {
return false;
}
}
Die _Out_ Anmerkung wird Visual Studio-Codeanalyse, um zu überprüfen, dass der Aufrufer einen Nicht-NULL-Zeiger auf einen Puffer für pInt wird und dass der Puffer von der Funktion initialisiert wird, bevor er zurückgibt.
SAL empfohlen
Hinzufügen von Anmerkungen zum vorhandenen Code
SAL ist eine leistungsstarke Technologie, die Ihnen helfen kann, die Sicherheit und Zuverlässigkeit des Codes zu verbessern.Nachdem Sie SAL erfahren, können Sie die neue Möglichkeit zur täglichen Arbeiten anwenden.Im neuen Code können Sie Salz-basierte Spezifikation entwurfsbedingt verwenden gründlich; im älteren Code können Sie Anmerkungen inkrementell hinzufügen und Vorteile steigern, wenn Sie aktualisieren.
Öffentliche Header Microsoft sind bereits gekennzeichnet.Daher empfehlen wir vor, dass in den Projekten Sie zuerst Endknotenfunktionen und Funktionen kommentieren, die Win32-API aufrufen, um den meisten Vorteil abzurufen.
Wenn kommentiere ich?
Im Folgenden einige Richtlinien:
Kommentieren Sie alle Zeigerparameter.
Fügen Sie WertBereichsanmerkungen, damit die Codeanalyse Puffer- und Zeigersicherheit sicherstellen kann.
Fügen Sie Sperrenregeln und Sperrennebeneffekte.Weitere Informationen finden Sie unter Hinzufügen einer Anmerkung zum Sperrverhalten.
Kommentieren Sie Treibereigenschaften und andere domänenspezifische Eigenschaften.
Oder Sie können alle Parameter mit Anmerkungen, um den Absichtfreien überall in verständlich zu machen und diesen zu erleichtern, zu überprüfen, ob Anmerkungen Vergangenheit wurden.
Verwandte Ressourcen
Siehe auch
Referenz
Hinzufügen einer Anmerkung zu Funktionsparametern und Rückgabewerten
Hinzufügen einer Anmerkung zum Funktionsverhalten
Hinzufügen einer Anmerkung zu Strukturen und Klassen
Hinzufügen einer Anmerkung zum Sperrverhalten
Angeben, wann und wo eine Anmerkung gültig ist
Empfohlene Vorgehensweisen und Beispiele (SAL)
Weitere Ressourcen
Verwenden von SAL-Anmerkungen zum Reduzieren von C/C++-Codefehlern