캐스트 연산자(cast operator)
캐스트 연산자(cast operator)
어떤 값의 타입을 다른 타입으로 명시적으로 바꾸고 싶을 때 쓰는 것이 캐스트(cast)예요. (type-name)expression 형태로, 괄호 안에 목표 타입을 적고 뒤에 값을 붙이죠. C의 캐스트는 스칼라 타입 사이의 명시적 타입 변환을 수행해요.
출처: cppreference
본문
문법
( type-name ) expression |
| 자리 | 의미 |
|---|---|
type-name |
void 타입이거나 어떤 스칼라 타입. |
expression |
스칼라 타입의 모든 표현식. (type-name이 void라면 어떤 것이든 될 수 있어요.) |
동작 방식(Explanation)
type-name이 void라면, expression은 그 부수 효과(side effect)를 위해 평가되고 반환 값은 버려져요. 표현식 문(expression statement)으로서 그대로 쓰는 것과 같죠.
그렇지 않고 type-name이 expression의 타입과 정확히 같다면 아무것도 하지 않아요. (단, expression이 부동 소수점 타입이면서 그 타입이 가리키는 것보다 더 큰 범위와 정밀도로 표현되는 경우는 예외예요 — 아래 참고.)
그 외의 경우, expression의 값은 type-name이 가리키는 타입으로 다음과 같이 변환돼요.
대입에 의한 것처럼 모든 암시적 변환이 허용돼요.
암시적 변환 외에 다음 변환들도 허용돼요.
- 임의의 정수는 임의의 포인터 타입으로 캐스트할 수 있어요.
NULL같은 널 포인터 상수(캐스트가 필요 없어요)를 제외하면, 결과는 구현 정의이고, 올바르게 정렬되어 있지 않을 수 있으며, 참조된 타입의 객체를 가리키지 않을 수 있고, 트랩 표현(trap representation)일 수도 있어요. - 임의의 포인터 타입은 임의의 정수 타입으로 캐스트할 수 있어요. 결과는 구현 정의예요. 널 포인터 값조차도 반드시 0이 되는 건 아니에요. 결과가 대상 타입에 표현될 수 없다면 동작은 미정의예요 (부호 없는 정수는 포인터에서 오는 캐스트에 모듈로 산술을 적용하지 않아요).
- 임의의 객체 포인터는 임의의 다른 객체 포인터로 캐스트할 수 있어요. 값이 대상 타입에 맞게 올바르게 정렬되어 있지 않다면 동작은 미정의예요. 그렇지 않고 값이 원래 타입으로 다시 변환되면 원래 값과 비교했을 때 같아요. 객체 포인터를 어떤 문자 타입 포인터로 캐스트하면, 그 결과는 객체의 가장 낮은 바이트를 가리키고 대상 타입의
sizeof만큼 증가시킬 수 있어요. 다른 말로 하면 객체 표현을 들여다보거나memcpy/memmove로 복사본을 만드는 데 쓸 수 있죠. - 임의의 함수 포인터는 임의의 다른 함수 타입 포인터로 캐스트할 수 있어요. 결과 포인터를 원래 타입으로 다시 변환하면 원래 값과 비교했을 때 같아요. 변환된 포인터로 함수 호출을 하면 동작은 미정의예요 (함수 타입이 호환되지 않는 경우).
- 포인터 사이(객체든 함수든)를 캐스트할 때, 원래 값이 자기 타입의 널 포인터 값이라면 결과는 대상 타입의 올바른 널 포인터 값이에요.
어떤 경우든(암시적 변환을 실행할 때와 동일 타입 캐스트 모두), expression과 type-name이 모두 부동 소수점 타입이고 expression이 그 타입이 가리키는 것보다 더 큰 범위와 정밀도로 표현된다면(FLT_EVAL_METHOD 참고), 그 범위와 정밀도는 대상 타입에 맞게 벗겨져요.
캐스트 표현식의 값 범주(value category)는 항상 비-lvalue(non-lvalue)예요.
주의할 점(Notes)
const, volatile, restrict, _Atomic 한정자는 lvalue에만 영향을 주기 때문에, cvr-한정 또는 원자 타입으로의 캐스트는 대응하는 비한정 타입으로의 캐스트와 정확히 동등해요.
void로의 캐스트는 때때로 사용되지 않는 결과에 대한 컴파일러 경고를 잠재우는 데 유용해요.
여기에 나열되지 않은 변환은 허용되지 않아요. 특히
- 포인터와 부동 소수점 타입 사이에는 변환이 없어요.
- 함수 포인터와 객체 포인터(
void*포함) 사이에는 변환이 없어요.
구현이 intptr_t와/또는 uintptr_t를 제공한다면, 객체 타입 포인터(cv void 포함)에서 이 타입들로의 캐스트는 항상 잘 정의돼요. 그러나 함수 포인터에는 보장되지 않아요. (C99부터)
함수 포인터와 객체 포인터 사이의 변환은 많은 컴파일러가 확장으로 받아들이고, POSIX의 dlsym() 함수의 일부 용법에서 기대되기도 해요.
예제
객체 표현을 들여다보는 것은 캐스트의 정당한 용도예요. 그리고 스칼라 타입이 아닌 타입은 캐스트할 수 없다는 점(그래도 void로는 캐스트 가능)을 확인해 볼게요.
#include <stdio.h>
int main(void)
{
// examining object representation is a legitimate use of cast
double d = 3.14;
printf("The double %.2f (%a) is: ", d, d);
for (size_t n = 0; n < sizeof d; ++n)
printf("0x%02x ", ((unsigned char*)&d)[n]);
// edge cases
struct S { int x; } s;
// (struct S)s; // error; not a scalar type
// even though casting to the same type does nothing
(void)s; // okay to cast any type to void
}
가능한 출력:
The double 3.14 (0x1.91eb851eb851fp+1) is: 0x1f 0x85 0xeb 0x51 0xb8 0x1e 0x09 0x40
double인 d를 unsigned char*로 캐스트한 뒤 각 바이트를 순서대로 읽어 메모리에 실제로 저장된 비트 표현을 출력해요. (struct S)s처럼 스칼라가 아닌 타입으로의 캐스트는 오류이고, (void)s처럼 어떤 타입이든 void로 캐스트하는 것은 괜찮아요.
더 알아보기
- 암시적 변환(Implicit conversions): 대입 등에서 자동으로 일어나는 타입 변환.
intptr_t/uintptr_t: 포인터 값을 온전히 담을 수 있는 정수 타입.