
재귀 알고리즘 문제를 풀다가 답안을 제출했는데 자꾸 오답처리가 되었다.
알고리즘에도 문제가 없고 내 컴파일러에서는 실행결과에도 아무런 문제가 없었다.
그래서 다른 사람들의 코드와 비교하다가 이유를 찾았다.
오답 코드
#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 컴파일러의 일반적인 방식)
printf의 두 번째 인자인 count를 먼저 평가한다. isPalindrome(s, &count)를 평가한다.isPalindrome 함수가 실행되면서 내부의 recursion이 호출되고, count의 값이 (예를 들어) 3으로 변경된다. 함수는 팰린드롬 여부에 따라 1 또는 0을 반환한다.printf 함수는 최종적으로 (2)번 단계에서 반환된 값과 (1)번 단계에서 미리 계산해 둔 count의 값(0)을 가지고 출력한다.1 0 (오답)2. 인자를 왼쪽에서 오른쪽으로 평가
printf의 첫 번째 인자인 isPalindrome(s, &count)를 먼저 평가한다.count의 값이 3으로 변경되고, 함수는 1 또는 0을 반환한다.count를 평가한다. 이 시점에서 count의 값은 (2)번 단계에서 변경된 3이다다.printf 함수는 (1)번 단계의 반환값과 (3)번 단계에서 계산된 count의 값(3)을 가지고 출력한다.1 3 (정답)백준의 채점 서버의 컴파일러는 방식 1처럼 동작했고, 나의 컴파일러는 방식 2처럼 동작해서 생긴 오류였다.
이처럼 컴파일러나 환경에 따라 결과가 달라질 수 있는 코드는 정의되지 않은 동작(Undefined Behavior)에 의존하는 코드라고 부르며, 프로그래밍 시 반드시 피해야 한다.
temp 변수를 사용한 코드는 왜 항상 올바르게 동작할까 ?
temp = isPalindrome(s, &count); // 1번 라인
printf("%d %d\n", temp, count); // 2번 라인
C 언어에서 세미콜론(;)은 "시퀀스 포인트(Sequence Point)" 역할을 한다.
시퀀스 포인트는 이전 라인에서 발생한 모든 계산과 부수 효과(side effect)가 다음 라인으로 넘어가기 전에 반드시 완료됨을 보장한다.
isPalindrome(s, &count)가 호출됩니다. 이 함수의 실행이 완전히 끝날 때까지 프로그램은 다음으로 넘어가지 않는다.count의 값은 최종값(예: 3)으로 업데이트된다.isPalindrome의 반환값은 temp 변수에 저장된다.;): 1번 라인의 모든 동작(count 값 변경 포함)이 완료되었음을 보장한다.printf가 호출된다.temp와 count의 값이 이미 모두 확정된 상태이다.printf의 인자 평가 순서가 어떻게 되든 상관없이, 이미 계산이 끝난 temp의 값과 count의 최종값을 가져와 출력하므로 항상 올바른 결과가 나온다.앞으론 세미콜론(시퀀스 포인트)을 통해 연산의 순서를 명확하게 강제하는 등 C의 동작과정을 더 이해하고 어떤 컴파일러 환경에서도 동일한 결과를 보장하는 코드를 작성해야겠다.