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, OUTou IN_OUTvariantes 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 != falsereturn != FALSEet 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