[2-10] 구조체

구조체(Structure Types)는 사용자 정의 자료형입니다. 한 가지 이상의 자료형을 멤버로 가질 수 있습니다. 다음과 같이 선언합니다.

struct 구조체명 {
  멤버 변수;
};
  • 구조체명 : 구조체의 이름입니다.
  • 멤버 변수 : 구조체

예를 들어 국영수 점수를 관리하는 score 구조체를 다음과 같이 정의할 수 있습니다.

struct score{
    int korean;
    int english;
    int math;
};

구조체 변수 선언은 다음과 같이 합니다.

struct score myScore;

열거형 변수를 선언할 때 enum을 붙여줬던 기억 나죠? enum대신 struct를 사용했군요!

구조체 변수 초기화하기

구조체 를 초기화하는 방법은 크게 두 가지입니다.

  1. 변수 선언 이후에 멤버 변수마다 초기화하기
  2. 변수 선언 때 초기화하기

먼저 변수 선언 이후에 멤버 변수마다 초기화하는 방법을 알아보겠습니다.

구조체의 요소인 변수를 멤버 변수라고 합니다. 멤버 변수에 접근하는 방법은 간단합니다 구조체 변수와 멤버 변수 사이에 마침표를 사용합니다. 즉, 수학 점수를 입력하는 멤버 변수 math에 다음과 같이 값을 입력할 수 있습니다.

myScore.math = 90;

다음은 아예 구조체 변수를 선언할 때 초기화하는 방법을 알아보겠습니다. 다음과 같이 중괄호와 쉼표를 사용하면 됩니다.

struct score myScore = {80, 90, 100};

이때 초기화하지 않은 멤버 변수는 0으로 패딩됩니다.

정말로 이 두 방식 모두를 지원하는지 예제로 확인해보겠습니다.

#include <stdio.h>

typedef struct score{
    int korean;
    int english;
    int math;
};

int main(void) {
	struct score myScore;
	struct score yourScore = {80, 90};
	
	myScore.korean = 100;
	myScore.english = 90;
	myScore.math = 80;
	
	printf("myScore.korean = %d\n", myScore.korean);
	printf("myScore.english = %d\n", myScore.english);
	printf("myScore.math = %d\n", myScore.math);
	
	printf("yourScore.korean = %d\n", yourScore.korean);
	printf("yourScore.english = %d\n", yourScore.english);
	printf("yourScore.math = %d\n", yourScore.math);
	
	return 0;
}

[결과]

myScore.korean = 100
myScore.english = 90
myScore.math = 80
yourScore.korean = 80
yourScore.english = 90
yourScore.math = 0

예상대로 두 초기화 방식 모두 잘 작동합니다. 그리고 초기화하지 않은 yourScore.math은 0으로 패딩되어 값이 0으로 출력되었군요!

별칭 만들어 사용하기

struct를 붙이기 번거롭다면 다음과 같이 struct score의 별칭을 만들어주면 됩니다.

typedef struct score{
    int korean;
    int english;
    int math;
} SCORE;

이제 struct 예약어를 사용하지 않고도 score 구조체 변수를 다음과 같이 선언할 수 있습니다.

SCORE myScore;

그럼 점수를 입력하고 출력하는 예제를 살펴봅시다.

#include <stdio.h>

typedef struct score{
    int korean;
    int english;
    int math;
} SCORE;

int main(void) {
	struct score myScore;
	SCORE yourScore;
	
	myScore.korean = 100;
	myScore.english = 90;
	myScore.math = 80;
	
	yourScore.korean = 80;
	yourScore.english = 90;
	yourScore.math = 100;
	
	printf("myScore.korean = %d\n", myScore.korean);
	printf("myScore.english = %d\n", myScore.english);
	printf("myScore.math = %d\n", myScore.math);
	
	printf("yourScore.korean = %d\n", yourScore.korean);
	printf("yourScore.english = %d\n", yourScore.english);
	printf("yourScore.math = %d\n", yourScore.math);
	
	return 0;
}

[출력]

myScore.korean = 100
myScore.english = 90
myScore.math = 80
yourScore.korean = 80
yourScore.english = 90
yourScore.math = 100

입력한 대로 제대로 출력이 되었군요.

물론 int형 말고 다른 자료형도 멤버 변수로 가질 수 있고, 심지어 자기 자신도 가질 수 있습니다(자기 자신을 멤버 변수로 가지려면 포인터를 알아야 합니다. 포인터는 3부에서 다룹니다. 그후에 다시 설명하겠습니다).

아예 구조체 정의와 변수 선언을 동시에 할 수도 있습니다.

struct score{
    char name[10];
    int korean;
    int english;
    int math;
} s1;  <= 구조체 score형 변수 s1선언

앗 이와 같은 구조를 어디선가 본 것 같네요?

