C언어

15-2)구조체로 만든 자료형의 크기

SleeveStar 2021. 10. 5. 16:55
반응형

구조체 멤버 정렬 기준

 

과거에는 컴퓨터 시스템의 메모리 용량이 적어서 프로그래머들은 메모리를 최대한 적게 사용하도록 프로그램을 개발해야 했다. 최근에는 컴퓨터 시스템의 메모리 용량이 점차 늘어나면서 메모리를 조금 더 사용하더라도 프로그램의 실행 속도가 향상되도록 프로그램을 개발하고 있다. 그런데 구조체의 경우에는 다양한 크기의 메모리를 하나의 그룹으로 묶어 사용하다 보니 구조체 요소를 사용할 때 실행 속도가 떨어지는 문제가 있다. 그래서 구조체의 요소를 일정한 크기로 정렬하여 실행 속도를 더 빠르게 하는 개념이 C언어 컴파일러에 추가되었다. 컴파일러마다 용어의 차이는 있지만 마이크로소프트에서 제공하는 C컴파일러의 경우에는 '구조체 멤버 정렬' 기능을 제공하며 1, 2, 4, 8바이트로 정렬 기준을 설정할 수 있다.

 

구조체로 만든 자료형의 크기는 구조체를 구성하는 요소들의 크기를 합산한 것과 같다고 설명했다. 하지만 실제로는 컴파일러에서 구조체 정렬 기준을 어떻게 설정하는지에 따라 구조체로 만든 자료형의 크기가 달라질 수 있다. 그래서 이 기능을 모르고 단순히 구조체를 구성하는 요소의 크기를 합산해서 구조체 크기로 사용하다가 버그가 발생해 고생하는 경우도 있다. 구조체 멤버 정렬 기준에 따라 구조체로 선언한 자료형의 크기가 어떻게 달라지는지 설명하기 위해 오른쪽과 같이 Test 구조체를 선언하고, Test 자료형의 크기가 어떻게 달라지는지 하나씩 살펴보자.

 

struct Test
{
     char a; /*1바이트*/
     int b; /*4바이트*/
     short c; /*2바이트*/
     char d; /*1바이트*/
};

 

 

1바이트 정렬

 

이 정렬을 사용하면 구조체의 본래 의미대로 메모리가 구성된다. 따라서 이 기준으로 정렬된 Test 자료형의 크기는 8바이트이다.

 

 

2바이트 정렬

 

각 요소는 2의 배수에 해당하는 주소에서 시작할 수 있고 전체 크기가 2의 배수가 되어야 한다. 따라서 요소가 놓일 주소가 2의 배수가 아니라면 해당 1바이트를 버리고 2의 배수가 되는 주소에 놓인다. 하지만 예외적으로 요소의 자료형이 2바이트보다 작은 경우에는 해당요소의 크기로 정렬된다. 예를 들어 2, 4, 8바이트의 자료형의 요소들은 2의 배수에 해당하는 주소에 배치되지만 2바이트 보다 작은 1바이트 자료형 요소들은 그대로 1바이트로 정렬된다.

위와 같은 기준을 적용하면 전체 크기는 9바이트가 되어야 하지만, 2바이트 정렬은 전체 크기가 2의 배수가 되어야 하기

때문에 10바이트가 된다. 그리고 마지막 1바이트를 사용하지 않는다. 결론적으로 2바이트 정렬 기준으로 정렬한 Test 구조체의 크기는 10바이트이다.

 

 

4바이트 정렬

 

각 요소는 4의 배수에 해당하는 주소에서 시작할 수 있고 전체 크기가 4의 배수가 되어야 한다. 따라서 요소가 놓일 주소가 4의 배수가 아니라면 해당 1~3바이트를 버리고 4의 배수가 되는 주소에 놓인다. 하지만 요소의 자료형이 4바이트보다 작은 경우에는 해당 요소의 그기로 정렬된다. 예를 들어 4바이트와 8바이트 자료형의 요소들은 4의 배수에 해당하는 주소에 배치되지만 4바이트보다 작은 1바이트 자료형은 1바이트 정렬이 적용되고 2바이트자료형은 2바이트 정렬이 적용된다.

 

 

8바이트 정렬

 

