C 언어(24) - 구조체

C 언어 강의, 구조체의 메모리 구조를 통해 다루기

Featured image

🔚 짧게 하는 복습

✅ 1. 문자 배열과 문자열의 차이를 안다.

✅ 2. 문자열을 리터럴로 사용하는 방법을 안다.

✅ 3. 라이브러리 함수를 이용해서 문자열을 다루는 법을 안다.(과제 참조)

혹시 기억이 안 난다면, 다시 돌아가자


우리는 저번 강의까지 참 많은 자료형과 자료구조에 대해 다루었다.

처음에는 간단한 자료형부터, 포인터 자료형 그리고 배열과 문자열까지 결합해서 배웠다.

오늘은 자료구조 중 구조체에 대해 다루어보겠다.


구조체란?

지금까지의 역사를 보면, 새로운 자료구조가 나왔다는 이야기는 새로운 자료구조의 수요가 생겼다는 말과 같다.

이제 필요할 만한 것은 전부 배운 것 같은데, 무엇이 더 필요했다는 것일까?

예를 들어 우리가 학생의 데이터를 처리해야 하는 일이 생겼다고 하자.

학생의 이름, 학번, 점수 등을 저장하고 싶은데, 여기서 문제는 이 복합 자료를 한 번에 처리할 방법이 없다.

모든 배열을 만들어 놓고 인덱스를 통해서 참조하고 다루어야 한다는 문제가 있다.

우리는 이 부분에서 새로운 자료구조 구조체를 도입한다.

구조체의 정의는 아래와 같고, 함수의 정의처럼 보통 int main위에 정의해준다.

struct student{
    char* name;
    int stu_number;
    double score;
};
// 주의할 점은 정의할 때, 안에 값을 초기화시켜주면 안 된다.
// 또한, 필요한 만큼 구조체 안의 원소는 추가할 수 있다.

이는 아래와 같이 선언 및 초기화할 수 있다.

#include<stdio.h>
#include<string.h>

struct student{
    char* name;
    int stu_number;
    double score;
};
// 주의할 점은 정의할 때, 안에 값을 초기화시켜주면 안 된다.
// 또한, 필요한 만큼 구조체 안의 원소는 추가할 수 있다.
// 배열, 문자열, 심지어 구조체도 구조체의 원소로 넣을 수 있다.

int main(){
    struct student Younghee; // 선언
    struct student Minsu = {"Minsu", 12345678, 99.9}; //초기화

    return 0;
}

그런데, 위의 코드에서 struct student를 매번 쓸 것을 생각하면, 벌써 귀찮다.

이런 경우에 우리가 쉽게 다루는 방법을 배운 적이 있었는데, typedef이다.

조금 수정해서 덜 귀찮게 해보자.

#include<stdio.h>
#include<string.h>

typedef struct student{
    char* name;
    int stu_number;
    double score;
}stu;//struct student라는 귀찮은 자료형 대신, stu라는 새로운 이름으로 이용하겠다는 뜻
// 주의할 점은 정의할 때, 안에 값을 초기화시켜주면 안 된다.
// 또한, 필요한 만큼 구조체 안의 원소는 추가할 수 있다.

int main(){
    stu Younghee; // 선언
    stu Minsu = {"Minsu", 12345678, 99.9}; //초기화
    //훨씬 간결해짐.
    return 0;
}

구조체의 메모리 구조

지금까지 배열 및 문자열을 거쳐온 여러분이라면, 새로운 자료구조를 만났을 때 어떤 메모리 구조로 저장이 되는지 궁금할 것이다.

여러 자료형을 선언해야 하는 구조체의 메모리 구조는 어떻게 되는지 알아보자.

우선 구조체는 전체적인 틀은 배열의 모습과 크게 다르지 않다.

가장 메모리 크기가 큰 원소를 찾아서, 그 원소의 크기로 배열을 만든다.

예를 들어 아래의 코드에서는, double형이 가장 크기 때문에 8바이트로 만든다.

typedef struct student{
    char* name; //포인터의 크기는 보통 8바이트(1 word)
    int stu_number; // 4바이트
    double score; //8바이트
}stu;

위의 이미지와 같은 모습으로 만들어진다. (편의를 위해 1칸을 4바이트 단위로 만듦)

그런데 여러분은 궁금한 점이 있을 것이다. int형은 4바이트인데 8바이트가 할당되면 어떻게 저장되는지를 말이다.

이는 간단한데, 앞의 4바이트만 사용하고, 나머지 4바이트는 사용하지 않는다. 즉 빈 곳인 채로 놔두는데, 이를 패딩이라 한다.


