C언어 (16) 구조체와 공용체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로 바뀐다.
그림처럼 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의 크기를 가진 구조체가 된다
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
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
이런 결과가 나오고, 어떻게 저장이 되어있는 것인지 그림으로 보면
이렇게 메모리 구조가 만들어져 있다. 이제 link를 따라 가보면
포인터인 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에 참조해 값을 사용할 수 있다.
구조체와 함수, 공용체, 열거형까지 더 알아보자.