Q&A

  • array 와 pointer
array는 memory allocation한 pointer와 같다. C에서 하던 이야기죠...

pointer를 array처럼, array를 포인터처럼 사용할 수 있었죠. 파스칼도 되겠죠?



ex_1)



var

test : PChar;

begin

test := AllocMem(sizeof(Char)*10);

....

end;



ex_2)



var

test : array[0..9] of Char;



위와 같을 때 ex1과 ex2의 test는 똑같은 방법으로 사용할 수 있진 못한

것같아요. ex1의경우 @test[sizeof(char)] 는 test + sizeof(char) 와 같겠죠? 이것들이 ex2에서 @test[1] 와는? pointer형일때는 변수뒤의 대괄호 [] 안에

byte offset이 오는 것같습니다.그러니 array처럼 사용할 수는 없는 거겠죠.

게다가 pascal의 array는 array index가 C처럼 항상 0 부터 시작하는 것이

아니기때문에 pointer를 array처럼 사용한다는 것이 .....

pointer의 주소를 array 변수 첫번째가 가르키게 해서 pointer를 array처럼

사용할 수는 있을까요? 하지만 제가 바라는 것은 pointer 변수자체를 array처럼

또 array변수 자체를 pointer처럼 사용하는 것인데요.. 관계해서 아무 말슴이나

좀 해주세요.





