Рекомендации и примеры (SAL)

Ниже приведены некоторые способы получения максимальной из языка аннотирования исходного кода (SAL) и устранения некоторых распространенных проблем.

_In_

Если функция должна выполнять запись в элемент, используйте _Inout_ вместо _In_ . Это особенно важно в случаях автоматического преобразования из старых макросов в SAL. До SAL многие программисты использовали макросы как комментарии — макросы с именами IN , OUTIN_OUT , или разновидностями этих имен. Хотя мы рекомендуем преобразовывать эти макросы в SAL, мы советуем соблюдать осторожность при их преобразовании, поскольку код мог быть изменен с момента написания первоначального прототипа, а старый макрос больше не будет отражать то, что делает код. Будьте особенно осторожны с OPTIONAL макросом комментариев, так как он часто размещается неправильно, например, на неправильной стороне запятой.

#include <sal.h>

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

Если вызывающему объекту не разрешено передавать указатель null, используйте _In_ или _Out_ вместо _In_opt_ или _Out_opt_ . Это относится даже к функции, которая проверяет свои параметры и возвращает ошибку, если она не NULL должна быть. Несмотря на то, что функция проверки ее параметра на непредвиденное NULL и корректное возвращение является хорошей защитной практикой, это не означает, что заметка параметра может иметь необязательный тип ( _*Xxx*_opt_ ).

#include <sal.h>

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

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

_Pre_defensive_ и _Post_defensive_

Если функция находится на границе доверия, то рекомендуется использовать примечание _Pre_defensive_. Модификатор "защитное" изменяет определенные заметки, чтобы указать, что в момент вызова интерфейс должен проверяться строго, но в теле реализации он должен предположить, что могут быть переданы неверные параметры. В этом случае _In_ _Pre_defensive_ предпочтительнее на границе доверия, чтобы указать, что хотя вызывающий объект будет получать ошибку при попытке передать NULL , тело функции будет анализироваться так, как если бы параметр был NULL , а все попытки отменить ссылку на указатель без предварительной проверки для NULL будут помечены. Примечание _Post_defensive_ также доступно для использования в обратных вызовах, где вызывающий код считается надежной стороной, а вызываемый код — ненадежной.

_Out_writes_

В следующем примере демонстрируется обычное неправильное использование _Out_writes_ .

#include <sal.h>

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

Заметка _Out_writes_ означает, что у вас есть буфер. cbВ нем выделено байты с первым байтом, инициализированным при выходе. Эта аннотация не является строго неправильной и позволяет выражать выделенный размер. Однако он не указывает, сколько элементов инициализируется функцией.

В следующем примере показаны три правильных способа полного указания точного размера инициализированной части буфера.

#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

Использование параметра _Out_ PSTR практически всегда неверно. Это сочетание интерпретируется как наличие выходного параметра, указывающего на символьный буфер, а буфер завершается нулем.

#include <sal.h>

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

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

Аннотация, например _In_ PCSTR , является общей и полезной. Он указывает на входную строку с завершающим нулем, так как предварительное условие _In_ разрешает распознавание строки, завершающейся нулем.

_In_ WCHAR* p

_In_ WCHAR* p говорит, что имеется указатель p ввода, указывающий на один символ. Однако в большинстве случаев это не является требуемой спецификацией. Вместо этого, скорее всего, используется спецификация массива, заканчивающегося нулем; для этого используйте _In_ PWSTR .

#include <sal.h>

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

// Correct
void Func2(_In_ PWSTR wszFileName);

Отсутствие правильной спецификации завершения null является наиболее распространенным. Используйте соответствующую STR версию для замены типа, как показано в следующем примере.

#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_

Если параметр является указателем и необходимо выразить диапазон значения элемента, на который указывает указатель, используйте _Deref_out_range_ вместо _Out_range_ . В следующем примере диапазон * Пкбфиллед выражается, а не Пкбфиллед.

#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) не обязательно является обязательным для некоторых средств, так как его можно вывести из _Out_writes_to_(cbSize,*pcbFilled) него, но он показан здесь для полноты.

Неправильный контекст в _When_

Еще одна распространенная ошибка — использование вычисления после состояния для предусловий. В следующем примере _Requires_lock_held_ — это предварительное условие.

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

Выражение return ссылается на значение после состояния, которое недоступно в предварительном состоянии.

TRUE в _Success_.

Если функция завершается успешно, если возвращаемое значение не равно нулю, используйте return != 0 в качестве условия успеха, а не return == TRUE . Ненулевое значение не всегда означает эквивалентность фактического значения, предоставляемого TRUE компилятором. Параметр в _Success_ — выражение и следующие выражения вычисляются как равные. return != 0, return != false, return != FALSE и return без параметров или сравнений.

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

Ссылочная переменная

Для ссылочной переменной предыдущая версия SAL использовала подразумеваемый указатель в качестве целевого объекта заметки и требовал добавления __deref к заметкам, которые присоединены к ссылочной переменной. Эта версия использует сам объект и не требует дополнительного _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
);

Аннотации для возвращаемых значений

В следующем примере показана общая проблема в аннотациях возвращаемого значения.

#include <sal.h>

// Incorrect
_Out_opt_ void *MightReturnNullPtr1();

// Correct
_Ret_maybenull_ void *MightReturnNullPtr2();

В этом примере _Out_opt_ сообщает, что указатель может быть NULL частью предусловия. Однако предусловия нельзя применить к возвращаемому значению. В этом случае правильная Аннотация имеет _Ret_maybenull_ значение.

См. также:

Использование аннотаций SAL для сокращения числа дефектов кода C/C++
Основные сведения о языке SAL
Добавление заметок к параметрам и возвращаемым значениям функций
Аннотирование поведения функции
Аннотирование структур и классов
Аннотирование режима блокировки
Указание времени и места применения заметки
Встроенные функции