구조체 정리해봄

Designated Hitter Jack·2023년 9월 1일

SW 사관학교 정글

목록 보기
4/44
post-thumbnail

구조체(structure type)

구조체란?

다양한 자료형을 하나로 묶을 수 있는 복합 자료형으로 다양한 형태의 데이터를 처리할 때 기본으로 사용된다. 기본 타입만으로는 나타낼 수 없는 복잡한 데이터를 표현할 수 있다.
C에서 배열은 배열 내의 값들의 데이터 타입이 같아야 하기 때문에 다양한 데이터 타입을 한번에 묶어서 다룰 때 써야한다.

구조체 선언과 멤버 사용

구조체 역시 하나의 자료형으로, 변수 선언이 가능하지만 변수 선언 전에 컴파일러에 이것이 구조체라는 것을 미리 알려주는 구조체 선언을 수행해야 한다.
이때 구조체를 구성하는 변수를 구조체의 멤버, 또는 멤버 변수라고 부른다.

struct student
{
	int num;
	double grade;
};

같은 식으로 struct 예약어와 구조체 이름을 통해 구조체를 선언한 후, 내부에 멤버 변수의 데이터 타입과 변수명을 적어서 정의한다.
함수와는 다르게 닫는 중괄호로 블록을 닫은 후에 세미콜론을 써야 한다.

이렇게 구조체를 선언하면 이 구조체로 변수를 선언할 수 있다.

struct student s1;

위의 두 코드는 다음과 같이 하나로 줄일 수 있다.

struct student
{
	int num;
	double grade;
} s1;

이런식으로 구조체 변수를 선언하면 메모리에 저장공간이 할당된다. 위의 예시에선 4바이트 int형과 8바이트 double형이 들어가므로 구조체 변수 s1에 할당된 메모리는 연속된 12바이트이다.

이때 구조체 안의 멤버 변수에 접근하려면 멤버 접근 연산자 . 을 사용한다.

s1.num = 2;
s1.grade = 2.7;

typedef 키워드

C에서 typedef 키워드는 이미 존재하는 타입에 새로운 이름을 붙일 때 사용한다. 구조체 변수를 선언하거나 사용할때에는 매번 struct를 이용하여 이것이 구조체라는 것을 컴파일러에 명시해야 하는데 typedef 키워드를 사용하면 매번 struct를 이용하지 않아도 된다.

typedef struct student PROFILE;

PROFILE이라는 새 이름을 붙여줬다.

구조체 선언과 동시에 타입에 새로운 이름을 붙여줄 수도 있다.

typeof struct 
{
	int num;
    double grade;
}PROFILE;

구조체의 정의와 typeof 선언을 동시에 할 때는 구조체의 이름을 생략할 수 있다.

구조체 변수의 크기

컴파일러는 구조체 멤버의 크기가 들쑥날쑥 한 경우 멤버 사이에 패딩 바이트를 넣어서 멤버를 가지런하게 정렬한다. 이를 바이트 얼라인먼트라고 한다. 바이트 얼라인먼트의 방식은 시스템마다 다를 수 있지만 보통 가장 크기가 큰 멤버가 메모리를 할당하는 기준이 된다. 즉, 앞서 선언했던 struct student 가 실제로 차지하는 메모리는 16바이트가 된다. num 멤버는 8바이트 블록의 처음 4바이트에 할당되고, grade 멤버가 남은 4바이트에 들어갈 수 없으므로 다음 8바이트 블록에 할당된다. 여기에서 패딩 바이트는 num 멤버 뒤의 4바이트, 전체 메모리는 16바이트가 된다.

기준 블록 내에서 크기가 작은 멤버는 각 자료형의 크기 단위로 할당된다.
char형은 1바이트, short형은 2바이트, int형은 4바이트 단위로 끊어서 할당된다.

이렇게 구조체에 할당된 메모리는 패딩 바이트때문에 메모리가 낭비되고 있는 상태다. 여기서 멤버의 선언 순서를 변경한 것 만으로 메모리를 줄일 수 있다.

구조체 변수의 초기화

구조체 변수를 초기화 할 때는 멤버 연산자(.) 와 중괄호({})를 사용한다.
다음과 같은 방법으로 구조체 변수를 초기화 할 수 있다.

s1 = {.num = 01, .name = "홍길동", .grade = 4.5};

이 방법을 사용하면 원하는 멤버 변수만을 초기화 할 수 있고, 초기화하지 않은 멤버 변수는 0으로 초기화 된다.

또한 배열의 초기화 방법과 마찬가지로 초기화 할 수도 있다.

s1 = {01, "홍길동", 4.5}

여기서 초깃값의 순서는 멤버 변수가 정의된 순서와 일치해야 한다. 나머지 변수는 0으로 초기화 된다.

구조체의 멤버

구조체에는 정수, 실수 데이터 타입 외에도 다양한 데이터 타입들이 멤버로 들어갈 수 있다. 배열, 포인터, 앞서 선언된 다른 구조체 역시 가능하다.
구조체 내부의 구조체 멤버에 접근하기 위해선 멤버 연산자 . 을 2번 사용하면 된다.

student.profile.age = 18;

구조체 변수를 함수 매개변수에 사용하기

구조체 변수는 대입 연산이 가능하기 때문에 함수의 인수로 주거나 함수에서 여러 값을 구조체로 묶어서 동시에 반환하는 것이 가능하다.
기본적으로 C에서 함수는 한번에 하나의 데이터만을 반환할 수 있다.
함수를 호출할 때 인수로 구조체 변수를 사용하면 멤버들의 값을 한꺼번에 함수에 줄 수 있다. 이는 함수가 값을 반환할 때도 똑같으므로 함수가 여러 개의 값을 한번에 반환할 수 있다.

