Рекомендации и примеры (SAL)
Ниже приведены некоторые способы получить большую часть языка заметок исходного кода (SAL) и избежать некоторых распространенных проблем.
_In_
Если функция должна записываться в элемент, используйте _Inout_
вместо _In_
него. Это важно в случаях автоматического преобразования из старых макросов в SAL. До SAL многие программисты использовали макросы в качестве комментариев — макросы, которые были названы IN
, OUT
IN_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
Аннотирование параметров функции и возвращаемых значений
Аннотирование поведения функции
Аннотирование структур и классов
Аннотирование поведения блокировки
Указание того, когда и где применяется заметка
Встроенные функции