[C] union(공용체) 이란
union(공용체)이란
C언어에서 union은 하나의 메모리 공간을 여러 가지 방식으로 해석할 수 있도록 하는 데이터 타입입니다. 이는 구조체와 유사하지만 구조체는 각 멤버 변수가 메모리 공간을 따로 사용하는 반면, union은 모든 멤버 변수가 같은 메모리 공간을 공유합니다.
실제 사용 예시는 다음과 같습니다. ADC_CONFIG는 구조체이고 HADC는 공용체입니다.
#include <stdio.h>
typedef struct
{
unsigned char CNFG1;
unsigned char CNFG2;
unsigned short MODE;
} ADC_CONFIG;
typedef union
{
ADC_CONFIG cnfg;
unsigned char byte[4];
} HADC;
int main(){
ADC_CONFIG adc1 = {0x01, 0x80, 0xF000};
printf("strct ADC_CONFIG size: %d\n", sizeof(ADC_CONFIG));
HADC a1;
printf("union HADC size: %d\n", sizeof(a1));
return 0;
}
main문에서 구조체 adc1과 공용체 a1의 크기를 출력하였습니다. 결과는 아래와 같습니다.
strct ADC_CONFIG size: 4
union HADC size: 4
공용체는 메모리 공간을 공유하기 때문에 메모리 크기의 변화가 없습니다. 공용체의 메모리 크기는 가장 큰 자료형에 의해 결정됩니다. ADC_CONFIG의 크기와 unsigned char byte[4]의 크기는 모두 4바이트로 동일하고 따라서 공용체의 크기도 4가 됩니다.
차이점은 공용체를 사용하면 아래 코드와 같은 방식으로 메모리 접근이 가능합니다.
#include <stdio.h>
typedef struct
{
unsigned char CNFG1;
unsigned char CNFG2;
unsigned short MODE;
} ADC_CONFIG;
typedef union
{
ADC_CONFIG cnfg;
unsigned char byte[4];
} HADC;
int main(){
ADC_CONFIG adc1 = {0x01, 0x80, 0xF000};
printf("strct ADC_CONFIG size: %d\n", sizeof(ADC_CONFIG));
HADC a1;
printf("union HADC size: %d\n", sizeof(a1));
// cnfg를 통한 메모리 접근
a1.cnfg.CNFG1 = 0x11;
a1.cnfg.CNFG2 = 0x22;
a1.cnfg.MODE = 0xFBCD;
printf("CNFG1: 0x%X, CNFG2: 0x%X, MODE: 0x%X\n", a1.cnfg.CNFG1, a1.cnfg.CNFG2, a1.cnfg.MODE);
// bytep[4]를 통한 바이트 단위 메모리 접근
a1.byte[0] = 0x33;
a1.byte[1] = 0x44;
a1.byte[2] = 0x55;
a1.byte[3] = 0x66;
printf("CNFG1: 0x%X, CNFG2: 0x%X, MODE: 0x%X\n", a1.cnfg.CNFG1, a1.cnfg.CNFG2, a1.cnfg.MODE);
return 0;
}
즉, 구조체로 메모리에 접근할 수도 있고 바이트 단위로 메모리에 접근하는 것도 가능합니다.
임베디드 시스템에서의 union(공용체) 사용
union은 임베디드 시스템에서 레지스터 주소를 정의할 때 사용됩니다. 예시와 함꼐 살펴보겠습니다.
/** \brief 10, Port Input/Output Control Register 0 */
#define P10_IOCR0 /*lint --e(923)*/ (*(volatile Ifx_P_IOCR0*)0xF003B010u)
/** \brief Port Input/Output Control Register 0 */
typedef union
{
unsigned int U; /**< \brief Unsigned access */
signed int I; /**< \brief Signed access */
Ifx_P_IOCR0_Bits B; /**< \brief Bitfield access */
} Ifx_P_IOCR0;
/** \brief Port Input/Output Control Register 0 */
typedef struct _Ifx_P_IOCR0_Bits
{
unsigned int reserved_0:3; /**< \brief \internal Reserved */
unsigned int PC0:5; /**< \brief [7:3] (rw) */
unsigned int reserved_8:3; /**< \brief \internal Reserved */
unsigned int PC1:5; /**< \brief [15:11] (rw) */
unsigned int reserved_16:3; /**< \brief \internal Reserved */
unsigned int PC2:5; /**< \brief [23:19] (rw) */
unsigned int reserved_24:3; /**< \brief \internal Reserved */
unsigned int PC3:5; /**< \brief [31:27] (rw) */
} Ifx_P_IOCR0_Bits;
위의 예시에서 Ifx_P_IOCR0는 union으로 정의되어 있어서 U, I 혹은 B로 메모리에 접근할 수 있습니다.
Ifx_P_IOCR0_Bits는 비트 필드를 사용하여 비트 단위로 멤버를 만들었습니다.Ifx_P_IOCR0_Bits는 아래와 같은 레지스터 구조를 코드로 나타낸 것입니다.
처음 3비트는 reserved_0으로 그 다음 5비트는 PC0로, 그 다음 3비트는 reserved_1으로 다음 5비트는 PC1, 그 다음 3비트는 reserved_2으로 다음 5비트는 PC2, 그 다음 3비트는 reserved_3으로 다음 5비트는 PC3으로 정의됐습니다.
이렇게 정의한 경우 아래 코드와 같이 특정 비트 영역을 접근하는 게 가능합니다.
/** \brief 10, Port Input/Output Control Register 0 */
#define P10_IOCR0 /*lint --e(923)*/ (*(volatile Ifx_P_IOCR0*)0xF003B010u)
/** \brief Port Input/Output Control Register 0 */
typedef union
{
unsigned int U; /**< \brief Unsigned access */
signed int I; /**< \brief Signed access */
Ifx_P_IOCR0_Bits B; /**< \brief Bitfield access */
} Ifx_P_IOCR0;
/** \brief Port Input/Output Control Register 0 */
typedef struct _Ifx_P_IOCR0_Bits
{
unsigned int reserved_0:3; /**< \brief \internal Reserved */
unsigned int PC0:5; /**< \brief [7:3] (rw) */
unsigned int reserved_8:3; /**< \brief \internal Reserved */
unsigned int PC1:5; /**< \brief [15:11] (rw) */
unsigned int reserved_16:3; /**< \brief \internal Reserved */
unsigned int PC2:5; /**< \brief [23:19] (rw) */
unsigned int reserved_24:3; /**< \brief \internal Reserved */
unsigned int PC3:5; /**< \brief [31:27] (rw) */
} Ifx_P_IOCR0_Bits;
int main(){
P10_IOCR0.B.PC0 = 0x10;
P10_IOCR0.B.PC1 = 0x10;
P10_IOCR0.B.PC2 = 0x10;
P10_IOCR0.B.PC3 = 0x10;
return 0;
}
임베디드 시스템에서는 하드웨어 모듈을 사용하기 위해 특정 메모리 영역의 특정 비트에 접근할 일이 많기 때문에 다음과 같이 정의하여 사용하는 경우가 많습니다.