형변환은 암시적인 형변환(자동 형변환)과 명시적인 형변환(강제 형변환)이 있습니다. 암시적인 형변환은 컴파일러에 의해
자동으로 변환되고 명시적인 변환은 '()' 연산자를 사용하여 변환한다.
char형 변수(1byte)를 int형 변수(4byte)에 대입하는 경우 암시적인 형변환(자동 형변환)이 수행됩니다. 작은 자료형을
큰 자료형으로 변환하는 경우 데이터 손실이 없으므로 컴파일러는 자동으로 형변환을 수행합니다.
#include <stdio.h>
void main()
{
char c = 5;
int n;
n = c;
printf("%d\n", n);
}
결과 값 : 5
위의 코드는 변수 c와 n이 다른 자료형이지만 대입이 가능한 것을 볼 수 있습니다. 가능한 이유는 컴파일러가 내부적으로
형변환을 하고 있기 때문입니다. 이와 같이 내부적으로 형변환하는 것을 암시적인 형변환이라고 하고 프로그래머가 직접
형변환 연산자를 사용하여 변환하는 것을 명시적인 형변환이라 합니다. 중요한 사실은 모든 연산은 동일한 형(type)에서만
가능하다는 것입니다.
연산자 '()'를 사용하여 변수의 자료형을 다른 자료형으로 변환할 수 있고, '(변환 자료형)' 연산자를 사용하여 형변환을
하는 것을 명시적인 형변환이라 합니다. 예를 들어 '(char)'는 char형으로 자료형을 변환하라는 의미이고 '(char*)'은
char형 주소로 자료형을 변환하라는 의미입니다. '(int)'는 int형으로 자료형을 변환하라는 의미이고 '(int*)'은 int형 주소로
자료형을 변환하라는 의미입니다.
#include <stdio.h>
void main()
{
char c = 5;
int n;
n = (int) c;
printf("%d\n", n);
}
출력결과 : 5
'()' 연산자를 사용하여 char형 변수 c를 int형으로 명시적으로 형변환하여 변수 n에 대입하고 있습니다. char형 변수를
int형 변수로 형변환 한다는 것은 아래와 같이 두 자료형의 크기와 형태(의미)를 같게 하는 것입니다.
다음은 주소 형변환 부분입니다. 주소도 변수의 자료형과 마찬가지로 명시적인 형변환이 가능합니다.
#include <stdio.h>
void main()
{
char c = 'A';
printf("%x %x %x\n", &c, (char*)&c, (int*)&c);
printf("%x %x %x\n", &c + 1, (char*)&c + 1, (int*)&c + 1);
printf("%x %x %x\n", &c + 2, (char*)&c + 2, (int*)&c + 2);
printf("%x %x %x\n", &c + 3, (char*)&c + 3, (int*)&c + 3);
}
결과 값
0x0018 0x0018 0x0018
0x0019 0x0019 0x001c
0x001a 0x001a 0x0020
0x001b 0x001b 0x0024
'&c'에 '(char*)'와 '(int*)'를 붙여 각각 주소 형변환을 하고 있습니다. 이때 '(char*)'는 '&c'가 char형 주소이므로 두 형태는
같습니다. (&c == (char*)&c) 하지만 '(int*)'를 붙여 '&c'를 int형 주소로 강제 변환하고 이곳에 정수를 가산하면 주소가 4byte
씩 증가하는 것을 볼 수 있습니다. 위에서 공부한 것과 같이 int형 주소에 정수를 더하면 4byte씩 증가하게 됩니다.
다음은 이때까지의 내용을 토대로 응용을 해보겠습니다.
#define offsetof(TYPE, MEMBER) ( (size_t) &( (TYPE*)0->MEMBER ) )
위의 매크로는 리눅스 커널에서 실제로 사용되는 것입니다. offsetof는 특정 구조체 내부에 있는 멤버변수의 위치(offset값)를
알아내는데 사용하는 매크로 입니다. 사용예는 다음과 같은 구조체가 있을 경우
struct AAA
{
int a;
int b;
int c;
};
offsetof(struct AAA, b)를 할 경우 4가 됩니다.
상당히 복잡해보이는데 하나하나 해석을 해보자면 먼저 (TYPE*)은 TYPE의 형태로 주소 형변환을 하는 것입니다. 그러므로
(TYPE*) 연산자 뒤에는 주소가 나와야합니다. (TYPE*)0을 하게되면 0번지를 TYPE의 형태로 메모리를 잡는다고 알려집니다.
그렇게 되면 아래의 그림처럼 나타낼 수 있습니다.
그리고 여기에 (TYPE*)0->MEMBER를 하면, 아래의 그림처럼 접근을 할 수 있게 됩니다.
그래서 위의 코드 처럼 b의 offset값을 알려고 한다면 (struct AAA*)0->b가 되는데 여기에 &연산자를 사용하여
주소를 알아냅니다. 그러면 &((struct AAA*)0->b)는 0x0004인 것을 알 수 있습니다. 이 값을 size_t로 역시
형변환을 하게 되면 4가 됩니다. size_t는 보통 unsigned int로 정의되어 있습니다.