구조체를 정렬할 때 모든 요소가 기준 정렬 바이트보다 작으면 요소 중에서 가장 큰 요소의 크기로 정렬된다. 따라서 지금 예시로 사용하는 구조체는 가장 큰 요소의 크기가 4바이트이기 때문에 8바이트 정렬을 사용해도 4바이트로 정렬되어 버린다. 따라서 오른쪽과 같이 Test 구조체에서 8바이트 요소를 사용하도록 수정해보자.

struct Test
{
     char a; /*1바이트*/
     double b; /*4바이트*/
     short c; /*2바이트*/
     char d; /*1바이트*/
};

각 요소는 8의 배수에 해당하는 주소에서 시작할 수 있고 전체 크기가 8의 배수가 되어야 한다. 따라서 요소가 놓일 주소가 8의 배수가 아니라면 해당 1~7바이트를 버리고 8의 배수가 되는 주소에 놓인다. 하지만 요소의 자료형이 8바이트보다 작은 경우에는 해당 요소의 크기로 정렬된다. 예를 들어 8바이트 자료형 요소들은 8의 배수에 해당하는 주소에 배치되지만 8바이트보다 작은 1, 2, 4바이트 자료형은 각각 1, 2, 4바이트 정렬이 적용된다. 위와 같은 기준을 적용하면 전체 크기는 19바이트가 되어야 하지만, 8바이트 정렬은 전체 크기가 8의 배수가 되어야 하기 때문에 크기는 24바이트가 된다. 그리고 마지막 5바이트를 사용하지 않는다. 결론적으로 8바이트 정렬 기준으로 정렬된 Test 구조체의 크기는 24바이트이다.

 

마지막에 설명한 8바이트 정렬의 예시를 보면 알 수 있듯이 이 구조체는 24바이트 중에 무려 12바이트나 버려진다. 그런데 놀랍게도 요즘 컴파일러들은 8바이트 정렬을 기본 값으로 하고 있다. 따라서 구조체에 8바이트 크기의 자료형을 사용하지 않았다면 낭비가 적겠지만, double이나 __int64 같은 8바이트 크기의 자료형을 사용하는 순간 구조체 크기가 갑자기 커지게 된다.

 

 

구조체의 요소는 같은 크기끼리 모아 주는 것이 좋다

 

이 문제는 구조체로 자료형을 선언할 때 같은 크기의 요소들끼리 모아 주는 것만으로도 프로그램의 효율을 크게 높일 수 있다.

 

기존 구조체 - A 개선된 구조체 - B
struct Test
{
     char a;  /*1바이트*/
     double b;  /*8바이트*/
     short c;  /*2바이트*/
     char d;  /*1바이트*/
};
struct Test
{
     char a;  /*1바이트*/
     char b;  /*1바이트*/
     short c;  /*2바이트*/
     double d;  /*8바이트*/
};

다음은 개선된 구조체(B)의 메모리 배치도이다. 그림을 보면 구조체의 크기가 16바이트이다. 단순히 구조체 요소의 순서만 변경했을 뿐인데 낭비되던 메모리가 12바이트에서 4바이트로 줄어들었습니다.

 

이처럼 작은 부분이라도 정확하게 개념을 이해해서 소스 코드를 구성하면 특별환 최적화 작업을 해주지 않아도 프로그램의 메모리 사용 효율을 높일 수가 있다. 그리고 앞에서 설명한 것처럼 컴파일러 설정에 따라서 구조체로 선언한 자료형의 크기가 바뀔 수 있다. 따라서 구조체로 선언한 자료형의 크기가 바뀔 수 있다. 따라서 동적 메모리 할당을 할 때 구조체의 크기를 직접 계산해서 사용하는 것보다 자료형의 크기를 계산해 주는 sizeof 연산자를 사용하는 것이 안전하다.

 

sturct Test *p1 = (struct Test *)malloc(16);     /*설정에 따라 오류가 발생할 수 있음*/
struct Test *p2 = (struct Test *)malloc(sizeof(struct Test));     /*권장하는 형태*/

 

 

 

 

 

반응형

'C언어' 카테고리의 다른 글

16)파일 입출력  (0) 2021.11.15
15-3) 구조체를 활용한 연결 리스트  (0) 2021.10.15
15-1) 배열과 구조체  (1) 2021.09.27
15) 데이터를 그룹으로 묶는 구조체  (0) 2021.09.06
14) 구조체와 연결 리스트  (0) 2021.07.12