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