2  COMMENTS
  • Profile
    구창민 1999.08.31 02:10
    안녕하세요?

    파스칼 언어는 C못지 않은 문법체계를 구축하고 있습니다.

    C언어에 익숙해져 있다가 적응하는 시간이 조금 필요하긴 하지만,

    아래의 내용들을 숙독해 보세요.

    저도 일전에 C로 작성된 프로젝트를 파스칼로 포팅하는데

    많은 도움을 준 김영준님이 쓰신 강좌입니다.

    내용이 상당히 긴데.. 제가 전부 찾아서 올린건지 모르겠네요..

    보시고, 도움되시길 바랍니다.

    그럼..



    제 목 : [강좌] 포인터 #1



    ### 포인터 ###

    첫번째



    포인터형을 이용한 자료 구조를 흔히 동적 자료 구조하고 하는데, 이것은

    정적인 자료 구조인 배열이나 레코드 등의 자료형의 크기가 컴파일 시에 이미

    결정되어 할당되는데 비하여 포인터를 이용한 자료형은 프로그램의 수행 중에

    크기를 바꿀 수 있기 때문에 붙여진 이름이다. 동적 자료 구조는 흔히 고급

    프로그래밍 언어를 배우면서 자칫 넘지 못하는 장애물이다. 여기서는 포인터

    의 기본적인 개념과 간단한 응용 사례를 다룬다.



    ========================================================================



    1. 포인터의 개념



    1) 왜 동적인 자료 구조를 이용해야 하는가?



    왜 동적인 자료 구조를 이용해야 하는가? 이에 대한 대답을 얻기 위해 리스

    서에 따라 늘어 놓은 것이다. 따라서 리스트는 다음과 같이 하나의 배열로 표

    현할 수 있을 것이다.



    var

    List : array[1..ListSize] of SomeObject;



    하지만 이러한 선언은 몇가지 문제점이 있다. 먼저 우리는 프로그램을 작성

    하기 전에 ListSize를 결정하여야 하는데, 리스트에 들어갈 목적물의 갯수가

    고정되어 있다면 상관이 없지만 그렇지 않고, 수시로 변한다면 어떻게 해야

    할까? 적당히 어림해서 정할 수도 있겠지만 만에 하나라도 선언된 갯수보다

    많은 목적물이 들어갈 경우에는 프로그램은 수행 에러를 내게 된다.

    그렇다면, ListSize를 예상되는 목적물의 갯수보다 훨씬 크게, 예를 들어

    한 1000 개 정도로 잡으면 이러한 문제를 해결할 수 있을 것 같다. 하지만 우

    리는 한정된 기억 용량을 가지고 있기 때문에 리스트의 크기를 너무 크게 잡

    을 경우 프로그램은 비효율적이 되고 만다.



    또한 프로그램의 수행 중에 배열에 들어 있는 목적물 중 하나를 지웠다고

    해 보자. 이 때 배열의 중간에 빈 공간이 생기는데, 배열은 연속적으로 기억

    되어야 하므로 우리는 일일이 모든 원소를 움직여 이 자리를 채워야 한다. 반

    대로 새로운 목적물을 하나 삽입한다면 마찬가지로 그 뒤에 있는 목적물을 모

    두 뒤로 밀어내야 한다. 이러한 작업은 프로그램의 수행 속도를 크게 떨어뜨

    리게 될 것이다. 이러한 문제들을 해결하기 위래 우리는 동적인 자료 구조인

    포인터형을 배울 필요가 있다.



    '동적 자료 구조(dynamic data sructure)'란 '정적 자료 구조 (static data

    structure)'에 대응하는 말이다. 정적 자료 구조는 컴파일시에 그 크기와 그

    값이 저장될 곳이 결정되는데 비하여 동적 자료 구조는 프로그램 수행 도중에

    그 크기와 저장되는 위치가 변경될 수 있다는 데서 붙여진 이름이다.



    2) 포인터 변수의 선언과 이용



    포인터의 선언은 따로 정의된 자료형의 이름이 있는 것이 아니라 그 포인터

    가 가리키게 될 자료형의 이름 앞에 꺽쇠(^) 표시를 해서 나타낸다. 예를 들

    어 정수형 자료를 가리키는 포인터는 다음과 같이 선언할 수 있다.



    type

    IntPtr = ^Integer;



    이제, IntPtr은 정수 하나를 가리키는 포인터 형이다. 물론 IntPtr 형의 변

    수를 선언할 수도 있다.



    var

    aPtr : IntPtr;



    이제 aPtr은 정수에 대한 포인터형으로 선언되었다. 이것을 다음과 같은 그

    림으로 표시할 수 있다.



    aPtr

    ???? ????

    ? ? ?????? ? ?

    ???? ????



    위 그림에서 aPtr이란 변수는 다른 어떤 목적물(여기서는 Integer 형)을 가

    리킨다는 사실을 나타내고 있다. 그렇다면 aPtr이 가지는 값은 어떤 것일까?

    우리는 포인터 변수가 가지는 실제 값에 대해서는 알 필요가 없다. 그것은

    단지 어떤 값 - 여기서는 정수값 - 을 가리키는데 쓰일 수 있는 값 - 실제로

    는메모리의 주소 값 - 이다. 그 어떤 값, 즉 포인터가 가리키는 값을 나타내

    고 싶으면 포인터 변수의 이름 뒤에 꺽쇠(^) 표시를 하면 된다. 즉,



    aPtr^



    은 정수형 포인터 변수인 aPtr 가리키는, Integer 형의 어떤 목적물을 나타

    낸다. 포인터 형은 Integer와 같은 단순형에대해서만 정의할 수 있는 것이

    아니고 배열이나 레코드, 문자열 등 어떤 자료형도 가리킬 수 있다. 사실 정

    수나 실수 하나에 대한 포인터는 별로 쓸모가 없고 대개 레코드나 배열에 대

    한 포인터가 많이 사용된다. 예를 들어



    type

    CharArray = array[1..100] of Char;

    CharArrPtr = ^CharArray;

    Person = record

    Name : String[10];

    Age : Integer;

    end;

    PersonPtr = ^Person;

    var

    p : CharArrPtr;

    q : PersionPtr;



    이 경우 p^ 는 CharArray 형, q^ 는 Person 형이 되므로 다음과 같은 변수

    참조가 가능하다.



    p^[i] p^ (Char 형 배열) 의 i 번째 원소

    q^.Age q^ (Person 형 레코드) 의 Age 필드



    한편 어떤 경우에는 포인터가 아무 것도 가리키지 않음을 나타낼 수도 있

    다. 파스칼에서는 포인터가 아무 것도 가리키지 않음을 나타내기 위해 nil 이

    란 예약어를 준비하고 있다. 즉 포인터의 값이 nil 이면 그것은 아무 것도 가

    리키지 않는 포인터이다.



    aPtr := nil;



    이것을 그림으로는 다음과 같이 나타낸다.



    aPtr

    ????

    ? ? ?????? ?

    ????



    포인터가 아무 것도 가리키지 않는다면 그것이 '가리키는 것' 이란 말을 쓸

    수가 없다. 따라서 위와 같이 aPtr 이 nil 일 때 aPtr^ 이란 표현은 무의미

    하다.





    3) Pointer 형



    앞에서 설명한 포인터형은 어떤 기본형을 가리키는 형으로 정의되었다. 예

    를 들어 Integer 에 대한 포인터와 Real 에 대한 포인터는 서로 다르므로 이

    들 사이에는 호환성이 없다. 예를 들어



    var

    p :^Integer;

    q : ^Real;

    begin

    p := q; (* 컴파일 에러 *)



    터보 파스칼에서는 이런 문제점을 해결하기 위해 어떤 형의 포인터와도 호

    환인 Pointer 라는 특별한 형을 준비해 놓고 있다.



    var

    p : Pointer;



    p 가 Pointer 형으로 선언되면 이 포인터는 어떤 포인터 변수와도 호환이므

    로 어디에나 마구 쓸 수 있다. 특히 이 Pointer 형이 유용한 경우는 포인터

    변수를 가지고 무슨 일을 하는 프로시져나 함수의 형식 인자를 선언할 때이

    다.



    procedure Something(var p: Pointer);

    ...



    이런 식으로 선언해 놓으면 Something 은 모든 종류의 포인터 변수를 모두

    받아들일 수 있으므로 편리하다.

    Pointer 형 변수가 가리키는 동적 변수도 p^ 와 같은 형식으로 나타낼 수

    있다. 그런데, Pointer 형은 포인터가 가리키는 기본형이 정해진 것이 아니고

    아무거나 마구 가리킬 수 있기 때문에 p^ 의 형이 정확히 무엇이라고 말할 수

    없다. 이 때문에 p^ 을 가지고 무슨 일을 하려면 항상 형 변환(type casting)

    을 해 주어야만 한다. 예를 들어 n 이 정수형 변수일 때



    n := Integer(p^) + 1;



    는 p^ 가 가리키는 값을 정수로 해석하여 거기에 1 을 더한 값을 n 에 저장

    하라는 뜻이다. 그러나 이 경우 p^ 에 들어 있는 값이 정수라는 것을 확실히

    하여야 한다. 그것은 프로그래머의 책임이다.



    o. Pointer 형 변수는 단지 메모리의 어디인가에 있는 목적물을 가르키기

    만 할 뿐이지 그 목적물이 어떤 형의 데이터인지는 모른다. 그 목적물

    의 데이터형을 결정하는 일은 프로그래머의 책임이다.







    ### 포인터 ###

    두번째



    ========================================================================



    2. 기억 장소의 할당과 해제



    1) New 와 Dispose



    첫번째에서는 포인터 변수를 선언하기만 하고 그것이 가리키는 것에 대해서

    는 따로 언급하지 않았다. 그래서 포인터 변수를 선언하면 자동으로 그것이

    가리키는 것도 같이 생긴다고 생각하는 사람들이 있는데, 절대로 그렇지 않

    다. 첫번째에서 p 를 선언하고 곧장p^ 를 썼지만, 실제로 그렇게 하면 십중

    팔구 시스템이 폭주할 것이다.

    아까도 말했지만 포인터 변수는 다른 어떤 곳을 가리키는 값을 가진다. 포

    인터 변수를 처음 선언했을 때 그 값이 무엇인지 알 수 없다. 언뜻 생각하면

    처음 선언했을 때 nil 이 될 것 같지만, 실제로는 어떤 값을 가지고 있으나

    이보다 더욱 중요한 것은, 실제로 p 가 가리키는 것 (p^) 이 존재하지 않는

    다는 점이다. 우리가 포인터 변수를 선언한다는 것은 다른 어떤 것을 가리키

    는 그 값 (p 의 내용) 을 담을 수 있는 기억장소를 만드는 것이지, 그 포인터

    가 가리키는 것 (p^ 의 내용) 에 해당하는 기억장소를 선언하는 것은 아니다.

    예를 들어



    var

    p : ^Integer;



    라는 선언으로 생기는 것은 p 뿐이다. 이렇게 포인터만 있고 그것이 가리키

    는 기억장소가 없는 상황을 그림으로 나타내면 다음과 같다.



    p

    ????

    ? ? ?????? ?

    ????



    우리가 var 선언으로 만드는 정적 변수는 프로그램이 시작되기 전에 컴파일

    단계에서 기억장소를 다 할당받지만, p^ 와 같이 포인터를 통해 사용되는 동

    적변수는 프로그램이 시작되어 수행 중에 기억장소를 할당받아야 한다. 동적

    변수에 기억장소를 할당하려면 표준 프로시져인 New 를 사용하여 한다.



    ????????????????????????????

    ? procedure New ( var p : Pointer ); ?

    ????????????????????????????



    New 프로시져는 인자로 들어온 포인터 변수에 대응되는 동적 변수를 하나

    만들고 포인터가 그 동적 변수를 가리키도록 해 준다. 따라서 포인터를 선언

    한 다음 그 포인터를 가지고 무슨작업을 하려면 항상 New 프로시져로 그 포

    인터가 가리킬 동적 변수를 만들어 주어야 한다. (항상 그런 것은 아니다. 만

    약 메모리의 특정위치를 사용할 경우는 New 를 사용해서는 안된다.)

    New 에 의해 만들어지는 동적 변수의 형은 p 의 기본형에 따른다. 예를 들





    var

    p : ^Integer;

    q : ^String[20];

    begin

    New(p);

    New(q);

    p^ := 100;

    q^ := 'Turbo Pascal';

    :



    프로그램의 수행 결과 p^ 는 2 바이트 크기의 Integer 형이 되지만, q^ 는

    21 바이트 크기의 String[20] 형이 된다.



    동적 변수는 만들 수 있을 뿐만 아니라 없앨 수도 있다. 표준 프로시져인

    Dispose 는 인자로 들어온 포인터 변수가 가리키는 동적변수를 없앤다.



    ????????????????????????????

    ? procedure Dispose ( var p : Pointer ); ?

    ????????????????????????????



    Dispose 가 수행된 다음 p 의 값은 어떤 값인지 알 수 없으며, p^ 은 기억

    장소가 없어진 상태이므로 사용할 수 없다. 만약 p^ 를 사용하고자 한다면 다

    시 New 를 사용하여 동적변수를 다시 할당받아야만 한다.



    2) GetMem 과 FreeMem



    앞에서 본 New 와 Dispose 는 인자로 들어온 포인터 변수의 기본형에 따라

    서 일을 처리했다. 하지만 첫번째에서도 말했듯이 터보 파스칼에는 기본형이

    없는 Pointer 형이 있는데, 이 Pointer 형을 가지고 기억장소를 할당받으려면

    대체 얼마만큼을 주어야 할지를 알 수 없다. 따라서 Pointer 형의 포인터가

    가리키는 동적변수를 다루기 위해서 GetMem 과 FreeMem 이란 프로시져가 준비

    되어 있다.



    ?????????????????????????????

    ? procedure GetMem ( var p : Pointer; Size : Word ); ?

    ? procedure FreeMem ( var p : Pointer; Size : Word ); ?

    ?????????????????????????????



    먼저 GetMem 은 Size 바이트 크기인 동적 변수를 만들어서 그것을 Pointer

    형의 인자 p 가 가리키도록 한다. 주의할 것은 변수의 크기를 프로그래머가

    지정한다는 점이다. Size 는 Word 형인데, 지정할 수 있는 최대 크기는 65520

    이다.

    FreeMem 은 반대로 p 가 가리키는 동적 변수를 없앤다. 그런데 여기서도

    Size를 지정하는데, 그것은 p^ 의 크기와 똑같아야 한다. 만일 숫자가 틀리면

    그만큼의 기억장소를 사용할 수 없게 되거나, 아니면 동적 변수 영역이 엉망이

    되어 버린다.

    GetMem 의 사용법을 익히기 위해 다음 예제를 보자.



    program TesetGetMem;

    var

    s : String;

    p : Pointer;

    n : Word;

    begin

    Write('Type string : ');

    ReadLn(s);

    n := Length(s) + 1; { 'ABCDE' = 5 + 1 = 6 }

    GetMem(p, n);

    Move(s, p^, n);

    WriteLn('p^ = ', String(p^));

    WriteLn('Size of p^ = ', n);

    FreeMem(p, n);

    end. { of main }



    이 프로그램은 우선 문자열 s 를 읽어 들이고, 그 문자열의 실제 길이만큼

    의 - 실제 문자열 변수의 길이 = 문자열의 길이를 나타내는 1 바이트 + 실제

    문자열의 길이 - 동적 변수를 만든 다음 거기에 문자열의 내용을 복사한다.

    그 다음 p^ 의 내용을 문자열로 형 변환하여 출력하고 나서 그 동적 변수를

    없앤다. 이 방법은 실제로 문자열에 저장된 문자들의 갯수만큼만 기억장소를

    차지하므로 기억장소를 절약할 수 있다.

    여기서 p^ 의 내용을 형변환 하는 것은 p 가 실제로 어떤 형이 정해진 포인

    터 변수가 아니기 때문에 p^ 의 내용이 어떤 형인지 알 수가 없다. 때문에 p^

    를 사용하기 위해서는 반드시 형변환을 해 주어야만 한다. 만약 실제 p^ 의

    크기보다 형변환을 거친 p^ 의 크기가 크게 되면 동적변수의 영역은 엉망이

    되거나 시스템이 폭주하게 될 것이다..







    ### 포인터 ###

    세번째



    ========================================================================



    3. 포인터 변수에 대한 연산



    포인터는 다른 변수를 가리키는 값이기 때문에, 포인터에 대한 연산은 일반

    자료에 대한 연산과는 상당히 다르다. 이것을 이해하지 못하면 포인터를 이용

    하는 프로그램을 짤 수 없을 뿐더러 허투루 짰다가는 프로그램이 폭주하기 십

    상이다. 이 절에서는 포인터를 이용한 연산의 기본 개념을 잡아 보기로 한자.

    포인터를 이용할 때는 항상 포인터가 가리키는 값과 포인터 그 자체를 구분

    하는 것이 매우 중요하다. 예를 들어



    var

    p, q : ^Integer;



    을 생각해 보자.



    New(p);

    New(q);

    p^ := 10;

    q^ := 20;



    위 문장은 먼저 p 와 q 에 기억장소를 할당한다. 그런 다음 p 가 가리키는

    것 (p^) 에 10 을 지정하고, q 가 가리키는 것 (q^) 에 20 을 지정한다. 따라

    서 이 문장들이 수행된 다음에 p 와 q 는 다음 <그림 - 1> 과 같이 될 것이다.



    p

    ???? ????

    ? ? ?????? ? 10 ?

    ???? ????



    q

    ???? ????

    ? ? ?????? ? 20 ?

    ???? ????



    < 그림 - 1 >



    이제 이 상황에서 다음 문장을 수행한다고 생각해 보자.



    p^ := q^;



    이 문장의 의미는 무엇일까? p 가 가리키는 것에 q 가 가리키는 것을 지정

    한다. 다시 말해, p^ 에 p^ 의 값을 집어 넣는다. 따라서 이 문장을 수행한

    다음에는 다음 <그림 - 2> 과 같은 상황이 된다.



    p

    ???? ????

    ? ? ?????? ? 20 ?

    ???? ????



    q

    ???? ????

    ? ? ?????? ? 20 ?

    ???? ????



    < 그림 - 2 >



    이것은 p^ 와 q^ 를 보통의 변수라고 생각하는 것과 다름이 없다. 그러나,

    이번에는 좀 다른 경우를 보자. <그림 - 1> 과 같은 상황에서 다음 문장을 수

    행한다면



    p := q;



    이 문장은 q 의 값을 p 에 지정하는 것이다. 이렇게 하면 q 의 값과 p 의

    값이 같아진다. 그런데, 포인터 변수의 값은 어떤 다른 것을 가리키는 값이기

    때문에 p 와 q 가 같다는 것은 p 와 q 가 똑같은 것을 가리킨다는 말과 동일

    하다. 따라서 이 문장을 수행한 다음에는 <그림 - 3> 과 같이 p 와 q 가 같은

    곳을 가리키게 된다.



    p

    ???? ????

    ? ? ???? ? 10 ?

    ???? ? ????

    ?

    q ?

    ???? ??? ????

    ? ? ?????? ? 20 ?

    ???? ????



    < 그림 - 3 >



    이와 같이 포인터에 대한 연산과 그 포인터가 가리키는 것에 대한 연산은

    엄청난 차이가 있다. 앞으로 나올 포인터에 관한 모든 설명에서 이 원리를 이

    해하지 못하면 고생깨나 해야 할 것이므로 잘 봐 두기 바란다. 마지막으로,



    p := q^;



    이 문장은 지정문의 양쪽에 있는 변수의 형이 같이 않으므로 컴파일 에러가

    난다. (p 는 Integer 형에 대한 포인터이고, q^ 는 Integer 형 그 자체이다.)



    한편 기억장소를 할당하기 위해 New 와 Dispose 를 이용할 때는 주의할 점

    이 몇가지 있다. 앞의 <그림 - 1> 에서는 p 와 q 가 어떤 동적 변수를 가리키

    고 있다. 그런데 이 상황에서



    p := q;



    라는 문장을 수행했기 때문에 <그림 - 3> 과 같은 상황이 된다. 이 상태에

    서 Dispose(p) 를 수행하면 다음 <그림 - 4> 과 같은 상태가 된다.



    p

    ???? ????

    ? ? ???? ? 10 ?

    ???? ? ????

    ?

    q ?

    ???? ???

    ? ? ?????? ?

    ????



    < 그림 - 4>



    이 때 p 와 q 가 같은 곳을 가리키므로 원래 p 가 가리키던 것, 즉 현재 10

    이 들어 있는 동적변수는 어떤 포인터도 가리키지 않게 된다. 따라서 이 동적

    변수를 프로그램 내에서 사용할 방법이 전혀 없다. 물론 이 동적변수를 지울

    수도 없다.

    이렇게 프로그램 내에서 사용되지도 않으면서 기억장소만 차지하고 있는 동

    적 변수를 우리는 쓰레기(garbage)라고 부른다. 쓰레기가 생기면 기억장소가

    낭비되기 때문에, 어떤 동적변수를 다 사용할 다음에는 항상 Dispose 를 통해

    지워 주어야 한다.



    이번에는 또 다른 문제점을 보자. <그림 - 3> 에서 보면 p 와 q 가 같은 기

    억장소를 가리키고 있다. 그런데 이 상태에서



    Dispose(p);



    를 하면 p 가 가리키는 동적 변수를 지워 버리므로 <그림 - 4> 와 같은 상

    황이 된다.

    이 때 우리는 Dispose(p) 만 수행했지만 p 와 q 가 같은 기억장소를 가리키

    고 있으므로 q 가 가리키는 동적 변수 역시 없어진다. 따라서 이 다음에 q^

    를 사용하면 엉뚱한 결과가 나온다. 이 경우 q 는 무엇인가를 가리키고 있지

    만 그에 해당하는 동적 변수가 없으므로 이런 것을 허공에 떴다 (dangling)

    고 한다. 허공에 뜬 포인터 변수를 잘못 쓰면 시스템이 폭주하는 경우가 있으

    므로 항상 주의하여야 한다.

  • Profile
    Lyle 1999.08.31 03:31
    구창민 께서 말씀하시기를...

    > 안녕하세요?

    > 파스칼 언어는 C못지 않은 문법체계를 구축하고 있습니다.

    > C언어에 익숙해져 있다가 적응하는 시간이 조금 필요하긴 하지만,

    > 아래의 내용들을 숙독해 보세요.

    > 저도 일전에 C로 작성된 프로젝트를 파스칼로 포팅하는데

    > 많은 도움을 준 김영준님이 쓰신 강좌입니다.

    > 내용이 상당히 긴데.. 제가 전부 찾아서 올린건지 모르겠네요..

    > 보시고, 도움되시길 바랍니다.

    > 그럼..

    >

    감사합니다. 하지만 이미 보았던 내용이에요. 그리고 그내용 안에서는 array를

    pointer처럼 쓸 수 있는 내용에대해선 나오질 않아서...



    델파이4에는 동적 배열이 있고, 또 그게 아니어도 다음처럼 pointer를 배열처럼

    쓸 수가 있죠.



    type

    aChar : array[0..0] of Char;

    PaChar : ^test;

    var

    test : PaChar;

    begin

    test := AllocMem(sizeof(Char)*10);

    .....

    end;



    위와같이하고서 test[0], test[1], test[2] 등을 쓸 수는 있네요. 하지만 이건

    'array는 memory allocation한 pointer다'와는 좀 다르죠? 델파이4부터 동적

    배열도 있기때문에 pointer와 큰차이없이 쓸 수는 없지만 여전히 좀 부족한

    감이 있습니다. 뭔가 더 알고나면 해결될 답답함이었으면 좋겠네요.