티스토리 뷰

안녕하세요, IT디자이너입니다.

 

구조체의 사용방법이나 구조체의 정의는 여러분들도 다 아시겠지만 한번 더 설명드리자면, "연관있는 데이터를 하나로 묶을 수 있는 자료형"입니다.

 

사용방법은 간단한 인터넷 검색을 통하여 금방 익히실 수 있으실 겁니다. 그러하여,

 

이번 포스팅은 C언어의 구조체를 사용하는 방법보다는 구조체의 특징에 관하여 포스팅하도록 하겠습니다.

 

 

1. 구조체의 크기는 어떻게 정해질까?

 

먼저 예제 소스코드로 설명드리도록 하겠습니다. 아래의 소스코드의 구조체 크기는 몇이 나올까요?

 

1-1

typedef struct data {
	float a;
	double b;
	char c;
}Data;


int main()
{
	Data a;

	printf_s("%d", sizeof(a));
}

 

구조체 특징에 관하여 알지 못할 경우 100% 틀리는 경우가 많습니다. 

 

float(4byte) + double(8byte) + c(1byte) = 13byte로, 답 : 13byte로 아시는 분들이 대부분이실 겁니다.

 

하지만 답은!

.

.

.

.

.

24byte입니다. 

 

무려 13byte와 11byte나 차이가 나는 구조입니다. 그 이유는 바로 구조체의 특징이기 때문입니다. 

 

구조체는 멤버변수 중 가장 데이터 타입이 큰 멤버변수로 그리드를 구성하며, 그리드(grid)의 경계를 침범하지 않습니다. 

그리고 다음 멤버변수 데이터 타입의 따라 패딩(padding)이 할당됩니다. 

 

아래 그림으로 추가적으로 설명드리겠습니다. 

 

float a;는 앞의 double b; 크기의 맞추어 나머지 byte부분을  padding으로 채우며 해당 구조체의 그리드(grid)는 8byte 기준으로 해당 그리드의 double b;가 딱 맞게 채워집니다.

 

다음에 오는 멤버변수 char c;는 1byte를 채우고 나머지를 7byte로 padding을 채웁니다.

 

다음 예제로 추가적으로 설명드리도록 하겠습니다. 

 

1-2

#include <iostream>

typedef struct data {
	
	short c;
	int a;
	double b;
	
}Data;



int main()
{
	Data a;

	printf_s("%d", sizeof(a));
}

 몇byte가 출력될까요?

.

.

.

.

.

.

 

 

 위의 소스 코드의 답은 16byte입니다. 

 

그림으로 설명드리도록 하겠습니다. 

 

 

short c;는 다음 멤버변수 int a(4byte)의 크기에 따라서 4byte가 할당되며 나머지 byte는 padding 값으로 채워지게 됩니다. 

 

int a는 하나의 그리드(grid)를 채워 넣을 수 있는 크기로 padding 없이 크기를 할당하며, 나머지 double b;는 8byte를 할당합니다. 

 

 

 

2. 구조체의 주소

 

구조체의 멤버변수에 접근할 때 과연 C/C++언어는 어떻게 접근하는가? 라는 생각을 해보셨을 겁니다. 

 

작성한 소스코드와 디스어셈블러로 확인해보도록 하겠습니다.

 

먼저 구조체 Data a;를 1,2,3 순서로 초기화하는 코드를 보시고 디스어셈블러의 코드를 살펴보시길 바랍니다. 

 

1의 값을 eax 레디스터에 담은 후 멤버변수 a가 아닌 da구조체 주소에 접근하여 1을 복사하고 있습니다. 

 

즉, 구조체의 주소는 첫 번째 멤버변수를 뜻합니다. 두 번째 멤버변수는 ebp를 기준으로 -8을 하여 접근하며 세 번째 멤버변수는 -4를 통하여 접근하고 있습니다. 

 

 

여러분들의 이해를 돕고자 그림으로 설명드리도록 하겠습니다. 

 

 

스택의 주소는 낮을 수록 위를 향하며, 주소가 클 수록 ebp에 가까워집니다. 즉 주소를 두 번째, 세 번째 멤버변수는 ebp에서 - 한 값을 구하면 됩니다.!!

 

 

하지만, 구조체 포인터는 상대 주소로 표현됩니다. 

 

 

위 소스코드를 보시면, da2 구조체 주소를 ecx에 담고 ecx 즉 현재 구조체 주소를 기준으로 + 연산을 하여 멤버변수를 접근하는 어셈블리 코드를 확인할 수 있을겁니다. 

 

즉, 구조체가 포인터냐 또는 일반 구조체냐의 따라서 멤버변수 접근방법이 바뀝니다.

 

 

 

3. 구조체의 복사

 

구조체의 복사는 32bit 환경과 64bit 환경에 따라서 어셈블리 코드가 변경됩니다. 

 

먼저 32bit 환경에서의 어셈블리 코드를 확인해보겠습니다. 

 

32bit

32bit 환경에서의 복사는 각각 멤버변수의 주소에 따라서 복사 연산이 이루어지는 모습이 보여집니다. 

 

즉, 우리의 예상과 똑같이 멤버 대 멤버 복사가 이루어지는 모습입니다. 

 

하지만 64bit 환경에서는 조금 다릅니다. 

 

64bit

64bit 환경에서는 멤버 대 멤버가 아닌 byte 단위로 복사됩니다. 

 

노란색으로 밑줄 친 코드를 볼 경우 ecx의 값 만큼 byte 단위로 접근하여 반복하는 복사하는 경우입니다. 

 

이러한 경우는 정확히는 모르겠으나, 치명적인 단점이 한 가지 있습니다. 

 

바로 멀티스레드 환경입니다. 멀티스레드 환경에서는 만약 4byte 단위로 읽고 출력하는 과정에서

 

3번째 반복하여 3byte를 읽는 도중 동기화 오류가 발생될 경우 심각한 출력의 오류가 발생될 수 있기 때문입니다.!!!!

 

 

그렇기 때문에 64bit 환경에서는 꼭 아래와 같이 직접적으로 멤버변수를 대입하여 복사해주시길 바랍니다.

 

64bit


이번 포스팅은 구조체의 핵심 특징들을 설명해드렸습니다. 

 

2번째에서 구조체의 첫 번째 멤버변수는 구조체의 주소다 라고 언급을 하였는데,

이러한 특징으로 가장 많이 쓰이는 멤버변수는 첫 번째 멤버변수로 사용하는 것이 가장 좋습니다.

 

또한, 이렇게 구조체가 불필요한 패딩 공간을 확보하면서 까지 그리드 기준과 다음 멤버변수의 기준으로 맞추는 메모리의 크기를 맞추는 이유는 캐시 hit율을 높이기 위해서입니다. 

 

 

추가적인 연산없이 구조체 주소에 바로 접근하면 되기 때문입니다.!!

 

 

이상으로 IT디자이너였습니다.!!!!

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG more
«   2024/10   »
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
27 28 29 30 31
글 보관함