구조체가 메모리에 올라갈 때의 이용과 구조체와 정의 방법은 같지만 멤버 지정방식이 다른 공용체에 대해 정리해보겠습니다.
구조체의 사용과 활용 | 구조체 멤버 정렬 구조체 메모리 활용 ( 초기화, 복사 ) 구조체 배열 사용 |
공용체의 사용 | 공용체의 시작 구조체와 공용체의 활용 |
구조체의 사용과 활용
구조체가 메모리에 올라갔을 때 멤버를 정렬하는 기능으로 메모리에 접근할 때 비트에 따라 바이트 단위가 다르게 접근됩니다.
32비트 CPU일 때는 4바이트 단위, 64비트 CPU는 8바이트 단위로 접근합니다.
32비트 CPU에서 4바이트보다 작은 데이터에 접근할 경우 내부적으로 시프트 연산이 발생하여 효율이 떨어진다 합니다. 그렇기에 효율성이 좋도록 적절한 데이터 크기인 2, 4, 8, 16바이트 형식으로 정렬을 하게 됩니다.
정렬을 하기 전 크기를 알아야 하는데 sizeof연산을 통해 알 수 있습니다.
sizeof(struct 구조체)
sizeof(구조체 별칭)
sizeof(구조체 변수)
크기를 확인하면 구조체 전체 크기는 예상한 것과는 조금 크게 나올 수 있습니다.
C언어에서 구조체를 정렬할 때 멤버 중 가장 큰 자료형의 크기를 배수로 하여 정렬하기 때문입니다.
정렬하여 남는 공간은 패딩 형식으로 들어가게 되고, 패딩은 공간을 맞추기 위해 남는 공간을 채우는 것을 말합니다.
구조체를 정렬한 뒤 멤버의 위치가 어디에 위치해있는지 확인하기 위해서 offsetof 매크로를 사용하면 됩니다. stddef.h 헤더 파일에 정의되어 있으며 아래의 형식으로 되어있습니다.
offsetof(struct 구조체, 멤버)
offsetof(구조체 별칭, 멤버)
offsetof 매크로에 구조체와 멤버를 지정하면 구조체에서 해당 멤버의 상대 위치가 반환되어 위치를 알 수 있습니다. 여기서, 첫 멤버의 상대 위치는 0입니다.
데이터 전송이나 저장을 할 때 구조체 정렬 크기를 조절할 수도 있는데 각 컴파일러에서 제공하는 특별한 지시자를 사용하면 됩니다. Visual Studio, GCC 4.0 이상, GCC 4.0 미만으로 나뉘며 아래와 같습니다.
// Visual Studio, GCC 4.0 이상
#pragma pack(push, 정렬크기) /* 정렬 크기로 정렬 */
#pragma pack(pop) /* 정렬 설정을 이전 상태(기본값)로 되돌림
// GCC 4.0 미만
__attribute__((aligned(정렬크기), packed))
구조체를 정의할 때 위아래로 나누어 넣어주면 됩니다.
정렬 크기를 1로 설정하여 1바이트로 지정하면 1바이트 단위로 정렬하게 되므로 남는 공간 없이 자료형 크기 그대로 메모리에 올라가 구조체 전체 크기는 자료형 크기만큼 나오게 되는 것입니다.
정렬의 크기를 조정한 후, #pragma pack(pop)을 사용하여 아래에 오는 구조체에 영향이 가지 않게 해주어야 합니다.
GCC버전이 4.0 미만이라면 구조체가 끝나는 세미콜론(;) 앞에 해당 지시자를 넣어주면 됩니다.
구조체도 메모리 공간을 차지하므로 메모리 관련 함수도 사용이 가능합니다. 그렇기에 메모리 함수를 사용하여 구조체와 메모리에 값을 설정하는 방법과 구조체와 메모리 내용을 복사하는 것을 보겠습니다.
구조체의 멤버를 모두 0으로 만들 수 있는데 각 멤버에 접근하여 0의 값을 하기보다는 쉽게 아래와 같이 중괄호({})를 이용하여 0으로 초기화하거나 memset 함수를 통해 메모리 내용을 한꺼번에 값을 설정하면 됩니다.
// 직접 접근
구조체변수.멤버 = 0;
구조체변수.멤버 = 0;
구조체변수.멤버 = 0;
// 초기화
struct 구조체이름 변수이름 = { 0, };
// 한번에 값 설정
memset(구조체 포인터, 설정할 값, sizeof(struct 구조체));
memset함수는 string.h 헤더 파일에 선언되어 있고, 구조체 변수의 값을 설정할 때 &구조체와 같이 주소 연산자(&)를 사용하여 변수 메모리 주소를 구해 넣어줍니다.
여기서 포인터로 할당하였으면 메모리 주소를 담고 있기에 주소 연산자(&)를 사용하지 않아도 됩니다.
다른 내용의 구조체 내용을 복사할 때 사용하는 함수는 memcpy함수를 사용하면 됩니다. memory copy에서 따왔으며 string.h 헤더 파일에 선언되어 있습니다.
memcpy(목적지 포인터, 원본 포인터, 크기);
- void *memcpy(void *_Dst, void const *_Src, size_t_Size);
- 목적지 포인터를 반환
목적지 포인터와 원본 포인터의 위치를 확인해야 하며 주소 연산자의 사용은 위의 함수와 같습니다.
구조체에서 배열을 이용하여 1차원, 2차원 표현도 가능합니다.
일반적으로 많은 요소를 지정할 때 사용하는 대괄호([])를 이용하여 선언할 수 있습니다. 구조체 배열은 변수 이름 뒤에 대괄호를 붙여 크기를 지정하면 됩니다.
struct 구조체이름 변수이름[크기];
구조체 배열에 접근하기 위해 인덱스를 지정하면 접근이 가능합니다.
구조체를 선언하는 동시에 인덱스에 해당하는 값을 초기화할 수 있는데 아래와 같이 진행됩니다. /* 배열[인덱스].멤버 */
struct 구조체이름 변수이름[크기] = {{.멤버이름1 = 값, .멤버이름2 = 값}, {.멤버이름1 = 값, .멤버이름2 = 값}};
struct 구조체이름 변수이름[크기] = {{값, 값}, {값, 값}};
배열이 아닌 요소마다 메모리를 할당하고자 할 때는 구조체 포인터 배열을 만들고 malloc함수로 각 요소에 메모리를 할당하면 됩니다.
struct 구조체이름 *포인터이름[크기];
메모리를 할당할 때는 배열의 크기만큼 반복하면서 각 요소에 구조체 크기만큼 메모리를 할당해주면 됩니다. 배열의 크기를 구하려면 구조체 포인터 배열의 전체 크기에서 구조체 포인터의 크기로 나눠주면 됩니다. [sizeof(포인터변수)/sizeof(struct 구조체이름 *)]
sizeof(struct 구조체이름)은 구조체가 차지하는 크기이고, sizeof(struct 구조체이름 *)는 구조체 포인터의 크기입니다.
배열의 요소에 접근하기 위해서는 배열 안에 들어있는 요소가 포인터이므로 화살표 연산자(->)를 이용하여 멤버에 접근합니다. 또한, 동적 할당이기에 할당받은 메모리는 free함수로 해제하여야 합니다.
공용체 사용
공용체는 구조체와 정의 방법이 같지만 멤버의 지정방식이 다릅니다. 공용체는 모든 멤버가 공간을 공유하기 때문입니다.
즉, 공용체는 멤버 중 가장 큰 자료형의 공간을 공유하고 있습니다. 공용체를 만들기 위해서는 union키워드를 사용하여 정의합니다.
union 공용체이름 {
자료형 멤버이름;
};
공용체의 변수 선언은 [union 공용체이름 변수이름;] 형식입니다.
sizeof연산으로 공용체를 구해보면 공용체의 크기는 아래와 같이 확인할 수 있습니다. 해당 공용체의 멤버 중 가장 큰 자료형의 크기가 byte단위로 나옵니다.
sizeof(union 공용체)
sizeof(공용체변수)
여기서 주의할 점은 공유 형식이기에 어느 한 멤버에 값을 저장하면 나머지 멤버의 값은 사용할 수 없는 상태가 됩니다.
그렇기에 공용체의 멤버는 한 번에 하나씩 사용하여야 정상적으로 사용할 수 있습니다. 이러한 특성으로 여러 멤버에 동시에 접근하지 않는 경우 같은 메모리 레이아웃에 멤버를 모아둘 때 사용합니다. 특히 임베디드 시스템이나 커널 모드 디바이스 드라이버 등에서 주로 사용합니다.
공용체 멤버에 값을 저장할 때 리틀 엔디언 방식으로 저장됩니다. 리틀 엔디언은 1바이트씩 앞의 수는 뒤에 저장되고, 뒤의 수는 앞에 저장되는 방식입니다. 그렇기에 저장한 값을 전체를 불러오는 것이 아니라면 리틀 엔디언 방식으로 저장된 형식으로 출력됩니다.
동적 할당방식으로 값을 할당할 수 있는데 구조체와 같게 진행하면 됩니다. 또한 여기서 이중 구조체 방식인 구조체 안에 구조체나 공용체가 들어갈 수 있고, 공용체 또한 이중으로 구조체나 공용체가 들어갈 수 있습니다.
구조체 변수 안에서 해당 구조체나 공용체의 변수를 선언하면 사용할 수 있습니다. 멤버 접근방법과 초기화 방법은 아래와 같습니다.
// 멤버 접근
변수.멤버.멤버
// 구조체 변수를 선언하면서 안에 들어있는 구조체 초기화
struct 구조체이름 변수이름 = {값, 값, {값, 값}};
위의 멤버 접근 방식으로 동적 할당에서도 이용하여 값을 할당하거나 접근할 수 있습니다. 안의 구조체를 동적 할당할 때는 구조체 안에서 변수 선언할 때 구조체 포인터 형식으로 선언하고 화살표 연산자를 이용하여 동적 할당한 것에 또 동적 할당하는 방식으로 선언하면 됩니다.
즉, 접근하기 위해서는 포인터->포인터->멤버 형태의 모양입니다.
이상으로 구조체와 공용체의 활용에 대해 알아보았습니다. 감사합니다!
'TeamH4C_ProjectH4C > 프로그래밍 언어' 카테고리의 다른 글
[TeamH4C_C언어] 열거형과 자료형 변환 (0) | 2021.04.11 |
---|---|
[TeamH4C_C언어] 구조체 추가 정리(비트 필드) (0) | 2021.04.11 |
[TeamH4C_C언어] 문자열과 구조체 (0) | 2021.03.14 |
[TeamH4C_C언어] 포인터를 활용한 문제 (0) | 2021.03.06 |
[TeamH4C_C언어] 포인터의 활용 (0) | 2021.03.06 |