Bonnes pratiques et exemples (SAL)
Voici quelques façons de tirer le meilleur parti du langage d’annotation de code source (SAL) et d’éviter certains problèmes courants.
_In_
Si la fonction est censée écrire dans l’élément, utilisez _Inout_
plutôt _In_
que . Cela s’applique dans les cas de conversion automatisée de macros plus anciennes en SAL. Avant sal, de nombreux programmeurs utilisaient des macros comme commentaires , macros nommées IN
, OUT
ou IN_OUT
variantes de ces noms. Bien que nous vous recommandons de convertir ces macros en SAL, nous vous recommandons également de faire attention lorsque vous les convertissez, car le code a peut-être changé depuis l’écriture du prototype d’origine et l’ancienne macro peut ne plus refléter ce que fait le code. Soyez particulièrement prudent sur la OPTIONAL
macro de commentaire, car elle est fréquemment placée de manière incorrecte, par exemple sur le mauvais côté d’une virgule.
#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_
Si l’appelant n’est pas autorisé à passer un pointeur Null, utilisez _In_
ou _Out_
au lieu de _In_opt_
ou _Out_opt_
. Cela s’applique même à une fonction qui case activée ses paramètres et retourne une erreur si elle est NULL
quand elle ne doit pas être. Bien qu’avoir une fonction case activée son paramètre pour un retour inattendu NULL
et de manière normale est une bonne pratique de codage défensif, cela ne signifie pas que l’annotation de paramètre peut être d’un type facultatif (_*Xxx*_opt_
).
#include <sal.h>
// Incorrect
void Func1(_Out_opt_ int *p1)
{
*p = 1;
}
// Correct
void Func2(_Out_ int *p1)
{
*p = 1;
}
_Pre_defensive_
et _Post_defensive_
Si une fonction apparaît à une limite d’approbation, nous vous recommandons d’utiliser l’annotation _Pre_defensive_
. Le modificateur « défensif » modifie certaines annotations pour indiquer qu’au moment de l’appel, l’interface doit être case activée strictement, mais dans le corps de l’implémentation, il doit supposer que des paramètres incorrects peuvent être passés. Dans ce cas, _In_ _Pre_defensive_
est préférable à une limite d’approbation pour indiquer que même si un appelant obtient une erreur s’il tente de passerNULL
, le corps de la fonction est analysé comme si le paramètre peut être NULL
, et toutes les tentatives de déréférencement du pointeur sans la première case activée lui sont signalées.NULL
Une _Post_defensive_
annotation est également disponible, pour une utilisation dans les rappels où la partie approuvée est supposée être l’appelant et le code non approuvé est le code appelé.
_Out_writes_
L’exemple suivant illustre une mauvaise utilisation courante de _Out_writes_
.
#include <sal.h>
// Incorrect
void Func1(_Out_writes_(size) CHAR *pb,
DWORD size
);
L’annotation _Out_writes_
signifie que vous disposez d’une mémoire tampon. Il a cb
alloué des octets, avec le premier octet initialisé à la sortie. Cette annotation n’est pas strictement incorrecte et il est utile d’exprimer la taille allouée. Toutefois, il ne indique pas le nombre d’éléments initialisé par la fonction.
L’exemple suivant montre trois façons correctes de spécifier entièrement la taille exacte de la partie initialisée de la mémoire tampon.
#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
L’utilisation est _Out_ PSTR
presque toujours incorrecte. Cette combinaison est interprétée comme ayant un paramètre de sortie qui pointe vers une mémoire tampon de caractères et la mémoire tampon est terminée par null.
#include <sal.h>
// Incorrect
void Func1(_Out_ PSTR pFileName, size_t n);
// Correct
void Func2(_Out_writes_(n) PSTR wszFileName, size_t n);
Une annotation telle qu’elle _In_ PCSTR
est courante et utile. Elle pointe vers une chaîne d’entrée qui a un arrêt null, car la condition préalable _In_
permet la reconnaissance d’une chaîne terminée par null.
_In_ WCHAR* p
_In_ WCHAR* p
indique qu’il existe un pointeur p
d’entrée qui pointe vers un caractère. Toutefois, dans la plupart des cas, ce n’est probablement pas la spécification prévue. Au lieu de cela, ce qui est probablement prévu est la spécification d’un tableau terminé par null ; pour ce faire, utilisez _In_ PWSTR
.
#include <sal.h>
// Incorrect
void Func1(_In_ WCHAR* wszFileName);
// Correct
void Func2(_In_ PWSTR wszFileName);
L’absence de spécification appropriée de l’arrêt Null est courante. Utilisez la version appropriée STR
pour remplacer le type, comme illustré dans l’exemple suivant.
#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_
Si le paramètre est un pointeur et que vous souhaitez exprimer la plage de la valeur de l’élément pointé par le pointeur, utilisez _Deref_out_range_
plutôt _Out_range_
que . Dans l’exemple suivant, la plage de *cciFilled est exprimée, et non pas le cpfilled.
#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)
n’est pas strictement nécessaire pour certains outils, car il peut être déduit de _Out_writes_to_(cbSize,*pcbFilled)
, mais il est montré ici pour l’exhaustivité.
Contexte incorrect dans _When_
Une autre erreur courante consiste à utiliser l’évaluation post-état pour les conditions préalables. Dans l’exemple suivant, _Requires_lock_held_
est une condition préalable.
#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);
L’expression return
fait référence à une valeur post-état qui n’est pas disponible en pré-état.
TRUE
dans _Success_
Si la fonction réussit lorsque la valeur de retour est différente de zéro, utilisez return != 0
la condition de réussite au lieu de return == TRUE
. Nonzero ne signifie pas nécessairement l’équivalence avec la valeur réelle que le compilateur fournit .TRUE
Le paramètre à _Success_
utiliser est une expression et les expressions suivantes sont évaluées comme équivalentes : return != 0
, , return != false
return != FALSE
et return
sans paramètres ni comparaisons.
// 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
);
Variable de référence
Pour une variable de référence, la version précédente de SAL a utilisé le pointeur implicite comme cible d’annotation et requis l’ajout d’une __deref
annotation aux annotations attachées à une variable de référence. Cette version utilise l’objet lui-même et ne nécessite _Deref_
pas .
#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
);
Annotations sur les valeurs de retour
L’exemple suivant illustre un problème courant dans les annotations de valeur de retour.
#include <sal.h>
// Incorrect
_Out_opt_ void *MightReturnNullPtr1();
// Correct
_Ret_maybenull_ void *MightReturnNullPtr2();
Dans cet exemple, _Out_opt_
indique que le pointeur peut faire NULL
partie de la condition préalable. Toutefois, les conditions préalables ne peuvent pas être appliquées à la valeur de retour. Dans ce cas, l’annotation correcte est _Ret_maybenull_
.
Voir aussi
Utilisation d’annotations SAL pour réduire les défauts de code C/C++
Présentation de SAL
Annoter les paramètres de fonction et les valeurs de retour
Annoter le comportement de la fonction
Annoter des structs et des classes
Annoter le comportement de verrouillage
Spécification du moment et de l’emplacement d’application d’une annotation
Fonctions intrinsèques