[C] 컴파일러의 차이로 발생하는 오류(Undefined Behavior)

김태희·2025년 7월 17일
0

백준 25501 - 재귀의 귀재

재귀 알고리즘 문제를 풀다가 답안을 제출했는데 자꾸 오답처리가 되었다.

알고리즘에도 문제가 없고 내 컴파일러에서는 실행결과에도 아무런 문제가 없었다.

그래서 다른 사람들의 코드와 비교하다가 이유를 찾았다.

오답 코드

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

int recursion(const char *s, int l, int r, int *count) {
  (*count)++;
  if (l >= r)
    return 1;
  else if (s[l] != s[r])
    return 0;
  else
    return recursion(s, l + 1, r - 1, count);
}

int isPalindrome(const char *s, int *count) {
  return recursion(s, 0, strlen(s) - 1, count);
}

int main() {
  int num, count, temp;
  char s[1002];
  scanf("%d", &num);
  while (num--) {
    count = 0;
    scanf("%s", s);
    printf("%d %d\n", isPalindrome(s, &count), count);
  }
  return 0;
}

정답 코드
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

int recursion(const char *s, int l, int r, int *count) {
  (*count)++;
  if (l >= r)
    return 1;
  else if (s[l] != s[r])
    return 0;
  else
    return recursion(s, l + 1, r - 1, count);
}

int isPalindrome(const char *s, int *count) {
  return recursion(s, 0, strlen(s) - 1, count);
}

int main() {
  int num, count, temp;
  char s[1002];
  scanf("%d", &num);
  while (num--) {
    count = 0;
    scanf("%s", s);
    temp = isPalindrome(s, &count);
    printf("%d %d\n", temp, count);
  }
  return 0;
}

오류 부분
	printf("%d %d\n", isPalindrome(s, &count), count);
    
   
수정 후    
    temp = isPalindrome(s, &count);
    printf("%d %d\n", temp, count);

결론부터 말하자면, 채점 서버의 컴파일러가 함수 인자를 처리하는 순서가 사용자의 로컬 환경과 달랐기 때문에 발생한 오류였다.


함수의 인자 평가 순서

C 언어 표준에서는 함수에 여러 개의 인자(argument)를 전달할 때, 컴파일러가 그 인자들의 값을 어떤 순서로 계산(평가)할지에 대해 정해두지 않았다.

즉, 아래와 같은 코드가 있을 때,
printf("%d %d\n", isPalindrome(s, &count), count);

컴파일러는 두 가지 방식 중 아무거나 선택할 수 있다.

1. 인자를 오른쪽에서 왼쪽으로 평가 (많은 C 컴파일러의 일반적인 방식)

  1. printf의 두 번째 인자인 count를 먼저 평가한다.
  2. 그 다음, 첫 번째 인자인 isPalindrome(s, &count)를 평가한다.
  3. isPalindrome 함수가 실행되면서 내부의 recursion이 호출되고, count의 값이 (예를 들어) 3으로 변경된다. 함수는 팰린드롬 여부에 따라 1 또는 0을 반환한다.
  4. printf 함수는 최종적으로 (2)번 단계에서 반환된 값과 (1)번 단계에서 미리 계산해 둔 count의 값(0)을 가지고 출력한다.
  5. 결과: 1 0 (오답)

2. 인자를 왼쪽에서 오른쪽으로 평가

  1. printf의 첫 번째 인자인 isPalindrome(s, &count)를 먼저 평가한다.
  2. 함수가 실행되면서 count의 값이 3으로 변경되고, 함수는 1 또는 0을 반환한다.
  3. 그 다음, 두 번째 인자인 count를 평가한다. 이 시점에서 count의 값은 (2)번 단계에서 변경된 3이다다.
  4. printf 함수는 (1)번 단계의 반환값과 (3)번 단계에서 계산된 count의 값(3)을 가지고 출력한다.
  5. 결과: 1 3 (정답)

백준의 채점 서버의 컴파일러는 방식 1처럼 동작했고, 나의 컴파일러는 방식 2처럼 동작해서 생긴 오류였다.

이처럼 컴파일러나 환경에 따라 결과가 달라질 수 있는 코드는 정의되지 않은 동작(Undefined Behavior)에 의존하는 코드라고 부르며, 프로그래밍 시 반드시 피해야 한다.


temp 변수를 사용한 순서 보장

temp 변수를 사용한 코드는 왜 항상 올바르게 동작할까 ?

temp = isPalindrome(s, &count);       // 1번 라인
printf("%d %d\n", temp, count);      // 2번 라인

C 언어에서 세미콜론(;)은 "시퀀스 포인트(Sequence Point)" 역할을 한다.

시퀀스 포인트는 이전 라인에서 발생한 모든 계산과 부수 효과(side effect)가 다음 라인으로 넘어가기 전에 반드시 완료됨을 보장한다.

  1. 1번 라인 실행: isPalindrome(s, &count)가 호출됩니다. 이 함수의 실행이 완전히 끝날 때까지 프로그램은 다음으로 넘어가지 않는다.
    • 이 과정에서 count의 값은 최종값(예: 3)으로 업데이트된다.
    • isPalindrome의 반환값은 temp 변수에 저장된다.
  2. 시퀀스 포인트(;): 1번 라인의 모든 동작(count 값 변경 포함)이 완료되었음을 보장한다.
  3. 2번 라인 실행: printf가 호출된다.
    • 이 시점에는 tempcount의 값이 이미 모두 확정된 상태이다.
    • printf의 인자 평가 순서가 어떻게 되든 상관없이, 이미 계산이 끝난 temp의 값과 count의 최종값을 가져와 출력하므로 항상 올바른 결과가 나온다.

앞으론 세미콜론(시퀀스 포인트)을 통해 연산의 순서를 명확하게 강제하는 등 C의 동작과정을 더 이해하고 어떤 컴파일러 환경에서도 동일한 결과를 보장하는 코드를 작성해야겠다.

0개의 댓글