C의 타입 시스템은 언어의 초창기(1960년대 말 - 1970년대 초)에 등장했습니다. 엄격한 정적 타입화는 컴파일러가 프로그램 실행 이전에 변수, 표현식 및 반환 값의 타입 일치를 검사할 수 있도록 합니다.
문제의 역사:
정적 타입화는 실행 중에만 발견될 수 있는 오류를 사전에 방지하기 위해 도입되었습니다. 시간이 지나면서 C의 타입 시스템은 새로운 플랫폼과 프로그래밍 스타일을 지원하기 위해 점차 복잡해졌습니다.
문제:
타입 불일치는 예측할 수 없는 결과를 초래할 수 있습니다: 메모리 손상, 잘못된 계산, 프로그램의 비정상 종료. 정적 검사가 없으면 이러한 상황을 피하기가 어렵습니다.
해결책:
C 코드는 컴파일 단계에서 변수와 표현식의 타입을 검사합니다. 예를 들어, float* 타입의 변수에 int에 대한 포인터를 강제 형변환 없이 할당할 수 없습니다. 이는 많은 오류를 방지합니다.
코드 예:
int x = 5; double y = 3.14; y = x; // 암시적 타입 확장 int -> double int* p = &x; double* q = (double*)p; // 허용되지만 안전하지 않음!
주요 특징:
왜 C에서는 어떤 포인터든 void*로 캐스팅하고 다시 원래 타입으로 되돌릴 수 있나요?
C 표준은 어떤 타입의 포인터도 void로 캐스팅하고 다시 돌아올 수 있도록 보장합니다. 이는 예를 들어 표준 라이브러리 함수(malloc, memcpy)에서 사용됩니다. 그러나 void를 잘못된 타입으로 다시 캐스팅하면 정의되지 않은 동작이 발생합니다.
int와 float 간의 산술 연산에서 암시적 타입 변환은 어떻게 발생하나요?
C는 작은 크기의 타입을 더 넓은 타입으로 자동으로 "승격"시킵니다. 일반적으로 double이나 float로 변환됩니다. 예를 들어, int와 float를 더할 경우 int는 연산 전에 float로 변환됩니다.
int a = 10; float b = 2.5f; float c = a + b; // a는 먼저 float로 변환됨
void 포인터는 역참조할 수 없다는 것이 맞나요?
네, void 포인터는 정의되지 않은 타입을 가리키므로 직접적으로 역참조할 수 없습니다. 컴파일러가 타입의 크기를 알지 못하기 때문입니다. 역참조를 위해서는 특정 타입으로 캐스팅해야 합니다:
void* ptr = ...; int x = *(int*)ptr;
정확한 캐스팅 없이 void*를 받는 함수에 서로 다른 타입의 포인터를 전달:
void print_value(void* data) { printf("%d\n", *(int*)data); // 만약 data가 double*라면 오류 } double d = 1.5; print_value(&d); // 잘못됨
장점:
단점:
정적 타입화와 검사를 통한 명시적 변환의 사용:
void print_int(void* data) { if (data) { printf("%d\n", *(int*)data); } } int value = 42; print_int(&value);
장점:
단점: