← 문제로

37. 정수 승격과 통상 산술 변환, signed/unsigned 혼합의 함정

난이도 중
내 리뷰 · C++
해설 · C++

해설 — 정수 승격과 통상 산술 변환, signed/unsigned 혼합의 함정

난이도: 중상

요약

이항 산술/비교 연산은 피연산자를 공통 타입으로 맞춘 뒤 수행한다. 규칙은 두 단계: (1) 정수 승격bool/char/shortint 보다 작은 정수는 먼저 int(값이 다 안 들어가면 unsigned int)로 승격. (2) 통상 산술 변환 — 남은 두 타입 중 랭크·부호에 따라 공통 타입 결정. 같은 랭크에서 signed 와 unsigned 가 섞이면 signed 가 unsigned 로 변환된다. 이 마지막 규칙이 음수를 거대한 양수로 바꿔 비교를 뒤집는다.

예측 출력:

0            // (1) i < u
300          // (2) c1 + c2
1            // (3) s > 0u
4            // (4) sizeof(true+true)
2            // (5) true + true
4294967286   // (6) a + b
1            // (7) 'a' < 'b'

핵심 개념 · 단계별 내부 동작

  • (1) i < u → 0(false): int -1unsigned 1. 같은 랭크라 intunsigned 로 변환 → -14294967295. 4294967295 < 1 은 거짓. 직관("-1 이 1 보다 작다")과 반대다. 컴파일러가 -Wsign-compare 로 경고하는 대표 사례.
  • (2) c1 + c2 → 300: unsigned char 두 개는 연산 전에 int 로 승격된다. 200+100=300int 로 계산 → 오버플로 없이 300. (만약 결과를 다시 unsigned char 에 담았다면 300 % 256 = 44 로 잘렸을 것.)
  • (3) s > 0u → 1(true): short -1int 로 승격(-1) 후, 상대가 unsigned int 라 통상 산술 변환으로 -14294967295. 4294967295 > 0 참. 음수 short 가 unsigned 와 비교되며 거대 양수로 바뀐다.
  • (4) sizeof(true+true) → 4: boolint 로 승격되어 int+int → 결과 타입은 int(4바이트). sizeof 는 타입 크기이지 값이 아니다.
  • (5) true + true → 2: 승격된 1 + 1 = 2. bool 이 아니라 int 결과.
  • (6) a + b → 4294967286: unsigned 10 + int -20. intunsigned 로 변환 → -204294967276. 4294967276 + 10 = 4294967286(2^32 미만이라 랩 없음). 결과 타입이 unsigned 라 그대로 출력.
  • (7) 'a' < 'b' → 1: 문자 리터럴은 char(값 97, 98)로 int 승격 후 비교 → 참.

흔한 오해·함정

  • "음수는 항상 양수보다 작다"는 signed/unsigned 혼합에서 깨진다. size() 는 대개 size_t(unsigned)라 if (i < v.size()) 에서 iint 이고 음수면 참이 아닌 거짓이 되어 루프/검증이 어긋난다.
  • 작은 정수 산술은 int 로 승격되므로 unsigned char 덧셈이 곧바로 wrap 하지 않는다 — wrap 은 결과를 좁은 타입에 다시 담을 때 발생.
  • sizeof(expr) 는 피연산자를 평가하지 않고 타입만 본다.

면접 포인트

  • 게임 서버 패킷 파싱의 실전 버그: 길이 필드를 int len 으로 읽고 if (len < buf.size()) 로 검증하면, 조작된 음수 lenbuf.size()(size_t) 와 비교되며 거대 양수로 바뀌어 검증을 통과 → OOB. 길이·인덱스는 부호·범위를 명시적으로 검사하고, 비교 전에 같은 부호 타입으로 정규화해야 한다.
  • -Wsign-compare/-Wconversion 경고를 켜고 무시하지 말 것.
  • 정수 승격(1단계)과 통상 산술 변환(2단계)을 분리해 설명할 수 있어야 한다 — 둘은 다른 규칙이다.