Procedimientos recomendados y ejemplos (SAL)

Estas son algunas maneras de sacar el máximo partido del lenguaje de anotación de código fuente (SAL) y evitar algunos problemas comunes.

_In_

Si se supone que la función debe escribir en el elemento , use _Inout_ en lugar de _In_ . Esto es especialmente relevante en los casos de conversión automatizada de macros anteriores a SAL. Antes de SAL, muchos programadores usaban macros como comentarios, macros que se denominaban IN , , o variantes de estos OUTIN_OUT nombres. Aunque se recomienda convertir estas macros en SAL, también le recomendamos que tenga cuidado al convertirlas porque el código podría haber cambiado desde que se escribió el prototipo original y es posible que la macro anterior ya no refleje lo que hace el código. Tenga especial cuidado con la macro de comentario porque con frecuencia se coloca incorrectamente, por ejemplo, en el OPTIONAL lado incorrecto de una coma.


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

    *p1 = 1;
}

// Correct
void Func2(_Inout_ PCHAR p1)
{
    if (p1 == NULL)
        return;

    *p1 = 1;
}

_opt_

Si el autor de la llamada no puede pasar un puntero nulo, use _In_ o en lugar de o _Out__In_opt__Out_opt_ . Esto se aplica incluso a una función que comprueba sus parámetros y devuelve un error si es NULL cuando no debería serlo. Aunque hacer que una función compruebe si su parámetro es NULL inesperado y devolver correctamente es una buena práctica de codificación defensiva, no significa que la anotación del parámetro pueda ser de un tipo opcional ( _*Xxx*_opt_ ).


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

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

_Pre_defensive_ y _Post_defensive_

Si una función aparece en un límite de confianza, se recomienda usar la _Pre_defensive_ anotación . El modificador "defensivo" modifica ciertas anotaciones para indicar que, en el punto de llamada, la interfaz debe comprobarse estrictamente, pero en el cuerpo de implementación debe suponer que se pueden pasar parámetros incorrectos. En ese caso, se prefiere en un límite de confianza para indicar que, aunque un llamador recibirá un error si intenta pasar NULL, el cuerpo de la función se analizará como si el parámetro pudiera ser NULL y se marcarán los intentos de anular la referencia al puntero sin comprobarlo primero para _In_ _Pre_defensive_ NULL. También hay disponible una anotación, para su uso en devoluciones de llamada donde se supone que la parte de confianza es el autor de la llamada y el código que no es de confianza es _Post_defensive_ el código llamado.

_Out_writes_

En el ejemplo siguiente se muestra un uso incorrecto común de _Out_writes_ .


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

La _Out_writes_ anotación significa que tiene un búfer. Tiene bytes cb asignados, con el primer byte inicializado al salir. Esta anotación no es estrictamente incorrecta y resulta útil expresar el tamaño asignado. Sin embargo, no muestra cuántos elementos inicializa la función.

En el ejemplo siguiente se muestran tres maneras correctas de especificar completamente el tamaño exacto de la parte inicializada del búfer.


// 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

El uso de _Out_ PSTR es casi siempre incorrecto. Esto se interpreta como tener un parámetro de salida que apunta a un búfer de caracteres y termina en NULL.


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

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

Una anotación como _In_ PCSTR es común y útil. Apunta a una cadena de entrada que tiene terminación NULL porque la condición previa de permite el _In_ reconocimiento de una cadena terminada en NULL.

_In_ WCHAR* p

_In_ WCHAR* p indica que hay un puntero de entrada p que apunta a un carácter. Sin embargo, en la mayoría de los casos, probablemente no sea la especificación que se pretende. En su lugar, lo que probablemente se pretende es la especificación de una matriz terminada en NULL; Para ello, use _In_ PWSTR .


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

// Correct
void Func2(_In_ PWSTR wszFileName);

Falta la especificación adecuada de terminación NULL es habitual. Use la versión STR adecuada para reemplazar el tipo, como se muestra en el ejemplo siguiente.


// 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 el parámetro es un puntero y desea expresar el intervalo del valor del elemento al que apunta el puntero, use en lugar _Deref_out_range_ de _Out_range_ . En el ejemplo siguiente, se expresa el intervalo de *pwFilled, no de modo que se rellenó.


// 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) no es estrictamente necesario para algunas herramientas porque se puede inferir de _Out_writes_to_(cbSize,*pcbFilled) , pero se muestra aquí para que sea completa.

Contexto incorrecto en _When_

Otro error común es usar la evaluación posterior al estado para las condiciones previas. En el ejemplo siguiente, _Requires_lock_held_ es una condición previa.


// 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);

La expresión result hace referencia a un valor posterior al estado que no está disponible en estado anterior.

TRUE en _Success_

Si la función se realiza correctamente cuando el valor devuelto es distinto de cero, use return != 0 como condición de éxito en lugar de return == TRUE . Distinto de cero no significa necesariamente la equivalencia con el valor real que el compilador proporciona para TRUE . El parámetro para es una expresión y las expresiones siguientes se evalúan como _Success_ equivalentes: return != 0 , , y sin parámetros ni return != falsereturn != FALSEreturn comparaciones.

// 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 referencia

En el caso de una variable de referencia, la versión anterior de SAL usaba el puntero implícito como destino de anotación y requería la adición de a las anotaciones __deref asociadas a una variable de referencia. Esta versión usa el propio objeto y no requiere el adicional _Deref_ .


// 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
);

Anotaciones en valores devueltos

En el ejemplo siguiente se muestra un problema común en las anotaciones de valor devuelto.


// Incorrect
_Out_opt_ void *MightReturnNullPtr1();

// Correct
_Ret_maybenull_ void *MightReturnNullPtr2();

En este ejemplo, _Out_opt_ indica que el puntero podría ser NULL como parte de la condición previa. Sin embargo, no se pueden aplicar condiciones previas al valor devuelto. En este caso, la anotación correcta es _Ret_maybenull_ .

Vea también

Utilizar anotaciones SAL para reducir defectos de código de C/C++
Introducción a SAL
Anotar parámetros de función y valores devueltos
Anotar el comportamiento de funciones
Anotar structs y clases
Anotar comportamiento de bloqueo
Especificar cuándo y dónde se aplica una anotación
Funciones intrínsecas