그렇습니다. 구조체의 별칭을 만들어 줄 때 비슷한 구조를 사용했죠. 무엇이 다르길래 하나는 별칭이고 이번에는 변수 선언일까요?

다시 한 번 별칭 선언을 살펴보겠습니다.

typedef struct score{
    int korean;
    int english;
    int math;
} SCORE;

typedef가 다르군요! typedef는 글자 그대로 타입을 정의하는 예약어입니다. typedef는 다음과 같이 사용합니다.

typedef A B;

A를 B라고 부르겠다는 뜻입니다.

따라서

struct score{
    int korean; 
    int english;
    int math;
}

를 SCORE라고 부르겠다는 뜻이겠군요!

typedef에 대한 더 자세한 내용은 3부에서 다루겠습니다.

배열을 이용한 학생 점수 관리

관우, 장비, 유비의 국영수 점수를 저장하는 구조체를 만들어봅시다.

#include <stdio.h>

struct score{
    char name[10];
    int korean;
    int english;
    int math;
} SCORE[3];

int main(void) {

	
	for(int i = 0 ; i <3 ; i++){
		
		printf("이름을 입력하세요 : ");
		gets(myScore[i].name);
		printf("\n성적을 국영수 순서로 입력하세요 : 예) 80 90 100 엔터");
		scanf("%d %d %d", 
                  &myScore[i].korean, 
                  &myScore[i].english, 
                  &myScore[i].math);
	}

	for(int i = 0 ; i <3 ; i++){		
		printf("myScore[%d].name = %s : ", i, myScore[i].name);
		printf("myScore[%d].korean = %d\n", i, myScore[i].korean);
		printf("myScore[%d].english = %d\n", i, myScore[i].english);
		printf("myScore[%d].math = %d\n", i, myScore[i].math);	
	}
	
	return 0;
}

[출력]

Guan Yu : 80 90 100
Zhang Fei : 30 20 10 
Liu Bei : 10 100 20

gets() 함수로 이름을 얻고, scanf() 함수로 성적을 얻습니다. gets() 함수 대신 다음과 같이 scanf() 함수를 쓸 수도 있습니다.

scanf("%s", myScore[i].name); 

그런데 scanf() 함수는 공백이나 EOF, 행내림, 줄내림 기호가 있으면 읽기를 중단하고 그때까지 읽는 결과를 반환합니다. 그래서 영문 이름에는 적합하지 못해 gets() 함수를 사용했습니다. 두 함수의 자세한 차이는 ‘[2-7] 표준 입력’에서 확인하세요.

이상 관우였습니다.

제갈량의 동남풍 : 구조체가 메모리에 할당될 때 크기

구조체는 과연 어떻게 메모리에 할당될까요? 궁금하시죠? 저 제갈량과 함께 예제를 살펴보며 알아보겠습니다.

문제입니다. 다음 구조체는 몇 바이트에 할당될까요?

struct info1{

char name[10];

int age;

char bloodType;

};

다음 중 답을 골라보세요.

  1. 15바이트
  2. 17바이트
  3. 20바이트

name에 10바이트, age에 4바이트, bloodType에 1바이트를 합치면 15바이트면 될 것 같습니다. 정말 그런지 확인해보겠습니다.

#include <stdio.h>

struct info{
    char name[10];
    int age;
    char bloodType;
};

int main(void) {
	struct info myInfo1;
	
	printf("info size = %dbyte\n", sizeof(myInfo));
	
	return 0;
}

[출력]

info size = 20byte

놀랍게도 20바이트라는 결과가 출력되었습니다. 왜 그럴까요?

운영체제는 메모리를 할당할 때 int형 크기를 기본 단위로 합니다. 따라서 변수 name[10]은 int형 3개 크기인 12바이트에 저장됩니다. 변수 age는 4바이트, bloodType은 1바이트만 쓸 것 같지만 4바이트, 총 20바이트(12 + 4 + 4)를 사용합니다. 아래 그림을 보겠습니다.

[그림] 구조체 변수 myInfo1가 메모리에 할당된 모습

구조체 변수 myInfo1가 메모리에 할당된 모습

사용하지 않는 멤버 변수 사이에 있는 메모리 공간에는 0값이 할당됩니다. 이렇게 0으로 값을 임의로 넣어두는 작업을 패딩이라고 합니다.

따라서 메모리 낭비를 줄이려면 다음과 같이 구조체 info의 멤버 변수를 나열하면 됩니다.

struct info{
    char name[10];
    char bloodType;
    int age;
};

실행해보시면 16바이트가 나올 겁니다.

대부분의 최신 컴파일러는 메모리 공간을 절약하는 최적화 기능을 제공해 위와 같이 수정으로 작성할 필요가 없을 때도 있습니다. 단 컴파일러마다 다르므로 최적화는 사용하는 컴파일 사양을 확인하시기 바랍니다.