본문으로 바로가기

파일의 IT 블로그

  1. Home
  2. 프로그래밍 강좌/C
  3. [C언어 강좌] #14-2 구조체(Structure Type)

[C언어 강좌] #14-2 구조체(Structure Type)

· 댓글개 · KRFile

포인터편에서 함수를 호출할때 두가지 방법인 값에 의한 호출(Call by Value), 주소에 의한 호출(Call by Reference) 를 배웠었습니다. 일반 변수의 값, 주소를 매개변수로 넘길 수 있었듯 구조체의 경우에도 구조체 변수의 값, 주소를 매개변수로 넘길 수 있습니다.

 

그리고 배열의 경우에는 배열을 매개변수로 넘길때 배열의 이름 자체가 주소고, 매개변수로 int a[]와 같이 써줘도 컴파일러에서 int * a와 같은 포인터로 자동변환하기 때문에 어떻게하든 주소에 의한 호출만 가능했었는데요.

 

과연 구조체는 여기서 어떻게 될까요?

한번 알아보겠습니다.

 

구조체 변수를 이용한 값에 의한 호출

#include <stdio.h>

struct pos
{
	int x;
	int y;
};

void CallbyValue(struct pos call){ //call구조체 변수에 p구조체 변수의 값만을 복사 (Call by Value) 
	printf("[call] %d %d\n", call.x, call.y);
	
	//복사본의 값을 변경 
	call.x = 2;
	call.y = 3;
}
int main(){
	struct pos p = {1,2};
	CallbyValue(p); 
	
	printf("[p] %d %d\n", p.x, p.y); //1,2 출력 
	return 0;
}

[call] 1 2
[p] 1 2

--------------------------------
Process exited after 0.008 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .

값에 의한 호출이라는 말이 있듯이

기본적으로 구조체 변수를 매개변수로 넘기게 되면 원본 주소를 넘기는것이 아닌

값을 복사하는 형태로 진행을 합니다.

 

인자값으로 구조체 변수를 넘기니 복사받을 매개변수 또한 구조체 변수여야 합니다.

 

예제가 좀 길어졌는데 구조체 변수를 값에 의한 호출로 진행하는 것을 잘 보여주고 있습니다.

 

보시면 CallbyValue(p); 로 함수에 구조체 변수 p를 넘기고 있습니다.

그것을 CallbyValue() 함수의 매개변수, 구조체 변수인 call에 구조체 변수 p의 값만을 그대로 복사합니다.

 

그리고 출력을 한다음에

복사본(=call)의 멤버 변수 값을 각각 2,3으로 변경을 했습니다.

그리고 p의 값을 출력해보았으나 2,3으로 변경되지 않고

1,2가 출력되는 모습입니다.

 

이것은 당연하게도 값에 의한 호출이기 때문입니다. 원본(주소)을 넘긴것이 아닌 복사본을 만들었기 때문에 복사본(call)의 값을 바꾼다고 해서 p(원본)의 값이 변경되지 않습니다.


구조체에서 값에 의한 호출을 할때 유의할 점이 있는데, 구조체의 경우에는 사용자 정의 자료형으로 얼마든지 사용자가 멤버 변수만 추가하면 메모리 크기를 키울 수 있습니다.

(구조체의 메모리 크기 계산법은 앞의 1편에서 이미 알려드렸습니다.)

 

값에 의한 호출을 하면 구조체의 복사본이 계속 늘어나서 메모리(스택영역)를 많이 잡아먹을 수 있습니다.

이 문제 어디서 많이 본 상황 아닌가요?

바로 배열이였습니다.

 

배열의 경우에는 연속된 메모리 공간으로, 매개 변수로 넘길때 메모리 효율성을 위해 복사하지 않고 원본 주소로 넘기는게 거의 강제되어 있습니다. (주소에 의한 호출)

 

