TECH 으로 돌아가기
TECH HACKER NEWS 6분 읽기 31 READS

C에서 배열을 거꾸로 순회하는 올바른 방법, 왜 자꾸 틀릴까

배열이나 버퍼를 역순으로 훑어야 하는 상황은 흔하다. 문자열을 뒤집거나, 스택처럼 쌓인 자료를 최근 것부터 꺼내거나, 리소스를 역순으로 해제할 때가 그렇다. 그런데 C에서 이 단순해 보이는 작업을 의외로 많은 개발자가 잘못 구현한다. 데이비드 프라이버는 자신의 블로그 글 'Iterating Backwards in C'에서 이 문제가 예전부터 반복돼 왔고, 코드를 자동 생성하는 LLM이 보편화되면서 오히려 더 자주 눈에 띈다고 지적한다. 잘못된 패턴이 학습 데이터에 섞여 들어가면 그 패턴이 그대로 재생산되기 때문이다.

정방향의 습관이 만드는 함정

정방향 순회는 거의 반사적으로 작성된다. for (size_t i = 0; i < count; i++) 형태로, 인덱스를 0부터 시작해 count 미만까지 증가시키는 관용구다. 문제는 많은 사람이 이 구조를 그대로 뒤집으려 한다는 데 있다. for (size_t i = count - 1; i >= 0; i--)처럼 말이다. 언뜻 대칭적이고 자연스러워 보이지만 이 코드는 전혀 동작하지 않는다.

핵심은 인덱스 타입이 size_t, 즉 부호 없는 정수라는 점이다. 부호 없는 값은 정의상 절대 0보다 작아질 수 없으므로 i >= 0이라는 조건은 영원히 참이다. 루프는 끝나지 않는다. 현대 컴파일러들은 이 조건이 항상 참임을 경고로 알려 준다. 더 고약한 경우는 count가 0일 때다. count - 1은 0에서 1을 뺀 값이 되어 즉시 SIZE_MAX, 즉 부호 없는 정수가 표현할 수 있는 최댓값으로 감싸 올라간다(wrap-around). 빈 버퍼를 넘겨받았을 뿐인데 인덱스가 갑자기 거대한 값이 되어 엉뚱한 메모리를 건드리게 되는 것이다.

부호 있는 인덱스로 우회하기

이 문제를 피하려고 흔히 선택하는 우회로가 인덱스를 int나 ptrdiff_t 같은 부호 있는 타입으로 바꾸는 방법이다. 부호 있는 정수라면 -1까지 내려갈 수 있으니 i >= 0 조건이 비로소 의미를 가진다. 실제로 동작은 한다. 하지만 프라이버는 이 방식이 또 다른 문제를 끌고 들어온다고 본다. 버퍼 크기나 개수는 보통 size_t로 관리되는데, 이를 부호 있는 타입으로 바꾸면 타입 간 변환이 끼어들고, 부호 있는 값과 없는 값을 비교하는 지점에서 미묘한 버그나 경고가 생길 여지가 남는다. 큰 크기 값이 부호 있는 타입의 범위를 넘어서는 극단적인 경우도 이론적으로는 존재한다.

후위 감소 연산자를 쓰는 정석

프라이버가 제시하는 제대로 된 방법은 오히려 훨씬 단순하다. for (size_t i = count; i--;) 한 줄이면 끝난다. 비결은 조건 자리에 놓인 후위 감소 연산자 i--에 있다. 이 연산자는 i를 1 감소시키되, 평가 결과로는 감소하기 전의 값을 돌려준다. 그래서 루프는 i가 count인 상태로 조건을 평가하는데, 이때 반환되는 값은 아직 count이고, 평가 직후 i는 count - 1로 줄어든 상태로 본문에 진입한다. 결과적으로 본문 안에서 i는 항상 count - 1 이하, 즉 유효한 범위 안에 머문다.

종료 조건도 자연스럽게 맞아떨어진다. 조건 평가 시점의 값이 0이면, 0은 거짓이므로 루프가 끝난다. 그 순간 i는 감소 연산으로 SIZE_MAX까지 감싸 내려가지만, 이미 조건이 거짓으로 판정돼 본문에 들어가지 않으므로 아무 문제가 없다. 게다가 C 표준은 부호 없는 정수의 wrap-around 동작을 명확히 정의하고 있어, 부호 있는 정수의 오버플로와 달리 미정의 동작(undefined behavior)에 빠지지 않는다. 언어가 보장하는 안전한 동작에 기대고 있는 셈이다.

실무에서 무엇을 챙길까

이 관용구의 실질적 이점은 명료함이다. 부호 있는 인덱스로 타입을 갈아 끼우거나 변환을 신경 쓸 필요가 없고, 버퍼 크기를 다루는 코드 전반을 size_t로 일관되게 유지할 수 있다. 무엇보다 역순 루프가 제대로 구현됐는지 한눈에 판별된다. i--가 조건 자리에 있고 초기값이 count라면 올바른 것이고, count - 1에서 시작해 i >= 0을 검사하고 있다면 거의 틀린 것이다. 코드 리뷰에서 빠르게 걸러낼 수 있는 시각적 신호가 생긴다.

다만 이 패턴은 조건식에 부수 효과를 담는 다소 응축된 표현이라, C의 평가 규칙에 익숙하지 않은 팀원에게는 처음엔 낯설게 읽힐 수 있다. 한 줄 주석으로 의도를 적어 두거나 팀 내 관용구로 합의해 두면 가독성 논란을 줄일 수 있다. 또한 이 논의는 어디까지나 인덱스 기반 순회에 한정된다. 포인터를 역방향으로 움직이는 경우나, 역방향 반복자를 제공하는 다른 언어·라이브러리에는 그대로 옮겨지지 않는다. 그럼에도 생성형 도구가 만들어 낸 코드를 사람이 검수해야 하는 요즘, 이렇게 틀리기 쉬운 기본 관용구를 정확히 알아보는 눈은 여전히 사람의 몫으로 남아 있다.

SOURCE · HACKER NEWS
원문 전체 보기 → https://www.davidpriver.com/Iterating-backwards-in-C.html
SHARE
NEXT · CHOOSE

변화를 읽었다면,
내가 만들 수익 구조를 고릅니다.

정보를 더 모으는 데서 멈추지 않고, 광고·외주·판매·중개·구독 중 내 상황에 맞는 출발점을 정해보세요.

21가지 수익 구조 살펴보기 →
처리 중...