Home C언어 (17) 구조체와 공용체3
Post
Cancel

C언어 (17) 구조체와 공용체3

C언어 (17) 구조체와 공용체3

함수에 구조체를 이용하는 방법과 공용체, 열거형을 보자 .

구조체와 함수

구조체를 함수로 사용하는 경우는 두 가지가 있다.

  1. 구조체를 함수의 인자로 전달하기
  2. 구조체를 함수의 반환형으로 전달하기

인자로 사용할 때도 값으로 전달하는 방법과 주소로 전달하는 방법이 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
void print(struct point p);
struct point {
	int x;
	int y;
};
int main() {
	typedef struct point PO;
	
	PO p = { 20, 30 };
	print(p);
	printf("%d %d", p.x, p.y);

	return 0;
}
void print(struct point p) {
	p.x = 30;
	p.y = 40;
	printf("%d %d\n", p.x, p.y);
}
1
2
30 40
20 30

값으로 전달하면 인자 변수의 값이 복사되어 전달되기 때문에 main의 p와 print의 p는 다르다.
그래서 출력을 해도 2개는 다른 값이 나온다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
void print(struct point* p);
struct point {
	int x;
	int y;
};
int main() {
	typedef struct point PO;
	
	PO p = { 20, 30 };
	print(&p);
	printf("%d %d", p.x, p.y);

	return 0;
}
void print(struct point* p) {
	p->x = 30;
	p->y = 40;
	printf("%d %d\n", p->x, p->y);
}
1
2
30 40
30 40

주소로 전달하면 main의 p와 print의 *p는 같은 공간을 보고 있기에 값이 바뀐다.
이전에 봤던 call by value와 call by reference를 구조체 변수로 사용한 것이다.
구조체의 크기도 굉장히 커질 수 있기 때문에 주소로 전달하는 방법이 효율적일 수도 있다.

이번에는 함수의 반환으로 구조체를 사용해보자.
함수의 반환 값의 자료형은 함수 이름 앞에 적어준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
struct point print();
struct point {
	int x;
	int y;
};
int main() {
	struct point p1 = print();
	printf("%d %d", p1.x, p1.y);

	return 0;
}
struct point print() {
	struct point p = { 10, 20 };
	return p;
}

이렇게 함수의 이름 앞에 반환할 구조체인 struct point를 써주어 생성한 구조체의 값을 반환하는 함수이다.
마찬가지로 구조체의 주소를 반환하는 함수도 만들 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
struct point* print();
struct point {
	int x;
	int y;
};
int main() {
	struct point* p1 = print();
	printf("%d %d", p1->x, p1->y);

	return 0;
}
struct point* print() {
	struct point p = { 10, 20 };
	return &p;
}

주소를 반환해 참조하는 코드이다.
이렇게 구조체와 배열, 포인터, 함수를 섞어 사용하는 방법을 알아봤다.
구조체도 C언어에서 굉장히 활용도가 높아 확실히 알아두어야 한다.
struct ~라는 새로운 자료형을 만들어 변수를 선언해 사용한다고 생각하면 이해가 쉬울 것 같다.

이제 구조체와 비슷한 자료형인 공용체와 열거형을 간단히 알아보자.

공용체

공용체도 여러 개의 멤버 변수를 갖는데, 그 중 가장 큰 메모리 공간을 공유해 사용한다.
같은 메모리 영역을 여러 개의 변수가 공유하도록 해서 메모리를 절약한다.

그림으로 각각 표현해보자.

1
2
3
4
5
struct str{
	int i;
	char ch;
	double d;
};

image

이번에는 같은 멤버 변수를 가진 공용체를 보자. 공용체는 union 이라는 키워드로 선언한다.

1
2
3
4
5
union str{
	int i;
	char ch;
	double d;
};

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
struct str {
	int i;
	char ch;
	double d;
};
union uni {
	int i;
	char ch;
	double d;
};
int main() {
	struct str s1;
	union uni u1;
	printf("구조체의 크기 : %d, 공용체의 크기 : %d", sizeof(s1), sizeof(u1));

	return 0;
}

크기를 비교해보면 구조체는 가장 큰 자료형이 double이므로 위의 그림에서 padding까지 합쳐져 16이지만
공용체의 크기는 그림처럼 8의 크기로 각각의 멤버 변수들을 저장함을 알 수 있다.

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
union uni {
	int x;
	int y;
};
int main() {
	union uni u1;
	u1.x = 20;
	printf("x : %d, y : %d", u1.x, u1.y);
	return 0;
}
1
20 20

x와 y는 같은 메모리 공간을 공유하기 때문에 x에 20을 넣으면 y도 20이 된다.

이것을 어디에다 사용할 수 있을까?
개인적으로는 아직 한번도 써본 적도 쓴 걸 본 적도 없긴 하다.
예시를 찾아보니 메모리를 아주 효율적으로 사용해야 하는 통신 같은 곳에서 공용체를 사용한다고 한다.
인증을 할 때 통신 과정에서 이름과 주민번호 중 하나만 보내면 된다고 하자.

