접근 타입

접근 타입 (포인터) — Access types (pointers)

포인터는 잠재적으로 위험한 구조이며, 이는 Ada의 근본 철학과 충돌해요. Ada가 프로그래머를 포인터의 위험에서 보호하는 데 도움을 주는 방법은 두 가지가 있어요.

하나는 이미 본 대안 기능을 제공하는 것이에요. 매개변수 모드, 배열, 가변 크기 타입은 모두 C의 전형적인 포인터 사용을 대체할 수 있는 구조예요. 둘째로, Ada는 포인터를 가능한 한 안전하고 제한적으로 만들되, 프로그래머가 명시적으로 요청할 때(그리고 적절한 주의를 기울여 그 기능을 사용할 거라고 가정할 때) "탈출구"를 허용해요.

출처: 접근 타입 문서

본문

개요 (Overview)

Ada에서 간단한 포인터 타입, 즉 접근 타입(access type) 을 선언하는 방법:

package Dates is
   type Months is
     (January, February, March, April,
      May, June, July, August, September,
      October, November, December);

   type Date is record
      Day   : Integer range 1 .. 31;
      Month : Months;
      Year  : Integer;
   end record;
end Dates;

with Dates; use Dates;

package Access_Types is
    --  Declare an access type
    type Date_Acc is access Date;
    --                      ^ "Designated type"
    --                      ^ Date_Acc values
    --                        point to Date
    --                        objects

    D : Date_Acc := null;
    --              ^ Literal for
    --                "access to nothing"
    --  ^ Access to date
end Access_Types;

이 예시는 다음을 보여줘요:

  • 값이 특정 타입의 객체를 가리키는("지정하는") 접근 타입을 선언한다.
  • 이 접근 타입에서 변수(접근 값)를 선언한다.
  • null 값을 준다.

Ada의 강한 타입 철학에 따라, 지정 타입이 Date인 두 번째 접근 타입을 선언하면 두 접근 타입은 서로 호환되지 않아요:

with Dates; use Dates;

package Access_Types is
    --  Declare an access type
    type Date_Acc   is access Date;
    type Date_Acc_2 is access Date;

    D  : Date_Acc   := null;
    D2 : Date_Acc_2 := D;
    --                 ^ Invalid! Different types
end Access_Types;

대부분의 다른 언어에서 포인터 타입은 Ada처럼 명목(구조적) 타입이 아니라 구조적으로 타입이 매겨져요. 즉 두 포인터 타입은 같은 대상 타입과 접근성 규칙을 공유하는 한 같아요.

Ada는 그렇지 않아요. 적응하는 데 시간이 걸려요. 겉보기에 단순한 문제가 있어요. 타입에 대한 표준(canonical) 접근을 원한다면 어디에 선언해야 할까요? 흔히 쓰는 패턴은 "소유한" 특정 타입에 대한 접근 타입이 필요하면 그 타입과 함께 선언하는 것이에요:

package Access_Types is
   type Point is record
      X, Y : Natural;
   end record;

   type Point_Access is access Point;
end Access_Types;

(타입에 의한) 할당 (Allocation by type)

접근 타입을 선언했으면 그 타입의 변수에 의미 있는 값을 주는 방법이 필요해요. Ada에서는 new 키워드로 접근 타입의 값을 할당할 수 있어요.

with Dates; use Dates;

package Access_Types is
    type Date_Acc is access Date;

    D : Date_Acc := new Date;
    --              ^ Allocate a new Date record
end Access_Types;

할당하려는 타입에 제약이 필요하면 변수 선언에서 하듯 하위 타입 표시(subtype indication)에 넣을 수 있어요:

with Dates; use Dates;

package Access_Types is
   type String_Acc is access String;
   --                        ^
   --  Access to unconstrained array type
   Msg : String_Acc;
   --    ^ Default value is null

   Buffer : String_Acc :=
     new String (1 .. 10);
   --            ^ Constraint required
end Access_Types;

어떤 경우에는 타입만 지정해 할당하는 게 이상적이지 않을 수 있어요. 그래서 Ada는 할당과 함께 초기화하는 것도 허용해요. 이는 자격 표현식 구문으로 이루어져요:

with Dates; use Dates;

package Access_Types is
   type Date_Acc is access Date;
   type String_Acc is access String;

   D   : Date_Acc   :=
           new Date'(30, November, 2011);
   Msg : String_Acc := new String'("Hello");
end Access_Types;

역참조 (Dereferencing)

Ada 접근 타입 기능의 마지막 중요한 부분은 접근 값에서 가리키는 객체로 가는 방법, 즉 포인터를 역참조하는 방법이에요. Ada에서 포인터 역참조는 .all 구문을 사용하지만 자주 필요하진 않아요. 많은 경우 접근 값은 자동으로 역참조되거든요:

with Dates; use Dates;

package Access_Types is
   type Date_Acc is access Date;

   D     : Date_Acc :=
             new Date'(30, November, 2011);

   Today : Date := D.all;
   --              ^ Access value dereference
   J     : Integer := D.Day;
   --                 ^ Implicit dereference
   --                   for record and array
   --                   components
   --                   Equivalent to D.all.day
end Access_Types;

그 외 기능 (Other features)

C/C++에서 포인터를 사용해 봤다면 알겠지만, 우리는 여전히 포인터 사용의 기초로 여겨지는 기능들이 빠져 있어요. 예를 들어:

  • 포인터 산술 — 다음/이전 객체를 가리키도록 포인터를 증가·감소시키는 것.
  • 수동 해제 — C에서 free 또는 delete라고 하는 것. 이는 잠재적으로 안전하지 않은 연산이에요. 안전한 Ada 영역에 머물려면 절대 수동으로 해제하면 안 돼요.

이 기능들은 Ada에 존재하지만, 특정 표준 라이브러리 API를 통해서만 사용할 수 있어요.

주의: Ada의 지침은 대부분의 경우 수동 할당을 피할 수 있고, 피해야 한다는 것이에요. 수동 할당을 피하는 방법은 많고, 그중 일부는 이미 다뤘어요(매개변수 모드 등). 언어는 포인터를 피할 라이브러리 추상화도 제공해요:

  • 하나는 컨테이너 사용이에요. 컨테이너는 메모리가 자동으로 관리되므로 포인터를 피하는 데 도움을 줘요. 이 맥락에서 주목할 컨테이너는 Indefinite holder예요. 이 컨테이너는 String 같은 무한(indefinite) 타입의 값을 저장할 수 있게 해줘요.
  • GNATCOLL에는 Refcount라는 스마트 포인터용 라이브러리가 있어요. 이 포인터들의 메모리는 자동으로 관리되어, 할당된 객체에 더 이상 참조가 없으면 메모리가 자동으로 해제돼요.

상호 재귀 타입 (Mutually recursive types)

연결 리스트는 자료 구조에서 흔한 관용구예요. Ada에서는 이를 가장 자연스럽게 레코드 타입과 접근 타입의 두 타입으로 정의할 수 있는데, 이 둘은 서로 의존적이에요. 서로 의존하는 타입을 선언하려면 불완전 타입 선언(incomplete type declaration)을 사용할 수 있어요:

package Simple_List is
   type Node;
   --  This is an incomplete type declaration,
   --  which is completed in the same
   --  declarative region.

   type Node_Acc is access Node;

   type Node is record
      Content    : Natural;
      Prev, Next : Node_Acc;
   end record;
end Simple_List;

이 예시에서 NodeNode_Acc 타입은 서로 의존적이에요.

더 알아보기 (Learn more)