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로 변환되면 최댓값이 됨
- 컴파일러 경고를 활용하고 명시적 타입 체크를 수행할 것
- 보안 취약점으로 이어질 수 있으므로 주의 필요
참고 자료
- Google for Developers YouTube - C Dev Challenge
- C11 Standard - 6.3.1.8 Usual arithmetic conversions
'concept' 카테고리의 다른 글
| [OS] 뮤텍스 + Block() 방식의 문제점 (Producer-Consumer Problem) (0) | 2025.11.17 |
|---|---|
| [운영체제론] Constant Time Coalescing (Case 1-4) 정리 (0) | 2025.11.08 |
| tmux (수정 예정) (0) | 2025.11.03 |
| [운영체제론] 가상 메모리 (0) | 2025.10.31 |
| [운영체제론] Dynamic Memory Allocation (0) | 2025.10.29 |