Melhores práticas e exemplos (SAL)
Aqui estão algumas maneiras de aproveitar ao máximo a SAL (Linguagem de Anotação de Código-Fonte) e evitar alguns problemas comuns.
_In_
Se a função deve gravar no elemento, use _Inout_
em vez de _In_
. Isso é relevante em casos de conversão automatizada de macros mais antigas para SAL. Antes da SAL, muitos programadores usavam macros como comentários, macros nomeadas IN
, OUT
, IN_OUT
ou variantes desses nomes. Embora seja recomendável converter essas macros na SAL, também pedimos que você tenha cuidado ao convertê-las porque o código pode ter sido alterado desde que o protótipo original foi escrito e a macro antiga poderá não refletir mais o que o código faz. Tenha cuidado especial com a macro de comentário OPTIONAL
porque ela é colocada de maneira incorreta com frequência, por exemplo, no lado errado de uma vírgula.
#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_
Se o chamador não tiver permissão para passar um ponteiro nulo, use _In_
ou _Out_
em vez de _In_opt_
ou _Out_opt_
. Isso se aplica até mesmo a uma função que verifica seus parâmetros e retorna um erro se for NULL
quando não deveria. Embora ter uma função que verifique os parâmetros para ver se há um NULL
inesperado e retornar de modo elegante é uma boa prática de codificação defensiva, isso não significa que a anotação de parâmetro pode ser de um 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_
e _Post_defensive_
Se uma função aparecer em um limite de confiança, recomendamos que você use a anotação _Pre_defensive_
. O modificador "defensivo" modifica determinadas anotações para indicar que, no ponto de chamada, a interface deve ser verificada estritamente, mas no corpo da implementação ela deve assumir que parâmetros incorretos podem ter sido passados. Nesse caso, é preferível em um limite de confiança para indicar que, embora um chamador receba um erro se tentar passar NULL
, o corpo da função é analisado como se o parâmetro pudesse ser NULL
, _In_ _Pre_defensive_
e quaisquer tentativas de desreferenciar o ponteiro sem primeiro verificá-lo são NULL
sinalizadas. Uma anotação _Post_defensive_
também está disponível para uso em retornos de chamada em que a parte confiável é considerada o chamador e o código não confiável é o código chamado.
_Out_writes_
O exemplo a seguir demonstra um uso incorreto comum de _Out_writes_
.
#include <sal.h>
// Incorrect
void Func1(_Out_writes_(size) CHAR *pb,
DWORD size
);
A anotação _Out_writes_
significa que você tem um buffer. Ele tem bytes cb
alocados, com o primeiro byte inicializado na saída. Essa anotação não é estritamente errada e é útil para expressar o tamanho alocado. No entanto, ele não informa quantos elementos a função inicializa.
O exemplo a seguir mostra três maneiras corretas de especificar completamente o tamanho exato da parte inicializada do buffer.
#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
O uso de _Out_ PSTR
está quase sempre incorreto. Essa combinação é interpretada como tendo um parâmetro de saída que aponta para um buffer de caracteres e o buffer é encerrado em nulo.
#include <sal.h>
// Incorrect
void Func1(_Out_ PSTR pFileName, size_t n);
// Correct
void Func2(_Out_writes_(n) PSTR wszFileName, size_t n);
Uma anotação como _In_ PCSTR
é comum e útil. Ela aponta para uma cadeia de caracteres de entrada que tem encerramento nulo porque a pré-condição de _In_
permite o reconhecimento de uma cadeia de caracteres terminada em nulo.
_In_ WCHAR* p
_In_ WCHAR* p
informa que há um ponteiro de entrada p
que aponta para um caractere. No entanto, na maioria dos casos, essa provavelmente não é a especificação pretendida. Em vez disso, o que provavelmente se pretendia era a especificação de uma matriz terminada em nulo; para fazer isso, use _In_ PWSTR
.
#include <sal.h>
// Incorrect
void Func1(_In_ WCHAR* wszFileName);
// Correct
void Func2(_In_ PWSTR wszFileName);
A falta da especificação adequada do encerramento nulo é comum. Use a versão apropriada STR
para substituir o tipo, conforme mostrado no exemplo a seguir.
#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_
Se o parâmetro for um ponteiro e você quiser expressar o intervalo do valor do elemento apontado pelo ponteiro, use _Deref_out_range_
em vez de _Out_range_
. No exemplo a seguir, o intervalo de *pcbFilled é expresso, não 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)
não é estritamente necessário para algumas ferramentas porque ela pode ser inferida de _Out_writes_to_(cbSize,*pcbFilled)
, mas é mostrada aqui para exaustividade.
Contexto errado em _When_
Outro erro comum é usar a avaliação pós-estado para pré-condições. No exemplo a seguir, _Requires_lock_held_
é uma pré-condição.
#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);
A expressão return
refere-se a um valor pós-estado que não está disponível no pré-estado.
TRUE
em _Success_
Se a função tiver êxito quando o valor retornado não for zero, use return != 0
como a condição de êxito em vez de return == TRUE
. Não zero não significa necessariamente equivalência com o valor real que o compilador fornece para TRUE
. O parâmetro para _Success_
é uma expressão e as seguintes expressões são avaliadas como equivalentes: return != 0
, return != false
, return != FALSE
e return
sem parâmetros nem comparações.
// 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
);
Variável de referência
Para uma variável de referência, a versão anterior da SAL usava o ponteiro implícito como o destino da anotação e exigia a adição de uma __deref
a anotações anexadas a uma variável de referência. Esta versão usa o próprio objeto e não requer _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
);
Anotações em valores retornados
O exemplo a seguir mostra um problema comum em anotações de valor retornado.
#include <sal.h>
// Incorrect
_Out_opt_ void *MightReturnNullPtr1();
// Correct
_Ret_maybenull_ void *MightReturnNullPtr2();
Neste exemplo, _Out_opt_
informa que o ponteiro pode ser NULL
parte da pré-condição. No entanto, as pré-condições não podem ser aplicadas ao valor retornado. Nesse caso, a anotação correta é _Ret_maybenull_
.
Confira também
Usando anotações da SAL para reduzir defeitos de código do C/C++
Noções básicas de SAL
Anotando parâmetros de função e valores retornados
Anotando o comportamento da função
Anotando estruturas e classes
Anotando o comportamento de bloqueio
Especificando quando e onde uma anotação se aplica
Funções intrínsecas