구조체 배열 선언하기

구조체를 요소로 하는 배열 역시 선언할 수 있다. 일반적으로 배열을 선언하는 방법과 같으며 각 요소로 접근하는 방법 역시 같다.

struct book text_book[3] = 
{
	{"국어", "홍길동", 15000},
    {"영어", "이순신", 18000},
    {"수학1", "강감찬", 10000}
}

각 요소로 접근하는 법은 다음과 같다.

text_book[0].title //"국어"
text_book[1].author //"이순신"
text_book[2].price //10000

구조체를 가리키는 포인터

구조체 변수에 주소 연산자를 사용하면 특정 멤버의 주소가 아닌 구조체 전체의 주소가 구해진다. 또한 그 값을 저장할 때 구조체 포인터를 사용한다.

#include <stdio.h>

struct score
{
	int kor;
    int eng;
    int mat;
};

int main(void)
{
	struct score yuni = {90, 80, 70} //구조체 변수 선언 및 초기화
    struct score *ps = &yuni; //구조체 포인터에 구조체 변수 yuni의 주소 저장
    
    printf("국어: %d\n", (*ps).kor);
    printf("영어: %d\n", ps->eng);
    printf("수학: %d\n", ps->mat);
    
    return 0;
}

위의 예시에서 볼 수 있듯, 구조체의 포인터는 (*)참조 연산자와 (->) 화살표 연산자로 둘 다 접근 할 수 있다.

여기서 참조 연산자는 멤버 연산자보다 우선 순위가 낮으므로 반드시 괄호로 묶어서 사용해야 한다.
화살표 연산자는 앞쪽에는 구조체 포인터를, 뒤쪽에는 접근하고자 하는 구조체의 멤버 변수 이름을 사용하면 된다. 일반적으로 화살표 연산자를 더 자주 사용한다고 한다.

공용체(Union type)

구조체와 선언방식은 비슷하지만 차이점은 모든 멤버가 하나의 저장공간을 같이 사용한다는 것이다.

#include <stdio.h>

union student
{
	int num;
    double grade;
};

int main(void)
{
	union student s1 = {315};
    
    printf("학번: %d\n", s1.num); //학번: 315
    s1.grade = 4.4;
    printf("학점: %.1lf\n", s1.grade); //학점: 4.4
    printf("학번: %d\n",s1.num); //학번: -171798437126 암튼 쓰레기값
    //학번의 초깃값이 학점 멤버에 의해서 바뀌었다.
    
    return 0;
}

공용체 변수의 크기

공용체 변수의 크기는 멤버 중 가장 크기가 큰 멤버로 결정된다. 예시에선 double형 멤버의 크기가 가장 크므로 union student의 크기는 8바이트가 된다.

공용체 변수의 초기화

공용체 변수의 초기화는 중괄호를 이용해 첫번째 멤버만 초기화한다.
첫번째 멤버가 아닌 멤버를 초기화 할 때는 멤버 접근 연산자로 멤버를 직접 지정해야 한다.

union student s1 = {.grade = 4.4};

공용체 멤버는 언제든지 다른 멤버에 의해 값이 변할 수 있으므로 항상 각 멤버의 값을 확인해야 하는 단점이 있다. 그러나 같은 공간에 여러 멤버가 값을 저장하므로 메모리를 절약할 수 있고, 같은 공간에 저장된 값을 여러 형태로 사용할 수 있다는 장점이 있다.

열거형(enumerated type)

열거형 역시 구조체와 비슷하다. 하지만 열거형은 변수에 저장할 수 있는 정수 값을 기호로 정의해서 나열한다.

#include <stdio.h>

enum season {SPRING, SUMMER, FALL, WINTER}; //열거형 선언 enumerate v.열거하다.

int main(void)
{ 
	enum season ss; //열거형 변수 선언
    char *pc = NULL;  // 문자열을 저장할 포인터
    
    ss = SPRING; //열거 멤버의 값 대입
    switch(ss)  //열거 멤버 판단
    {
    case SPRING: //SPRING이면
    	pc = "inline"; break; //"inline"문자열 선택
    case SUMMER: //SUMMER면
    	pc = "swimming"; break; //"swimming"문자열 선택
    case FALL:
    	pc = "trip"; break;
    case WINTER:
    	pc = "skiing"; break;
	}
	printf("나의 레저 활동 => %s\n", pc);
    
    return 0;
}

예약어 enum, 열거형의 이름 season, 열거형 변수에 저장할 기호화된 정수 값들 {SPRING, SUMMER, FALL, WINTER}
컴파일러는 멤버를 0 부터 차례로 하나씩 큰 정수로 바꾼다. SPRING은 0, WINTER는 3이 되지만 초깃값을 원하는대로 설정할수도 있다.

enum season {SPRING = 5, SUMMER, FALL = 10, WINTER};

이때 값이 새로 설정된 멤버 다음 멤버는 이전 멤버보다 1 큰 정수가 된다.
결국 열거형 멤버는 정수로 바뀌므로 기호를 사용하지 않고도 코딩은 할 수 있지만 사람이 이해하기 훨씬 어려워진다.

이 글은 서적 '혼자 공부하는 C언어'의 17장 0구조체 파트와 TCP school의 구조체를 참고했습니다.

profile
Fear always springs from ignorance.

0개의 댓글