suminworld

concept

Google Dev Challenge: C 언어 타입 변환의 함정

숨usm 2025. 11. 25. 09:10

Google for Developers에서 제시한 C 언어 Dev Challenge 문제입니다. "코드는 단순하지만 동작은 그렇지 않다"는 주제로, signed와 unsigned int 비교 시 발생하는 예상치 못한 동작을 다룹니다.

문제 소개

Google for Developers의 Dev Challenge는 일상적인 버그의 원인을 탐구하는 시리즈입니다. 이번 문제는 얼핏 보기에 -1이 명백히 1보다 작아 보이지만, C 언어의 타입 변환 규칙이 개입하면서 예상과 다른 결과가 나오는 상황을 보여줍니다.

코드 분석

#include <stdio.h>

int main() {
    int a = -1;
    unsigned int b = 1;
    if (a < b)
        printf("a is less\n");
    else
        printf("a is greater or equal\n");
}
```

## 예상 결과 vs 실제 결과

직관적으로 생각하면 -1 < 1이므로 "a is less"가 출력되어야 할 것 같지만, 실제로는 **"a is greater or equal"**이 출력됩니다.

## 원인: 암묵적 타입 변환 (Implicit Type Conversion)

C 언어에서 signed int와 unsigned int를 비교할 때, **signed int가 unsigned int로 변환**됩니다. 이것이 문제의 핵심입니다.

### 변환 과정

1. `a = -1` (signed int, 2의 보수 표현: `0xFFFFFFFF`)
2. `b = 1` (unsigned int: `0x00000001`)
3. `a < b`를 비교할 때, `a`가 `unsigned int`로 변환됨
4. `-1`을 unsigned int로 해석하면 `4294967295` (32비트 시스템 기준)
5. 따라서 `4294967295 < 1`은 거짓

### 비트 레벨 표현
```
signed int a = -1:     11111111 11111111 11111111 11111111
unsigned int로 해석:   4294967295 (2^32 - 1)
unsigned int b = 1:    00000000 00000000 00000000 00000001

C 표준의 타입 변환 규칙

C 언어 표준에서는 usual arithmetic conversions 규칙에 따라 다음과 같이 동작합니다:

  • signed와 unsigned를 혼합하여 연산할 때
  • 두 타입의 크기가 같으면 signed가 unsigned로 변환됨
  • 이때 비트 패턴은 그대로 유지되지만 해석 방식이 바뀜

실제 프로그래밍에서의 위험성

이런 버그는 실제 코드에서 자주 발생합니다. 특히 다음과 같은 경우에 주의해야 합니다:

배열 인덱스 처리

int find_last(int *arr, size_t len) {
    for (int i = len - 1; i >= 0; i--) {  // 위험!
        // len이 size_t (unsigned)이고 i가 int (signed)
        // len이 0일 때 언더플로우 발생 가능
    }
}

루프 조건

unsigned int size = get_size();
for (int i = 0; i < size - 1; i++) {  // size가 0이면?
    // size - 1은 매우 큰 양수가 됨
}

안전한 코드 작성 방법

1. 같은 타입 사용

int a = -1;
int b = 1;
if (a < b)  // 안전

2. 명시적 캐스팅

int a = -1;
unsigned int b = 1;
if ((int)b > a || a < 0)  // 의도를 명확히

3. 컴파일러 경고 활성화

gcc -Wall -Wextra -Wconversion -Wsign-compare test.c

4. 타입 체크

int a = -1;
unsigned int b = 1;
if (a < 0 || (unsigned int)a < b)
    printf("a is less\n");
else
    printf("a is greater or equal\n");

보안 관점에서의 중요성

이러한 타입 변환 버그는 다음과 같은 보안 취약점으로 이어질 수 있습니다:

  • Integer Overflow/Underflow: 버퍼 크기 계산 오류
  • Buffer Overflow: 잘못된 경계 검사
  • Memory Corruption: 배열 인덱스 오류

실제 CVE에서도 signed/unsigned 혼용으로 인한 취약점이 다수 보고되었습니다.

결론

C 언어의 암묵적 타입 변환 규칙을 정확히 이해하고, signed와 unsigned를 혼용할 때는 항상 주의해야 합니다. 특히 보안에 민감한 코드에서는 더욱 신중한 접근이 필요합니다.

핵심 정리

  • signed와 unsigned 비교 시 signed가 unsigned로 변환됨
  • -1은 unsigned로 변환되면 최댓값이 됨
  • 컴파일러 경고를 활용하고 명시적 타입 체크를 수행할 것
  • 보안 취약점으로 이어질 수 있으므로 주의 필요

참고 자료