제목: “이번 주의 팁 #227: 빈 컨테이너와 부호 없는 정수 연산 주의하기”
원문 게시일: 2023년 11월 16일
업데이트: 2024년 3월 11일
작성자: James Dennett
빠른 링크: abseil.io/tips/227
인덱스 기반 반복문: 여전히 유용한 경우가 있다
범위 기반 for
루프가 등장하면서 인덱스 기반 for
루프는 현대 C++ 코드에서 덜 자주 사용됩니다. 그러나 여전히 인덱스가 필요한 경우가 있습니다. 예를 들어, 여러 컨테이너를 병렬로 순회하거나, 하나의 컨테이너에서 인접한 여러 요소를 처리하려는 경우입니다. 이번 팁에서는 두 번째 경우에 발생할 수 있는 문제를 살펴봅니다.
문제 있는 코드
다음은 올바를 것처럼 보이는 코드입니다:
for (int64_t i = 0; i < v.size() - 1; ++i) {
ProcessPair(v[i], v[i+1]);
}
이 코드는 ProcessPair()
를 호출하기 전에 유효한 인덱스를 확인하는 것처럼 보입니다. 하지만 이 코드는 “올바를 것처럼 보일 뿐”입니다. 예를 들어, 컨테이너 v
가 비어 있는 경우를 고려하지 않은 것입니다. 만약 v
가 비어 있는 상태로 이 루프가 실행된다면, C++는 예기치 않은 동작을 일으킬 수 있습니다.
부호 없는 정수의 문제
C++ 표준에서는 컨테이너 크기를 나타내기 위해 부호 없는 정수 타입을 사용합니다 (v.size()
는 unsigned
타입). 그러나 부호 없는 정수는 연산 시 문제가 발생할 수 있습니다.
예를 들어, v.size()
가 0인 경우, v.size() - 1
은 -1이 아니라 최대값(예: std::size_t
의 경우 18446744073709551615
)으로 변환됩니다. 결과적으로, 조건 i < v.size() - 1
은 잘못 평가될 수 있습니다. 즉, 루프가 실행되고 v[i]
와 같은 코드에서 정의되지 않은 동작(UB)이 발생할 수 있습니다.
올바르게 수정하기
문제를 해결하기 위해 코드의 의도를 더 명확히 표현하면 문제가 사라집니다.
의도를 더 직접적으로 표현하기
루프 조건의 목적은 i
와 i + 1
이 모두 유효한 인덱스인지 확인하는 것입니다. 이를 C++로 직접적으로 번역하면 다음과 같이 표현됩니다:
- 인덱스
i
가 유효하려면i < v.size()
여야 합니다. - 인덱스
i + 1
이 유효하다면, 이는i < v.size() - 1
과 동등합니다. 하지만i + 1 < v.size()
로 표현하면 더 안전합니다.
새로운 조건인 i + 1 < v.size()
는 v.size()
에서 1을 빼는 연산을 피하므로, 부호 없는 정수의 오버플로를 방지합니다.
수정된 코드
수정된 코드는 다음과 같습니다:
for (int64_t i = 0; i + 1 < v.size(); ++i) {
ProcessPair(v[i], v[i+1]);
}
이 변경으로 인해:
v
가 비어 있을 때에도 루프가 안전하게 작동합니다.- 인덱스 범위 확인이 더 명확하고 직관적으로 표현됩니다.
요약
수정된 코드는 몇 바이트만 바뀌었지만, 중요한 몇 가지 교훈을 제공합니다:
- 부호 없는 정수 연산에 주의하세요. 부호 없는 정수는 예상치 못한 결과를 초래할 수 있습니다.
- 컨테이너 크기(
container.size()
)는 부호 없는 타입을 반환한다는 점을 기억하세요. - 코드의 올바름을 가능한 한 로컬하게 확인하세요.
- 의도를 직접적으로 표현하세요. 코드는 사람이 읽기 쉬워야 합니다.
이 팁을 통해 부호 없는 정수와 관련된 오류를 방지하고 더 안전한 코드를 작성할 수 있기를 바랍니다.