Home C언어 (16) 구조체와 공용체2
Post
Cancel

C언어 (16) 구조체와 공용체2

C언어 (16) 구조체와 공용체2

구조체에서 배열과 포인터를 어떻게 쓰는지 자세히 보자.

구조체의 메모리 저장 방법

image

구조체가 메모리에 저장되는 방식은 그림과 같다.

  1. 각각의 멤버 변수는 시작 주소부터 연속되어 저장을 한다.
  2. 크기는 구조체의 멤버 중 가장 큰 멤버의 자료형에 영향을 받는다.

예시로 확인을 해보자.

1
2
3
4
typedef struct score {
	char c;
	char a;
} SCORE;

의 경우에는 char형 변수가 1byte씩이므로 크기를 출력하면 2가 나온다.

1
2
3
4
5
typedef struct score {
	char c;
	char a;
	int num;
} SCORE;

그런데 int형 변수를 추가하면 가장 큰 자료형이 int(4byte)이므로 저장되는 단위가 4byte로 바뀐다.
image

그림처럼 c를 저장하고, a를 저장하면 2바이트가 채워진다.
다음에 저장할 int가 4바이트이므로 빈 공간으로 남은 2바이트를 채운다. 이를 padding이라고 부른다.
그 다음 int num을 채우면 8의 크기를 가진 구조체 변수가 된다.

1
2
3
4
5
typedef struct score {
	char c;
	int num;
	char a;
} SCORE;

이 때 int num과 char a의 순서를 바꾸면 어떻게 될까? 신기하게도 크기는 달라진다.
c는 1바이트에 저장되는데 다음에 저장할 num이 4바이트이므로 남은 3바이트를 패딩으로 채운다.
그 다음 char형 변수인 a도 4바이트 크기에 저장되기 때문에 결과는 12의 크기를 가진 구조체가 된다
image

1
2
3
4
typedef struct score {
	double d;
	int arr[11];
} SCORE;

이런 구조체의 크기는 몇일까? 영향을 받는 것은 자료형 하나의 크기이기에 8바이트에 기준이 맞춰진다.
그러므로 double(8byte) + int(4byte) * 11 = 52byte인데 8byte로 나눈다고 할 때 4바이트가 남는다.
배열의 가장 뒤 원소에 패딩으로 4byte가 붙어서 56바이트의 구조체가 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
typedef struct score {
	double d;
	int arr[11];
} SCORE;
struct student {
	SCORE s[3];
	int no;
};
int main() {
	typedef struct student STU;
	STU st1;
	printf("%d", sizeof(st1));
	return 0;
}

마지막으로 56byte의 크기를 가진 score 구조체 배열로 멤버 변수로 가진 student 구조체의 크기는 몇일까?
우선 56byte가 3개있으므로 56*3 = 168byte의 크기에 뒤에 int no가 추가된다.
구조체에서 가장 큰 자료형은 double이므로 int no 뒤에 4byte가 패딩으로 붙는다.
그러면 최종적으로 168 + 4 + 4 = 176byte의 크기를 가진다.

구조체와 배열

구조체 안의 멤버 변수로는 배열도 들어갈 수 있다.

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
struct student {
	char name[10];
	double score;
	char grade;
};
int main() {
	typedef struct student STU;
	STU s1 = { "Kim", 90, 'A'};
	STU s2 = { "Lee", 80, 'B' };
}

이렇게 사용할 수 있다.

그러면 구조체 자료형을 통해 배열도 만들 수 있을까?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
struct student {
	char name[10];
	double score;
	char grade;
};
int main() {
	typedef struct student STU;
	STU s[3] = {
		{ "Kim", 90, 'A'},
		{ "Lee", 80, 'B' },
		{ "Park", 70, 'C' }
	};

	printf("이름 : %s 점수 : %d 성적 %c \n", s[0].name, s[0].score, s[0].grade);
	return 0;
}

구조체 배열도 방법은 같고, 접근 방법도 s[0], s[1], s[2]로 각각의 원소에 접근할 수 있다.

구조체와 포인터

구조체에서 포인터를 사용하는 것은 이렇게 3가지로 들 수 있다.

  1. 멤버 변수로 포인터 사용
  2. 구조체 변수로 포인터 사용
  3. 자기 참조 구조체와 외부 참조 구조체

우선 멤버 변수로 포인터를 사용하는 것은

1
2
3
4
struct pointer {
	int* p1;
	double *p2
};

