D 언어 사양 — 메모리 안전

D 언어 사양 — 메모리 안전 (Memory Safety)

프로그램이 메모리를 손상시키는 일이 불가능한가를 따지는 챕터예요. D 언어에서 '메모리 안전'은 곧 프로그램이 절대 메모리를 망가뜨리지 못하도록 보장되는 것을 뜻해요. 그래서 D의 안전한 부분집합(safe subset)은 어떤 상황에서도 메모리 손상으로 이어지지 않는다고 보장되는 언어 기능들만 모아 놓은 거죠. 이 보장이 왜 필요한지에 대한 이유가 궁금하다면 아래 출처 문서에 링크된 rationale 글을 참고하면 돼요.

출처: https://dlang.org/spec/memory-safe-d.html

본문

메모리 안전(memory safety)이라는 건, 프로그램이 메모리를 손상시키는 것이 불가능하다고 정의돼요. 따라서 D의 안전한 부분집합은 결코 메모리 손상을 일으키지 않는다고 보장되는 프로그래밍 언어 기능만으로 구성돼요. 자세한 이유는 이 문서를 참고하세요.

메모리 안전한 코드는 특정 언어 기능을 사용할 수 없어요, 예를 들어:

  • 타입 시스템을 깨뜨리는 캐스트(cast)
  • 포인터 값의 수정
  • 지역 변수나 함수 매개변수의 주소를 취하는 것

사용법 (Usage)

메모리 안전 관점에서 함수는 세 가지 범주로 나뉘어요:

@system 함수는 언어 관점에서 합법적인 어떤 연산도 수행할 수 있어요. 여기에는 포인터 캐스트나 포인터 산술처럼 본질적으로 메모리 안전하지 않은 연산도 포함돼요. 다만 정적 배열을 범위 밖으로 인덱싱하거나 만료된 스택 프레임을 가리키는 포인터를 반환하는 것처럼, 컴파일 타임에 알려지는 메모리 손상 연산은 여전히 오류를 발생시킬 수 있어요. 그리고 @system 함수는 @safe 함수에서 직접 호출할 수 없어요.

@trusted 함수는 @safe 함수에서 호출될 수 있기 때문에 안전한 인터페이스(safe interface) 를 가져야 해요. 내부적으로는 @system 함수의 모든 능력을 가지고 있어요. 그래서 그 사용 범위는 매우 제한적이어야 해요. @trusted 함수의 전형적인 용도로는, 버퍼 포인터와 길이를 별도의 인자로 받는 시스템 호출을 감싸서 @safe 함수가 배열로 호출할 수 있게 하는 경우가 있어요.

@safe 함수는 수행할 수 있는 작업에 여러 제한이 있으며, 메모리 손상을 일으킬 수 있는 연산을 금지하도록 의도됐어요. 자세한 내용은 @safe 함수를 참고하세요.

@safe 속성은 컴파일러가 함수 본문을 사용할 수 있을 때, 예를 들어 템플릿 같은 경우에 추론될 수 있어요(function attribute inference).

배열 범위 검사(array bounds check)는 메모리 안전을 강제하는 데 필요하기 때문에, @safe 코드에서는 기본적으로(-release 모드에서조차) 활성화돼 있어요.

스코프와 반환 매개변수 (Scope and Return Parameters)

함수 매개변수 속성 returnscope는 함수에 전달되는 저수준 포인터의 행방을 추적하는 데 사용돼요. 그런 포인터에는 원시 포인터, 배열, this, 클래스, ref 매개변수, 델리게이트/지연(lazy) 매개변수, 그리고 포인터를 포함하는 집합체(aggregate)가 포함돼요.

매개변수에 붙는 return ref 속성이 의미하는 바는 다음과 같아요:

  • 매개변수의 주소에서 파생된 포인터가 함수에서 반환될 수 있어요 (예: 매개변수를 참조로 반환).
  • 매개변수의 주소에서 파생된 포인터는, void를 반환하는 함수의 첫 번째 매개변수에 저장될 수 있어요 — 단, 첫 번째 매개변수가 ref일 때만(iff) 그래요. this 참조는 메서드의 첫 번째 매개변수로 간주돼요.

scope는 함수 호출 결과로, 가리키는 객체에 대한 참조가 전역 변수나 함수에 전달된 포인터(그리고 함수 안에서 재귀적으로 호출되는 다른 함수들에도)에 보존되지 않도록 보장해요. 함수 본문과 매개변수 목록에서 scope인 변수는 그 결과 할당이 생략(elided)될 수 있어요.

매개변수에 붙는 return scope 속성이 의미하는 바는 다음과 같아요:

  • 그 매개변수에서 파생된 포인터가 함수에서 반환될 수 있어요.
  • 그 매개변수에서 파생된 포인터는, void를 반환하는 함수의 첫 번째 매개변수에 저장될 수 있어요 — 단, 첫 번째 매개변수가 ref일 때만(iff) 그래요. this 참조는 메서드의 첫 번째 매개변수로 간주돼요.

근거(Rationale): 두 번째 경우는 UFCS, 속성 설정자(property setter), 그리고 비멤버 함수(예: put(dest, source)처럼 쓰이는 put)를 지원하기 위한 거예요.

이 속성들은 메서드의 공식 매개변수 목록 뒤에 나타날 수도 있어요. 그 경우 this 참조에 적용돼요. 생성자의 경우 return은 (암시적으로 반환되는) this 참조에 적용돼요.

return이나 scope는 저수준 포인터가 아닌 타입에 적용되면 무시돼요.

참고: scope 매개변수에 대한 검사는 현재 -dip1000 명령줄 플래그로 컴파일된 @safe 코드에서만 활성화돼요.

한계 (Limitations)

메모리 안전이 코드의 이식성(portability)을 보장하거나, 건전한 프로그래밍 관행만 쓰인다거나, 바이트 순서(byte order) 의존성이 없다거나, 다른 버그가 없다는 뜻은 아니에요. 메모리 안전은 오직 메모리 손상 가능성을 제거하는 데만 집중해요.

더 알아보기 (Learn more)