구조체 변수의 경우에는 값에 의한 호출, 주소에 의한 호출 둘다가 가능하기 때문에

메모리 효율성이 걱정되면 배열과 마찬가지로 주소에 의한 호출을 하면 됩니다.

 

 

구조체 변수를 이용한 주소에 의한 호출

#include <stdio.h>

struct pos
{
	int x;
	int y;
};

void CallbyReference(struct pos * call){ //call구조체 변수에 p구조체 변수의 주소를 넘긴다. (Call By Reference) 
	printf("[call] %d %d\n", call->x, call->y); //(*call).x 로 접근해도 무관함 
	
	//가리키는 p의 값을 변경 
	call->x = 2;
	call->y = 3;
}
int main(){
	struct pos p = {1,2};
	CallbyReference(&p); //구조체 변수의 주소를 넘긴다. 
	
	printf("[p] %d %d\n", p.x, p.y); //2,3 출력 (원본 변경) 
	return 0;
}

[call] 1 2
[p] 2 3

--------------------------------
Process exited after 0.007642 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .

해당 예제는 구조체 변수의 주소를 넘기는, 주소에 의한 호출 예제입니다.

보시면 아까 값에 의한 호출과 다르게 p의 주소 &p를 매개 변수로 넘기고 있습니다.

 

주소를 넘기고 있으므로 받는쪽은 반드시 포인터여야 합니다.

그래서 struct pos * call 로 구조체 포인터 변수로 call을 선언해주었습니다.

 

구조체 포인터 변수가 p의 주소를 받아서

call = &p, 즉 p의 주소를 포인팅하고 있는 형태이므로

출력을 할때 call->x 또는 (*call).x로 출력을 해야 p.x 를 출력한것과 동일한 형태가 됩니다.

 

원본 주소를 받았기 때문에 call->x 와 call->y에 값을 2,3으로 대입해주고 나중에 main 함수로 돌아올때 p의 값을 출력해보았는데 아까와는 다르게 2,3으로 원본의 값이 변경된 상태입니다.

 

주소를 넘기기 때문에 구조체 메모리 효율성에 대해 걱정하지 않아도 되지만

이처럼 원본이 수정이 가능하기 때문에 원본 손실의 가능성이 있으니 이것 역시 주의하시길 바랍니다.

 

구조체 변수는 값에 의한 호출, 주소에 의한 호출 둘다 가능하니 필요에 따라 이용해주시면 되겠습니다.

 

함수 반환시 구조체 변수 반환하기

함수로 값을 반환할때 구조체 변수 역시 반환 가능합니다.

구조체 변수의 값을 반환할 수도 있고 주소를 반환할 수도 있습니다.

일반변수와 마찬가지로요.

 

방법도 일반변수랑 동일하게 해주시면 됩니다.

만약에 int형 값을 반환하는 함수가 있고 이름이 func라면

 

int func(){
  //코드 실행
  return 0; //정수형 반환
}

 

이런식으로 써주시면 되고,

만약에 char이라면 char func()로 써주시고 return 'a'와 같은 형태로 써줬습니다.

 

구조체를 반환해야 한다면 앞의 반환형태 int를 struct로 바꿔주면 되겠죠.

그리고 return 구조체 변수; 의 형태로 쓰는겁니다.

 

아래 예제를 봐주세요

 

#include <stdio.h>

struct pos
{
	int x;
	int y;
};

struct pos get_pos(int x, int y){
	struct pos point = {x, y};
	return point;
}

int main(){
	struct pos p = get_pos(3,4);
	printf("p.x : %d ; p.y : %d", p.x, p.y);
	return 0;
}

다음 예제는 get_pos() 함수를 이용해서 main()함수의 구조체 변수 p에 값을 할당하는 예시입니다.

 

보시면 get_pos라는 함수는 반환 형태가 struct pos 입니다. 방금전에 반환 형태를 struct 로 써주어야 한다고 했는데 이름 또한 써주야 합니다.

 