이렇게 해서 일반 포인터 변수 쓰듯이 사용하면 된다.

그러면 구조체 변수의 주소를 저장하는 구조체 변수 포인터는 어떻게 사용할까?
이도 크게 다르지는 않다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
struct student {
	int num;
	double score;
};
int main() {
	typedef struct student STU;
	
	STU s1 = { 1, 95.5 }, s2 = { 2, 85.5 };

	STU* p1 = &s1;
	STU* p2 = &s2;

	printf("번호 : %d 점수 : %lf\n", (*p1).num, (*p1).score);
	printf("번호 : %d 점수 : %lf\n", (*p2).num, (*p2).score);
}

구조체 형 포인터를 선언한 다음, 구조체 변수의 주소를 저장하고 *연산자를 통해 값을 가져온다.
주의해야 하는 것은 .(dot) 연산자가 *연산자보다 우선순위가 높기에 (*p1).num처럼 꼭 괄호가 필요하다.

이 때 포인터 변수만으로 구조체의 멤버 변수에 접근하는 방법인 -> 연산자가 있다.

1
(*p).no == p -> no
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
struct student {
	int num;
	double score;
};
int main() {
	typedef struct student STU;
	
	STU s1 = { 1, 95.5 }, s2 = { 2, 85.5 };

	STU* p1 = &s1;
	STU* p2 = &s2;

	printf("번호 : %d 점수 : %lf\n", (*p1).num, (*p1).score);
	printf("번호 : %d 점수 : %lf\n", p1->num, p1->score);
	printf("번호 : %d 점수 : %lf\n", (*p2).num, (*p2).score);
	printf("번호 : %d 점수 : %lf\n", p2->num, p2->score);
}

이렇게 사용하면 더 편하게 접근할 수 있다.
->연산자는 구조체 포인터 변수에서만 사용하니까 잘 알아두면 좋다.

자기 참조 구조체, 외부 참조 구조체

1
2
3
4
5
6
// 자기 참조 구조체
struct student {
	char name[20];
	int age;
	struct student* p;
};

이렇게 자신의 구조체인 struct student를 참조하는 구조체를 자기 참조 구조체라고 부르고

1
2
3
4
5
6
// 외부 참조 구조체
struct student {
	char name[20];
	int age;
	struct score* p;
};

구조체 내부에서 다른 구조체를 참조하는 구조체를 외부 참조 구조체라고 부른다.

우선 자기 참조 구조체는 연결 리스트라는 다른 자료 구조로 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
struct student {
	char name[20];
	int age;
	struct student* link;
};
int main() {
	typedef struct student STU;
	
	STU s1 = { "Kim", 20, NULL };
	STU s2 = { "Lee", 22, NULL };
	STU s3 = { "Park", 19, NULL };

	s1.link = &s2;
	s2.link = &s3;

	STU* current = &s1;
	while (current != NULL) {
		printf("이름: %s, 나이: %d\n", current->name, current->age);
		current = current->link; // 다음 노드로 이동
	}
}
1
2
3
이름: Kim, 나이: 20
이름: Lee, 나이: 22
이름: Park, 나이: 19

이런 결과가 나오고, 어떻게 저장이 되어있는 것인지 그림으로 보면

image

이렇게 메모리 구조가 만들어져 있다. 이제 link를 따라 가보면

image

포인터인 s1.link를 통해 s2에 이어지고, s2.link를 통해 s3에 이어진다.
그 다음 STU* current라는 포인터를 각 변수의 link로 변경하며 모든 구조체 변수를 출력할 수 있다.

외부 참조 구조체는 다른 구조체를 구조체 멤버로 받는 구조체와 비슷한 형태로 사용된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
struct point {
	int x;
	int y;
};
struct student {
	char name[20];
	struct point* link;
};
int main() {
	typedef struct student STU;
	typedef struct point PO;
	
	STU s1 = { "Kim", NULL };
	STU s2 = { "Lee", NULL };
	PO p1 = { 30, 40 };
	PO p2 = { 60, 80 };

	s1.link = &p1;
	s2.link = &p1;

	printf("%s %d %d\n", s1.name, s1.link->x, s1.link->y);
	printf("%s %d %d\n", s2.name, s2.link->x, s2.link->y);

	return 0;
}

student의 멤버 포인터 변수 link를 통해 외부 구조체인 point에 참조해 값을 사용할 수 있다.


구조체와 함수, 공용체, 열거형까지 더 알아보자.

This post is written by PRO.