Partilhar via


Melhores práticas e exemplos (SAL)

Aqui estão algumas maneiras de tirar o máximo proveito da linguagem de anotação de código-fonte (SAL) e evitar alguns problemas comuns.

_In_

Se a função deve gravar no elemento , use _Inout_ em vez de _In_. Isto é relevante em casos de conversão automática de macros mais antigas para SAL. Antes da SAL, muitos programadores usavam macros como comentários — macros que eram nomeadas IN, OUT, IN_OUT, ou variantes desses nomes. Embora recomendemos que você converta essas macros para SAL, também pedimos que você tenha cuidado ao convertê-las, porque o código pode ter mudado desde que o protótipo original foi escrito e a macro antiga pode não refletir mais o que o código faz. Tenha especial cuidado com a OPTIONAL macro de comentário porque ela é frequentemente colocada incorretamente, 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 ser. Embora ter uma função verificar o seu parâmetro para elementos inesperados NULL e retornar de forma graciosa seja uma boa prática de programação defensiva, isso não significa que a anotação do parâmetro possa 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 numa fronteira de confiança, recomendamos que utilize a _Pre_defensive_ anotação. O modificador "defensivo" modifica certas anotações para indicar que, no ponto de chamada, deve-se verificar rigorosamente a interface, mas no corpo da implementação deve-se assumir que podem ser passados parâmetros incorretos. Nesse caso, _In_ _Pre_defensive_ é preferível na fronteira 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, e quaisquer tentativas de desreferenciar o ponteiro sem antes tentar verificá-lo para NULL são sinalizadas. Uma _Post_defensive_ anotação também está disponível, para uso em retornos de chamada em que a parte confiável é assumida como sendo o chamador e o código não confiável é o código chamado.

_Out_writes_

O exemplo a seguir demonstra um uso indevido comum do _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 cb bytes alocados, com o primeiro byte inicializado ao sair. A anotação não está completamente errada e ajuda a expressar o tamanho alocado. No entanto, ele não diz quantos elementos a função inicializa.

O próximo exemplo 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 é quase sempre errado. Essa combinação é interpretada como tendo um parâmetro de saída que aponta para um buffer de caracteres e o buffer é terminado 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. Ele aponta para uma cadeia de caracteres de entrada que tem terminação nula porque a pré-condição de _In_ permite o reconhecimento de uma cadeia de caracteres terminada em nulo.

_In_ WCHAR* p

_In_ WCHAR* p diz que há um ponteiro p de entrada que aponta para um caractere. No entanto, na maioria dos casos, esta provavelmente não é a especificação pretendida. Em vez disso, o que provavelmente se pretende é 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 de especificação adequada de terminação nula é 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, é expresso o intervalo de *pcbFilled, e não de 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 pode ser inferido a partir do _Out_writes_to_(cbSize,*pcbFilled), mas é mostrado aqui para completude.

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 de estado posterior que não está disponível no estado anterior.

TRUE no _Success_

Se a função for bem-sucedida quando o valor de retorno for diferente de zero, use return != 0 como condição de sucesso em vez de return == TRUE. Não zero não significa necessariamente que é equivalente ao valor real que o compilador fornece para TRUE. O parâmetro to _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 ou 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 destino de anotação e exigia a adição de a __deref às 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 de retorno

O exemplo a seguir mostra um problema comum em anotações de valor de retorno.

#include <sal.h>

// Incorrect
_Out_opt_ void *MightReturnNullPtr1();

// Correct
_Ret_maybenull_ void *MightReturnNullPtr2();

Neste exemplo, _Out_opt_ diz que o ponteiro pode ser NULL como parte da pré-condição. No entanto, as pré-condições não podem ser aplicadas ao valor de retorno. Neste caso, a anotação correta é _Ret_maybenull_.

Ver também

Usando anotações SAL para reduzir defeitos de código C/C++
Compreender SAL
Anotando parâmetros de função e valores de retorno
Anotando o comportamento da função
Anotando instruções e classes
Anotando o comportamento de bloqueio
Especificando quando e onde uma anotação se aplica
Funções intrínsecas