구조체는 사용자가 직접 만들어서 이름을 붙여서 쓰는것이기 때문에 그냥 struct만 붙여서는 아무 의미가 없습니다.

get_pos()함수의 반환 형태가 struct pos 이므로 struct pos로 선언된 구조체 변수의 값을 반환하겠다는 의미입니다.

보시면 get_pos() 함수 내부에서 point라는 pos 구조체 변수를 만들어줬고 반환 하고 있습니다.

 

main() 함수 첫줄부터 보자면

struct pos p = get_pos(3,4);

라고 쓰면 get_pos()함수에 3과 4를 인자값으로 제공하고 있습니다.

get_pos() 내부에서 {3,4}로 구조체가 만들어지고 이를 반환하면

 

구조체 변수 p가 point변수의 값 3,4를 그대로 복사받게 됩니다.

그리고 출력해보면 p.x = 3, p.y = 4가 출력됩니다.

 

글이 길어서 읽기 싫어지셨겠지만 사실 일반 변수랑 크게 다를게 없습니다.

그냥 함수에서 구조체 변수로 값을 반환하면,

같은 구조체 변수로 받아주시면 됩니다.

 

반환형이 int인 함수가 4를 반환하면 int형 변수로 그 값을 받아야 하는것과 동일하죠.

 


지금까지는 구조체 변수 "값" 의 반환이였고 "주소" 의 반환도 봐봅시다.

이것도 일반변수랑 동일해요.

 

#include <stdio.h>

struct pos
{
	int x;
	int y;
};

struct pos * get_pos(int x, int y){
	static struct pos point;
    point.x = 3;
	point.y = 4;
	return &point; //구조체 변수의 주소를 넘긴다. 
}

int main(){
	struct pos * p = get_pos(3,4);
	printf("p.x : %d ; p.y : %d", p->x, p->y);
	return 0;
}

아까 값의 변환과 다른점은 딱 2가지인데요.

우선 구조체 변수 point 의 값이 아닌 주소 &point를 넘기고 있습니다.

 

반환 형태가 구조체의 주소, 일종의 구조체 포인터 이므로 반환 형태는 struct pos * get_pos()가 됩니다.

쉽게 생각해서 그냥 함수 이름 앞에 * 하나만 붙여주면 되겠습니다.

 

구조체 주소로 넘기므로 받는쪽은 구조체 포인터여야 합니다.

보시면 p가 point의 주소를 넘겨받고 있습니다.

 

메모리 적으로 따지면 현재 get_pos() 내부에서 만들어진 정적 변수(static) point의 주소를 p라는 구조체 포인터가 가리키고 있는겁니다.

 

static 키워드를 붙여줘야 하는 이유는 저번 매개변수에서 변수의 주소값 반환 편에서 한번 설명드렸는데요.

만약에 지역변수로 point를 만들어버리면 지역변수이므로 저 중괄호를 탈출할때 메모리가 자동 소멸되어 버립니다.

그러면 p의 입장에서 point의 주소를 가리켜야 하는데 point가 메모리에서 사라져버렸으니 문제가 생긴것이죠.

 

그렇지만 static 키워드를 붙여주면 프로그램 종료전까지 메모리가 남아 있게 되어 point는 계속 메모리에 상주하게 되고 함수를 빠져나가도 그대로 남아있게 되어 p가 가리킬 수 있게 되는것 입니다.

 

p로 point의 멤버 변수에 접근하려면 화살표 연산자 또는 . 연산자를 통해 접근합니다.

p->x == (*p).x

 


긴글 따라오느라 수고 많으셨습니다.

다음편에선 공용체와 열거형에 대해 알아보겠습니다 감사합니다.

SNS 공유하기
💬 댓글 개
이모티콘창 닫기
울음
안녕
감사해요
당황
피폐

이모티콘을 클릭하면 댓글창에 입력됩니다.