C언어에서 자료형은 메모리에 값을 읽거나 쓸 때 발생할 수 있는 개발자의 실수를 막기 위한 기능도 가지고 있다.
즉, 컴파일러는 사용 크기나 형식이 다른 두 메모리가 값을 주고받으면 잠재적으로 문제가 있다고 판단하여 경고 또는 오류처리한다.
예를 들어, 아래와 같이 4 byte 크기의 변수 a값을 1바이트 크기 변수 b에 대입하는것은 문제가 있다고 생각하는것임.
int a = 50;
char b;
b = a;
위의 코드가 반드시 경고, 오류가 발생하는것은 아님
컴파일러의 설정이나 컴파일러의 종류에 따라서는 위 코드가 오류나 경고 없이 처리되기도 한다
개발자입장에서, 의도적으로 이걸 맞게 처리하고싶은 경우, 형변환 연산자를 사용하여 컴파일러에게 개발자의 의도를 정확하게 명시하면 경고, 또는 오류처리되지않는다.
우리는 이것을 ' 명시적 형 변환 ' 이라고 말 한다.
int a = 50;
char b;
b = (char)a; // 개발자의 명시적 형 변환 사용 -> 경고 또는 오류처리 되지않는다.
이제 형변환의 종류에 대해 알아보자
1. 일반 변수와 상수 간의 형 변환
자료형은 변수에만 적용된다고 생각하지만, 상수도 자료형을 가지고 있다.
예를 들어, 아래와 같이 적은 정수형태의 숫자상수는 int자료형으로 처리된다.
5, 300000, 050, 0x2f
아래와 같이 실수형태로 적은 숫자상수는 double자료형으로 처리된다.
5.0, 1.32e-5
따라서, 아래와 같은 상황은 경고나 오류처리
char c;
float f;
c = 1000;
f = 3.141592;
왜냐하면, 1000은 int형이고, 3.141592는 double형으로, c 와 f 각각에 대입되는 변수와 자료형이 맞지 않아 경고가 발생하게되는것이다.
그러나, 매번 경고처리가 뜨는것은 아니다.
int형이여도, 아래와 같이 char형의 범위 안에 속하면 경고처리 되지 않는다.
char c;
c = 10;
그러나, 실수의 경우 유효 자릿수로 값을 표현하기 때문에 아래와 같이 크기를 작게 적어도 경고는 계속 발생하기때문에 경고를 제거하려면 형 변환 연산자를 사용해야한다.
float f;
f = 3.14; // 경고발생
f = (float)3.14; //정상적으로 처리
그래서 실수의 경우 위와 같이 형변환을 사용하는것이 불편하기 때문에 아래와 같이 f를 붙여서 형변환 대신 사용이 가능하다.
float f;
f = 3.14f; // 경고 발생 안함
2. 변수와 변수간의 형 변환
서로 다른 크기나 형식이 다른 변수가 값을 주고받는 상황은 잠재적으로 경고 또는 오류상황이다.
따라서, 아래와 같은 상황은 경고 또는 오류처리 될 수 있다.
그런데, int자료형을 char 자료형에 넣는 경우 현재 컴파일러의 기본 설정값으로는 오류처리 되지 않는다.
char c;
int i = 30000 ;
float f;
double d = 3.141592;
c = i; // 4바이트에서 1바이트로 줄어듦 손실 경고해야하지만 경고안함
f = d; // 8바이트 실수에서 4바이트 실수로 줄어듦 (손실경고)
i = d; // 실수에서 정수로 형식이 바뀜 (손실경고)
i = f; // 실수에서 정수로 형식이 바뀜 (손실경고)
f = i; // 정수에서 실수로 형식이 바뀜 (손실경고)
// 실수가 정수의 범위를 포함하지만 float은 int와 크기가 동일한 자료형이라서
// int의 정수범위를 float는 포함하지 못함
d = i; //정수에서 실수로 형식이 바뀌지만, double 자료형 범위에 포함되어 경고 안 함
실수가 정수가 될 때는 소숫점 이하의 값이 사라지게됨 -> 손실
그러나, 실수의 범위가 정수의 범위를 포함하기 때문에 경고가 발생하지 않기도 함
같은 크기의 자료형인 int와 float은 float 자료형에서 정수부가 int크기를 포함하지 못하기 때문에 경고처리되지만,
8byte크기인 double자료형의 경우에는 정수부가 int크기를 포함하기때문에 경고 처리되지 않는다.
형 변환 연산자를 사용하는 규칙
컴파일러 종류나 설정에 따라 간단한 경고상황은 무시하는 경우도 있음
그러나 이런것때문에 버그가 생겨 고생하니까 그냥 형변환 연산자를 사용해서 확실하게 해주는것이 더 좋다
형변환 연산자를 사용해야하는 상황
형 변환은 연산자를 기준으로 양쪽에 있는 자료형이 다르면 사용해야 한다.
int num = 3000;
int temp;
temp = (int)(int)num; // 첫번째 int는 temp변수의 자료형, 두번째 int는 num의 자료형
temp = num; // 생략가능
두개의 자료형이 일치하기 떄문에 둘 다 생략할 수 있다.
그러나 서로 다른 자료형이 대입된다면 ?
char 형 변환 연산자는 생략하지 못하고, 아래의 코드처럼 int자료형만 생략할 수 있다.
int num = 3000;
char c;
c = (char)(int)num;
c = (char)num; //int형은 생략가능
3. 포인터 변수 간의 형 변환
아래의 그림처럼 동일한 형식의 포인터간에 주소를 대입하는 경우 자료형이 동일함
따라서, 두 형변환 연산자는 모두 생략이 가능하다.
int *p_num;
int *p_temp;
p_temp = (int*)(int*)p_num;
그러나, 아래와 같이 두 포인터 변수의 자료형이 일치하지 않는다면 위에서 했던 것 처럼 p_temp 변수의 자료형에 해당하는 형 변환 연산자는 그대로 두면 됨
int *p_num;
char *p_temp;
p_temp = (char *) p_num;
그리고 *를 사용하게 되면 포인터 변수의 자료형이 아닌 두 포인터 변수가 가리키는 대상의 자료형을 의미하기 때문에 포인터 변수의 자료형에서 *가 하나 줄어든다고 생각하면 됨
예를 들어, 아래와 같이 선언된 3단계 포인터 p가 있다면, p의 자료형은 int *** 이지만
*p는 포인터 p가 가리키는 대상이 되기 떄문에 포인터 변수 p의 자료형에서 *가 하나 줄어든 int **가 된다는 의미임
int ***p;
*p = ???; // *p연산의 자료형은 int **
**p = ???; // **p연산의 자료형은 int *
***p = ???; // ***p 연산의 자료형은 int
따라서, 아래와 같은 대입 연산에서 포인터 변수가 사용되었다면,char ** 와 int ** 가 아닌 char * 와 int * 이라는 뜻임
int **p_num;
char **p_temp;
*p_temp = (char *)(int *) *p_num;
*p_temp = (char *)*p_num; // (int *) 생략해도됨
+
일반 정숫값을 포인터변수에 대입하는 상황을 적어보자
int *p_temp;
p_temp = (int *)(int)1000;
P_temp = (int *)1000; // (int) 생략 가능
변수의 주소를 얻기 위해, &를 사용하면 결과값이 주소가 나오기 떄문에, 이 값을 포인터 변수에 저장해야 한다.
따라서, &연산자를 사용한 변수의 자료형을 가리키는 포인터 형식과 동일한 자료형이 적용된다.
예를 들어, int 형식의 변수에 &연산자를 사용하면 결과값의 자료형은 int *가 되고, int *변수에 &연산자를 사용하면 결과값은 int ** 자료형을 가지게 된다는 의미
아래의 코드처럼 int형 변수 num 의 주소를 int * 변수에 저장하는 상황
int num;
int *p_temp;
p_temp =(int *)(int *) #
따라서, 위의 코드는 두 자료형이 동일하기떄문에 둘 다 생략해서 아래와 같이 적을 수 있다.
int num;
int *p_temp;
p_temp = #
만약 int변수의 주솟값을 short * 자료형을 사용하는 포인터에 대입했다면 아래와 같이 자료형이 서로 달라지게됨
int num;
short *p_temp;
p_temp = (short *)(int* )#
p_temp = (short *)#