만약 구조체로 통신 패킷을 구현한다고 하면

1
2
3
4
struct cert{
	char name[20];
	char number[20];
};

이렇게 구현하면 총 40byte의 크기를 가진다.

1
2
3
4
union cert{
	char name[20];
	char number[20];
}

이렇게 공용체를 사용하면 20byte로 같은 역할을 하는 구조를 만들 수 있다.

여기서 다룰 내용은 아니지만 공용체에서 특이한 결과가 나오는 것이 있어 조금만 봐보자.

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
union uni {
	char c;
	int num;
};
int main() {
	union uni u1;
	u1.num = 0x41424344;
	printf("c : %c", u1.c);
	return 0;
}
1
c : D

우선 0x41 0x42 0x43 0x44는 각각 십진수로 바꾸면 65, 66, 67, 68이다.
이는 아스키코드 표로 바꾸면 A, B, C, D이다.
그런 값을 num에 저장하고, c도 num과 공유하는 메모리를 가지므로 어떤 값이 들어갈 것이다.
그렇게 출력을 해보니 D가 나온다. 그 이유가 뭘까?

image

이렇게 저장이 된다면 char c는 0x41=65가 들어가고, A가 출력이 되어야 하는 것이 아닐까?
하지만 c에는 0x44 = 66의 값이 들어가 있다. 이 이유는 리틀 엔디안이라고 부르는 저장 방식 때문이다.

사용 이유나 더 자세한 이야기는 시스템 글을 쓰며 정리할 것 같아 이런게 있다는 것만 쓰고 넘어가자.
컴퓨터에서 메모리에 값을 저장할 때 위의 그림처럼 우리가 생각하는 순서대로 저장하는 것을 빅 엔디안
반대로 저장하는 것을 리틀 엔디안 방식이라고 부른다.

image

현재 대부분의 컴퓨터는 리틀 엔디안 방식을 사용하기에 실제로는 값이 이렇게 저장된다는 것이다.
그렇기에 예상과는 다른 결과가 나올 수 있으니 주의하자.

열거형

열거형은 변수가 가질 수 있는 값들을 미리 열거해 정수형 상수를 만드는 것이다.

1
2
3
4
5
6
#include <stdio.h>
enum season { SPRING, SUMMER, FALL, WINTER };
int main() {
	printf("%d %d %d %d", SPRING, SUMMER, FALL, WINTER);
	return 0;
}
1
0 1 2 3

이렇게 사용하면 SPRING = 0, SUMMER = 1, FALL = 2, WINTER = 3의 값을 가지는 상수가 된다.

1
2
3
4
5
6
#include <stdio.h>
enum season { SPRING = 10, SUMMER, FALL = 16, WINTER };
int main() {
	printf("%d %d %d %d", SPRING, SUMMER, FALL, WINTER);
	return 0;
}
1
10 11 16 17

열거형이 값을 주는 방법은 아무런 지정 값이 없으면 0부터 시작하고 1씩 증가하며
지정 값이 있으면 그 값을 주고, 다음에 이전 것의 1씩 증가하며 넣는다.
어떤 식으로 사용할 수 있는지 프로그램으로 확인을 해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
enum season { SPRING = 1, SUMMER, FALL, WINTER };
int main() {
	int month[13] = { 0 };
	for (int i = 1; i <= 12; i++) {
		if (i / 3 == SPRING)month[i] = SPRING;
		else if (i / 3 == SUMMER)month[i] = SUMMER;
		else if (i / 3 == FALL)month[i] = FALL;
		else month[i] = WINTER;
	}
	for (int i = 1; i <= 12; i++) {
		printf("%d ", month[i]);
	}
	return 0;
}
1
4 4 1 1 1 2 2 2 3 3 3 4

열거형의 사용 목적은 가독성에 있다.
봄은 1, 여름은 2, 가을은 3, 겨울은 4로 month[]라는 배열에 각각 저장하는 프로그램이 필요하다고 하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
int main() {
	int month[13] = { 0 };
	for (int i = 1; i <= 12; i++) {
		if (i / 3 == 1)month[i] = 1;
		else if (i / 3 == 2)month[i] = 2;
		else if (i / 3 == 3)month[i] = 3;
		else month[i] = 4;
	}
	for (int i = 1; i <= 12; i++) {
		printf("%d ", month[i]);
	}
	return 0;
}

2개의 코드는 같은 역할을 하는 것이지만 2번째 코드는 봤을 때 무슨 일을 하는지 직관적이지 않다.
반면 위의 코드는 무슨 일을 하는지 코드만 보고도 대충 알 수 있다.
이런 가독성을 위해 열거형을 사용한다.


이렇게 길었던 구조체에 대한 이야기가 끝났는데, 구조체도 정말 사용하는 곳이 많다.
다음은 문자열과 관련 함수에 대해 알아본다.

This post is written by PRO.