Поделиться через


Рекомендации и примеры (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
// _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_

Если вызывающему объекту не разрешено передавать указатель 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 почти всегда неправильно. Это сочетание интерпретируется как имеющий выходной параметр, указывающий на символьный буфер, и буфер завершается значением NULL.

#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 на общую и полезную. Он указывает на входную строку с завершением null, так как предусловие _In_ позволяет распознать строку, завершающуюся значением NULL.

_In_ WCHAR* p

_In_ WCHAR* p говорит, что есть указатель p ввода, указывающий на один символ. Однако в большинстве случаев это, вероятно, не спецификация, предназначенная. Вместо этого, вероятно, предполагается, что спецификация массива, завершаемого значением NULL; для этого используйте _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_него. В следующем примере диапазон *pcbFilled выражается, а не 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) не является строго обязательным для некоторых инструментов, так как он может быть выведен из _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
Аннотирование параметров функции и возвращаемых значений
Аннотирование поведения функции
Аннотирование структур и классов
Аннотирование поведения блокировки
Указание того, когда и где применяется заметка
Встроенные функции