Procedimientos recomendados y ejemplos (SAL)
A continuación verá algunas de las maneras de sacar el máximo partido del lenguaje de anotación de código fuente (SAL) y de evitar varias problemas comunes.
_In_
Si se supone que la función escribe en el elemento, use _Inout_
, en lugar de _In_
. Esto es relevante en los casos de conversión automatizada de macros anteriores a SAL. Antes de SAL, muchos programadores usaban macros como comentarios (macros denominadas IN
, OUT
, IN_OUT
, o cualquier otra variante de estos nombres). Aunque se recomienda convertir estas macros a SAL, también es muy importante que tenga cuidado al hacerlo, ya que es posible que el código haya cambiado desde que se escribiera el prototipo original y que la macro antigua ya no refleje lo que hace el código. Tenga especial cuidado con la macro de comentario OPTIONAL
, ya que con frecuencia se coloca incorrectamente (por ejemplo, en el lado incorrecto de una coma).
#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 el autor de la llamada no puede pasar un puntero nulo, use _In_
o _Out_
, en lugar de _In_opt_
o _Out_opt_
. Esto se aplica incluso a una función que comprueba sus parámetros y devuelve un error si es NULL
cuando no debe ser. Aunque tener una función que compruebe si hay algún NULL
inesperado en su parámetro y devuelva lo correcto es una buena práctica de codificación defensiva, no significa que la anotación de parámetros pueda ser de un tipo opcional (_*Xxx*_opt_
).
#include <sal.h>
// 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 anotación _Pre_defensive_
. "defensive" modifica ciertas anotaciones para indicar que, en el momento de la llamada, la interfaz debe comprobarse estrictamente, pero en el cuerpo de la implementación debe suponer que se podrían pasar parámetros incorrectos. En ese caso, _In_ _Pre_defensive_
se prefiere en un límite de confianza para indicar que, aunque un autor de la llamada obtiene un error si intenta pasar NULL
, el cuerpo de la función se analiza como si el parámetro pudiera ser NULL
y cualquier intento de desreferenciar el puntero sin comprobarlo NULL
primero se marca. También hay disponible una anotación _Post_defensive_
, que se puede usar en aquellas devoluciones de llamada en las que se supone que la parte de confianza es el autor de la llamada y el código que no es de confianza es el código llamado.
_Out_writes_
En el siguiente ejemplo se muestra un caso común de uso incorrecto de _Out_writes_
.
#include <sal.h>
// Incorrect
void Func1(_Out_writes_(size) CHAR *pb,
DWORD size
);
La anotación _Out_writes_
indica que tiene un búfer. Tiene cb
bytes asignados y el primero se inicializa al salir. Esta anotación no es estrictamente incorrecta y resulta útil para expresar el tamaño asignado. Sin embargo, no indica cuántos elementos inicializa la función.
En el ejemplo siguiente se muestran tres formas correctas de especificar completamente el tamaño exacto de la parte inicializada del búfer.
#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
El uso de _Out_ PSTR
es casi siempre incorrecto. Esta combinación se interpreta como que tiene un parámetro de salida que apunta a un búfer de caracteres y el búfer termina en null.
#include <sal.h>
// 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, además, útil. Apunta a una cadena de entrada que tiene una terminación null porque la condición previa de _In_
permite el reconocimiento de una cadena terminada en null.
_In_ WCHAR* p
_In_ WCHAR* p
dice que hay un puntero p
de entrada que apunta a un carácter. Sin embargo, en la mayoría de los casos, es probable que esta no sea la especificación prevista. En su lugar, lo que probablemente se pretende es la especificación de una matriz terminada en null; para ello, es preciso usar _In_ PWSTR
.
#include <sal.h>
// Incorrect
void Func1(_In_ WCHAR* wszFileName);
// Correct
void Func2(_In_ PWSTR wszFileName);
Es común que falte la especificación adecuada de terminación null. Use la versión adecuada de STR
para reemplazar el tipo, como se muestra en el ejemplo siguiente.
#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 el parámetro es un puntero y desea expresar el intervalo del valor del elemento al que apunta el puntero, use _Deref_out_range_
, en lugar de _Out_range_
. En el ejemplo siguiente, se expresa el intervalo de *pcbFilled, no 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)
no es estrictamente necesario para algunas herramientas porque se puede deducir de _Out_writes_to_(cbSize,*pcbFilled)
, pero se muestra aquí para completarse.
Contexto incorrecto en _When_
Otro error común es usar la evaluación del estado posterior para las condiciones previas. En el ejemplo siguiente, _Requires_lock_held_
es una condición previa.
#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);
La expresión return
hace referencia a un valor del estado posterior que no está disponible en el estado previo.
TRUE
en _Success_
Si la función se ejecuta correctamente cuando el valor devuelto no es cero, use return != 0
como condición de éxito, en lugar de return == TRUE
. Que un valor no sea cero no necesariamente significa que hay una equivalencia con el valor real que el compilador proporciona para TRUE
. El parámetro para _Success_
es una expresión y las siguientes expresiones se evalúan como equivalentes: return != 0
, return != false
, return != FALSE
y return
sin parámetros ni 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 las variables de referencia, la versión anterior de SAL usaba el puntero implícito como destino de la anotación y requería que se añadiera __deref
a las anotaciones que se adjuntaban a una variable de referencia. Esta versión usa el propio objeto y no requiere _Deref_
.
#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
);
Anotaciones en valores devueltos
En el ejemplo siguiente se muestra un problema común en las anotaciones de valores devueltos.
#include <sal.h>
// Incorrect
_Out_opt_ void *MightReturnNullPtr1();
// Correct
_Ret_maybenull_ void *MightReturnNullPtr2();
En este ejemplo, _Out_opt_
indica que el puntero puede 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_
.
Consulte también
Uso de anotaciones SAL para reducir defectos de código de C/C++
Introducción a SAL
Anotación de parámetros de función y valores devueltos
Anotación del comportamiento de funciones
Anotación de estructuras y clases
Anotación de un comportamiento de bloqueo
Especificación del momento y lugar en que se aplica una anotación
Funciones intrínsecas