부록 C — 암묵 변환 요약
29장의 규칙을 표로 압축한 것이다.
정수 승격
bool, char, signed char, unsigned char, short, unsigned short, 비트 필드가 산술에 참여하면:
- 값들이 전부
int에 담기면 →int - 아니면 →
unsigned int
즉 산술은 int보다 좁은 타입 위에서 일어나지 않는다.
통상 산술 변환 (두 피연산자의 공통 타입)
- 한쪽이
long double→ 둘 다long double - 아니고 한쪽이
double→ 둘 다double - 아니고 한쪽이
float→ 둘 다float 둘 다 정수 → 각각 정수 승격 후:
- 부호가 같으면 → 더 넓은 쪽
- 부호 없는 쪽이 더 넓거나 같으면 → 부호 없는 쪽
- 부호 있는 쪽이 더 넓고 상대 값을 다 담으면 → 부호 있는 쪽
- 그 밖 → 부호 있는 쪽의 부호 없는 판
실무 요약: 섞이면 넓은 쪽으로, 폭이 같으면 부호 없는 쪽이 이긴다. 부호 섞인 비교는 피한다(경고를 켜 두면 컴파일러가 짚어 준다).
가변 인자의 기본 진급
원형에 타입이 없는 자리(...)로 넘어가는 인자:
float→doublebool·char·short계열 →int(또는unsigned int)
그래서 printf에 float 전용 서식이 없고, %f가 double을 받는다 (19·25·58장).
정수 변환 등급 (변환 규칙의 기준자)
통상 산술 변환이 “더 넓은 쪽”을 고를 때 쓰는 순서다. 위가 높다.
long long > long > int > short > char / signed char / unsigned char > bool세 가지를 구별해 두어야 한다.
① char·signed char·unsigned char는 서로 등급이 같다. 셋은 폭이 같으므로 하나의 칸에 든다.
② bool의 등급은 그보다 낮다. 표준이 bool을 모든 부호 있는 정수 타입보다 낮은 등급으로 못박았다.
③ 등급은 폭이 아니다. 같은 폭이면 부호 없는 쪽과 부호 있는 쪽의 등급은 같고, 그때 통상 산술 변환에서 부호 없는 쪽이 이긴다. 그래서 -1 < 1u가 거짓이 된다 — -1이 거대한 부호 없는 값으로 바뀌기 때문이다(29장). 폭이 같아도 등급이 다를 수 있고(구현이 확장 정수 타입을 두는 경우), 등급이 같아도 부호가 결과를 가른다.
어떤 변환이 어떤 위험을 부르는가
| 변환 | 일어나는 일 | 위험 |
|---|---|---|
| 부호 있는 → 부호 없는 | 2의 보수 값이 그대로 재해석된다 | 음수가 거대한 값이 된다 |
| 부호 없는 → 부호 있는 | 담기면 그대로, 아니면 구현 정의 | 경계에서 값이 바뀐다 |
| 넓은 정수 → 좁은 부호 없는 정수 | 로 나눈 나머지(정의됨) | 조용한 값 손실 |
| 넓은 정수 → 좁은 부호 있는 정수 | 들어가면 그대로, 아니면 구현 정의 | C23 에서도 그렇다 |
| 정수 → 실수 | 가까운 값으로 반올림 | 큰 정수에서 정밀도 손실(8장) |
| 실수 → 정수 | 소수부를 버린다(0 방향) | 범위 밖이면 계약 밖 |
double → float | 정밀도를 줄인다 | 반올림 오차 |
포인터 → void * | 무손실 | 타입 정보만 잃는다 |
void * → 다른 포인터 | 무손실 | 정렬이 맞지 않으면 계약 밖(37장) |
| 정수 → 포인터 | 구현 정의 | 출처(provenance)를 잃는다(14장) |
표 98.1
조용한 변환이 일어나는 자리 목록
암묵 변환은 다음 자리에서 묻지 않고 일어난다.
- 산술 연산의 두 피연산자 사이(통상 산술 변환)
- 대입의 오른쪽 → 왼쪽 타입
- 함수 인자 → 매개변수 타입(원형이 있을 때)
return의 값 → 반환 타입- 조건 자리(
if,while,?:의 조건) →bool판정 ...로 넘어가는 인자(기본 진급)- 배열·함수 이름이 값으로 쓰일 때(포인터로 무너짐)
그 밖의 자주 만나는 변환
- 축소(넓은 → 좁은 정수): 부호 없는 목적 타입이면 으로 나눈 나머지가 되고(정의된 동작), 부호 있는 목적 타입이면 값이 들어갈 때만 그대로이고 아니면 구현 정의다(7장). 실제 구현이 대개 상위 비트를 버리는 것은 사실이지만, 그것은 구현의 선택이지 언어의 보장이 아니다 — C23이 2의 보수를 못박은 뒤에도 이 조항은 그대로다.
- 실수 → 정수: 소수부를 버린다(0을 향해). 범위를 벗어나면 계약 밖이다.
- 배열 → 포인터: 대부분의 문맥에서 첫 원소 주소로 무너진다. 예외는
sizeof의 피연산자일 때, 단항&의 피연산자일 때, 그리고 문자열 리터럴로 배열을 초기화할 때다(38·42장).alignof는 타입 이름만 받으므로 이 목록에 들어가지 않는다. - 함수 → 포인터: 함수 이름은 함수 포인터로 무너진다.