구조체를 함수의 매개변수로 사용하기.

구조체를 함수의 인수로 전달할 수 있다는 것은 굉장히 편리한 기능이다. 마치 정수나 실수를 사용하듯이 변수 자체를 그대로 전달할 수 있기 때문이다. 그러나 실제로 구조체를 함수의 인수로 직접 사용하는 경우는 별로 없다. 구조체가 커지면 인수 전달에 그만큼 많은 시간을 필요로 하고 메모리도 많이 소모하기 때문에 구조체보다는 포인터를 사용하는 방법이 더 효율적이다. 이럴 때는 당연히 포인터를 사용해야 한다. 다음과 같이 수정해 보자.

 

void OutFriend(tag_Friend *pf)     //당연히 포인터로 넘기는것이 좋다

{

     printf("이름=%s, 나이=%d, 키=%.1f\n",pf->Name,pf->Age,pf->Height);

}

 

void main()

{

     tag_Friend Friend={"김상형", 30, 180.0 };

     OutFriend(&Friend);

}

 

OutFriend 함수가 tag_Friend *형의 pf를 전달받도록 했으며 함수 내부에서는 멤버 연산자 대신 포인터 멤버 연산자를 사용했다. main 함수에서 OutFriend를 호출할 때는 구조체 자체를 전달하지 않고 대신 구조체의 번지 &Friend를 전달했다. 구조체 자체를 전달하느냐 아니면 구조체를 가리키는 포인터를 전달하여 간접적으로 구조체를 참조하도록 하느냐의 차이가 있는데 실행 결과는 일단 동일하다.

하지만 몇 가지 차이점이 존재하는데 우선 포인터를 통해 참조 호출을 했으므로 함수 내부에서 구조체를 변경할 수 있다. 형식 인수가 실인수의 사본이 아니라 번지를 알고 있으므로 -> 연산자로 실인수 자체를 읽고 쓸 수 있는 것이다. 그리고 성능상으로도 확연한 차이가 있는데 두말할 필요없이 포인터를 전달하는 방식이 훨씬 더 빠르다. 구조체는 보통 수십 바이트이고 커지면 수백 바이트 이상이 될 수 있지만 포인터는 기껏해야 4바이트밖에 안된다.

구조체를 통째로 복사하여 전달하는 데 걸리는 시간과 4바이트의 포인터를 전달하는 데 걸리는 시간은 비교해 보나 마나다. 그래서 구조체를 함수끼리 전달해야 할 필요가 있을 때는 보통 포인터를 사용한다. 단, 구조체가 아주 작다면 가령 10바이트 정도밖에 안된다면 이런 경우는 성능상의 불이익이 별로 없으므로 구조체를 그냥 넘기는 것이 더 편리하다.

구조체가 인수로 사용될 수 있는 것처럼 리턴값으로도 사용될 수 있다. 다음 예제는 구조체를 리턴하는 함수 GetFriend의 예이다.

 

  : StructRet

#include <Turboc.h>

 

struct tag_Friend {

     char Name[10];

     int Age;

     double Height;

};

 

tag_Friend GetFriend()

{

     tag_Friend t;

 

     strcpy(t.Name,"아무개");

     t.Age=22;

     t.Height=177.7;

     return t;

}

 

void main()

{

     tag_Friend Friend;

     Friend=GetFriend();

     printf("이름=%s, 나이=%d, 키=%.1f\n",

          Friend.Name,Friend.Age,Friend.Height);

}

 

함수 내부에서 tag_Friend형의 구조체 지역변수 t를 선언한 후 이 구조체에 적당히 값을 채우고 지역변수 자체를 리턴했다. 지역변수는 함수가 종료될 때 사라지므로 이 변수를 리턴하는 것이 조금 이상하게 보이겠지만 이 경우는 안전하다. 왜냐하면 리턴되는 값은 지역변수 자체가 아니라 지역변수의 복사본이며 리턴되는 즉시 이 값을 다른 구조체가 대입받기 때문이다. 만약 대입을 받지 않으면 리턴된 구조체는 버려진다.

하지만 구조체 지역변수의 포인터를 리턴하는 것은 안된다. 다음과 같이 지역변수의 포인터를 리턴하도록 예제를 수정해 보자.

 

tag_Friend *GetFriend()

{

     tag_Friend t;

 

     strcpy(t.Name,"아무개");

     t.Age=22;

     t.Height=177.7;

     return &t;

}

 

void main()

{

     tag_Friend *pFriend;

     pFriend=GetFriend();

     printf("이름=%s, 나이=%d, 키=%.1f\n",

          pFriend->Name,pFriend->Age,pFriend->Height);

}

 

이 예제를 컴파일하면 곧 사라질 지역변수의 번지를 리턴했다는 경고가 발생할 뿐만 아니라 제대로 동작하지도 않는다. GetFriend 함수는 지역변수 t의 멤버에 값을 대입한 후 그 포인터를 리턴하며 main에서는 이 포인터를 pFriend로 대입받았다. 여기까지만 보면 pFriend는 GetFriend 함수가 초기화해 놓은 t구조체의 번지를 가지고 있으며 이 번지에는 과연 구조체의 정보가 들어 있기도 하다.

그러나 이 값을 출력하려고 printf를 호출하는 순간 이 번지의 내용이 파괴되어 버리는데 printf 호출을 위해 스택에 저장된 값이 파괴되기 때문이다. pFriend 포인터가 가리키고 있는 스택상의 번지는 리턴 직후에만 유효하며 다른 함수를 호출하는 즉시 파괴되는 성질을 가지고 있다. 그래서 지역변수로 선언된 구조체(다른 변수도 마찬가지이다)의 번지를 리턴하는 것은 옳지 않다.

값은 임시 사본이 리턴되므로 상관없지만 포인터는 간접적으로 대상체를 참조하므로 대상체가 사라지면 무효해진다. 만약 GetFriend 함수에서 지역변수 t가 아닌 malloc이나 new로 동적 할당한 구조체의 번지를 리턴한다면 이 경우는 가능하다. 동적으로 할당된 메모리는 일부러 파괴하지 않는 한 그 내용을 계속 보존하기 때문이다.

이 글을 공유하기

댓글

Designed by JB FACTORY