SAL(Standard Annotation Language)에 앞서 어노테이션(annotation)이란 뭘까?
annotation : 주석과는 다르면서 소스 코드의 결과에는 영향을 미치지 않는 것을 말한다. #define을 통해 선언되는 상수나 어트리뷰트 등을 이용해 어노테이션을 추가하여 C++ 문법을 지키고 컴파일 결과를 달라지지 않게 하면서 개발자의 의도나 코드의 의미를 나타낼 수 있다.
마이크로소프트는 소스 코드에 개발자의 의도와 소스 코드의 의미를 추가할 수 있는 마이크로소프트 표준 소스 코드 어노테이션 언어로 SAL(Standard Annotation Language)를 제공해주고 있다.
SAL을 왜 사용해야할까?
간단히 말하면 SAL은 컴파일러가 코드를 검사할 수 있는 저렴한 방법이기 때문이다.
코드의 품질을 높이는 SAL
SAL을 사용하면 사용자와 code analysis tool에서 코드 디자인을 더 쉽게 이해할 수 있다.
다음과 같은 memcpy 함수가 있을 때,
void * memcpy(
void *dest,
const void *src,
size_t count
);
이 선언만 보고서는 해당 함수에 대해 정확하게 알 수 없다. SAL 주석이 없으면 설명서 또는 코드 주석을 사용해야 한다.
이 함수(memcpy)에 대한 설명이 다음과 같이 되어있다고 하자.
memcpy는 src에서 dest 까지의 bytes의 count를 복사한다. wmemcpy는 count wide characters (2 bytes)를 복사한다. 만약 src와 dest가 overlap이라면, memcpy의 동작은 정의되지 않는다. memmove를 사용하여 overlap 영역을 처리할 수 있다.
설명서에는 프로그램 정확성을 보장하기 위해 다음과 같은 몇 가지 정보가 포함되어 있다.
- memcpy는 src 버퍼로부터 dest버퍼까지 bytes의 count를 복사한다.
- dest 버퍼는 적어도 src 버퍼만큼 커야 한다.
그러나 컴파일러는 설명서 또는 주석을 읽을 수 없다. 두 버퍼 간에 관계가 있다는 것을 알지 못한다.
SAL은 다음과 같이 함수의 속성 및 구현에 대한 명확성을 제공해줄 수 있다.
void * memcpy(
_Out_writes_bytes_all_(count) void *dest,
_In_reads_bytes_(count) const void *src,
size_t count
);
이러한 주석은 설명서의 정보와 유사하지만 더 간결하다.
SAL 기본
SAL을 사용하기 위해서는 <sal.h>를 소스코드에 include 하여 사용한다. 마이크로소프트가 배포한 윈도 애플리케이션에서 포함되는 대부분의 헤더파일은 sal.h의 내용이 포함되어 있다. 만일 SAL을 사용하고 싶지 않다면 no_sal2.h를 소스코드 맨 처음에 포함할 수 있다. SAL은 마이크로소프트만의 기술이기 때문에 크로스 플랫폼을 지원하는 소스코드의 경우 프로젝트의 성격에 따라 SAL을 사용하지 않아야 되는 경우에 유용하다.
함수의 포인터 인자와 관련된 대표적인 어노테이션에는 _In_, _Out_, _Inout_, _Deref_out_ 등이 있다. _In_은 포인터를 인자로 받는 함수 내부적으로 포인터가 가리키는 데이터를 사용할 때 해당 포인터 변수 인자에 사용되고 _Out_은 반환 값 변수에 사용된다. _Inout_은 인자로 값을 전달하고 함수 내부적으로 변경될 가능성이 있을 때 사용하고, _Deref_out_은 이중 포인터의 경우 사용한다.
SAL 예제
(1) _In_
void InCallee(_In_ int *pInt)
{
int i = *pInt;
}
void GoodInCaller()
{
int *pInt = new int;
*pInt = 5;
InCallee(pInt);
delete pInt;
}
void BadInCaller()
{
int *pInt = NULL;
InCallee(pInt); // pInt should not be NULL
}
(2) _Out_
void GoodOutCallee(_Out_ int *pInt)
{
*pInt = 5;
}
void BadOutCallee(_Out_ int *pInt)
{
// Did not initialize pInt buffer before returning!
}
void OutCaller()
{
int *pInt = new int;
GoodOutCallee(pInt);
BadOutCallee(pInt);
delete pInt;
}
(3) _Inout_
참고
_Inout_은 _Out_의 경우처럼 수정 가능한 값에 적용해야 합니다.
void InOutCallee(_Inout_ int *pInt)
{
int i = *pInt;
*pInt = 6;
}
void InOutCaller()
{
int *pInt = new int;
*pInt = 5;
InOutCallee(pInt);
delete pInt;
}
void BadInOutCaller()
{
int *pInt = NULL;
InOutCallee(pInt); // 'pInt' should not be NULL
}
참고
'C, C++ > C, C++' 카테고리의 다른 글
[C] bit 연산 (0) | 2022.03.24 |
---|---|
[C++] '\n' 과 endl 차이 (0) | 2022.03.02 |
[C++] C++ 언어의 특징 (0) | 2022.03.02 |
[C] 4가지의 메모리 저장소 (스택, 힙, 데이터, 코드 영역) (0) | 2022.01.30 |
[C] C언어에서 음수를 표현하는 방법 (0) | 2022.01.07 |