패딩 최적화란?

배열과는 다르게 구조체가 패딩을 가진다는 점에서, 패딩은 구조체의 정말 중요한 성질이 된다.

그 중 패딩 최적화라는 것이 있는데, 아래의 코드를 실행해보자.

엥? 분명 같은 구조체의 원소를 가지는데, 왜 차지하는 메모리가 다를까?

이는 좀 더 엄밀하게 구조체가 정의되는 방법을 알 필요가 있다.

student1부터 보자.

  1. 메모리 차지가 가장 큰 원소를 찾는다.(int형 4바이트)
  2. 우선 4바이트를 할당한다.
  3. 구조체의 원소 순서대로 남은 공간에 넣을 수 있는지 확인한다. 가능하다면 공간에 앞에서부터 할당한다.
  4. 2번째 원소도 1바이트, 남은 공간 3바이트에 할당 가능하니, 할당한다.
  5. 다음 원소는 4바이트, 지금 남은 공간은 2바이트기에 padding처리하고 다음 4바이트를 만들어서 저장한다.
  6. 모든 원소 저장이 끝났으니, 구조체가 완성되었다. (총 8바이트)

반면 student2는 어떨까?

  1. 메모리 차지가 가장 큰 원소를 찾는다.(int형 4바이트)
  2. 우선 4바이트를 할당한다.
  3. 원소 순서대로 할당한다. 1바이트 할당 후 3바이트 남는다.
  4. 4바이트 원소가 들어가지 않으니 패딩처리 후, 새로운 4바이트 공간을 할당받아서 저장한다.
  5. 남은 공간이 없으니 새로운 원소를 저장할 4바이트를 할당한 후, 그중 1바이트 할당한다
  6. 모든 원소 저장이 끝났으니, 구조체가 완성되었다.(총 12바이트)

이렇게 패딩 공간을 줄여서 메모리를 적게 하는 방법패딩 최적화라고 한다.


구조체의 참조

자 이렇게 되면, 궁금한 점이 생긴다.

배열은 배열 이름이 배열의 첫 시작 주소였고, 정수 연산으로 인덱싱을 했었다.

또한, 이는 []라는 연산자를 통해서 간단히 표기할 수도 있었다.

그렇다면 구조체는 각 원소의 참조가 어떻게 될까?

구조체의 이름은 구조체 시작 주소를 가리키지 않고, 인덱싱이란 개념도 구조체에서 사용할 수 없다.

위에서 본 것처럼 메모리의 어떤 위치에 저장이 되는지 예측하기 힘들기 때문이다.

그래서 구조체의 시작 주소를 얻고 싶다면 직접 주소연산자를 이용해야 하고, 정수 연산 대신 .라는 연산자를 이용하면 원하는 원소의 메모리 시작 위치를 계산해준다.

아래의 코드를 직접 실행해보자.

그렇다면 여기서, 간접 참조만 해주면 원하는 값에 참조할 수 있는 것이다.

그런데 매번 이렇게 쓰면 얼마나 불편하겠는가….

그래서 . 연산자는 두 번째 기능이 있는데, 변수 이름 뒤에 붙이면 바로 원하는 값에 직접 참조가 가능하다.

이를 이용해서, 수정할 때는 아래처럼 할 수 있다.


📖 오늘의 핵심(다 알기 전까지는 넘어가지 말자❗)

✅ 1. 구조체의 정의, 선언을 할 수 있다.

✅ 2. 구조체의 메모리상 저장 방법과 패딩 최적화를 안다.

✅ 3. 구조체 원소의 간접 참조, 직접 참조

⚠️ 구조체의 패딩을 최대한 줄이는 방향으로 최적화를 하자.

💣 과제,

  1. 구조체에 이름(문자열), 학번(정수), 성적(문자열)을 저장하는 프로그램을 만든다. (난이도 下)

  2. 1번에서 만든 프로그램으로 구조체 A, B를 두 개를 선언한 후, 두 원소의 값을 바꾸는 프로그램을 만든다. (난이도 中)

  3. 구조체에 이름(문자열), 성적(수학, 영어, 국어 모두 정수형) 구조체를 저장하는 프로그램을 만든다. (난이도 中) (구조체 안에 구조체)

  4. 3번의 구조체 안의 구조체는 메모리 구조를 생각해보자.(난이도 上)

🔜 더 공부해보기,

  1. 읽어볼 거리(1) - 패딩 문제에 도전하는 공학자들의 방법