7 - fgets 문제의 오답이 나오는 이유를 서술해 보았다. +질문
All Lectures
NoticeQ&A

Q&A

7 - fgets 문제의 오답이 나오는 이유를 서술해 보았다. +질문
profile김효헌
05/29/22, 05:36 PM
Question Path :

!! 아래 내용은 제시한 가정 하에 설명이 되는 문제입니다. 가정한 모든 전제 조건을 참이라 하고 설명하므로 가정이 실제 환경과 하나라도 같지 않다면 틀린 내용이 되며, 채점기를 직접 이용하지 않고 아래 가정들을 적용해 테스트케이스로 디버깅을 하였기에 틀릴 가능성이 있어 비적극적으로 수용해주셨으면 합니다.

[0. 개요]

fgets 함수를 사용하여 문자열을 받아온 후 맨 마지막 문자를 NULL('\n')로 채우는 프로그램이며 코드 실행 시 정상 작동하는 프로그램. 채점기에서 입력과 출력 예시를 실행 프로그램과 동일하게(사실은 그렇지 않다.) 하였는데도 불구하고 채점 시 오답이 나온다.

[1. 디버깅]

(* '[', ']' : 공백이나 줄내림('\n') 등, 인지하기 어렵거나 투명한 문자의 범위를 나타내기 위해 사용된다.)

테스트 및 디버깅을 서술하기 전에 먼저 매우 중요한 조건이 되는 안내문을 먼저 보겠다. 아래는 {테스트케이스}에 있는 안내문이다.

"각 입력 케이스의 값이 실제 채점과 동일한 방식으로 표준입력에 전달됩니다. 일반 실행 시 에러가 없더라도 테스트케이스 실행에서 에러가 발생하는 경우, 채점 시에도 같은 에러가 발생합니다. 코드를 수정해서 다시 시도해 보시기 바랍니다."

첫 문장에서 알 수 있든 테스트케이스에서의 입력 값은 실제 채점과 같은 방식으로 표준입력, 즉 stdin에 전달된다. 우리가 입력을 받을 때 사용하는 함수는 보통 stdin이다. (fgets에 stdin이 쓰였으므로 위 내용에 적용된다.) 여기서 핵심은 입력값을 표준입력에 전달실제 채점과 동일한 방식, 그리고 테스트케이스 실행에서 에러가 발생하는 경우, 채점 시에도 동일한 에러가 발생합니다.*이다.

{테스트케이스}에서의 입력값과 출력값이 채점 시에도 모두 동일하다는 것을 알 수 있다. {테스트케이스}과 채점기가 동일하므로 {테스트케이스}와 채점기를 동일시 하여 설명하겠다. (테스트케이스 = 채점기)

이제 기본이면서 가장 중요한 조건을 설명하였으니 채점 시의 예시를 설명하겠다.

채점기 설정은 다음과 같다.

채점 입력 예시 : [sunrin internet] 채점 출력 예시 : [문자열 입력 : sunrint internet sunrin internet sunrin internet] (정확한 조건을 알 수 없는)줄내림 무시 또는 이와 유사한 설정

위 예시를 채점기의 시점으로 출력과 입력을 나눠보면

채점 입력값 : [sunrin internet] (채점기가 표준입력 stdin으로 전달할 문자열) 채점 출력값 : [문자열 입력 : sunrin internetsunrin internet] (printf 함수로 출력되는 문자열)

{제출}버튼을 눌러 제출이 되면 채점기는 위의 {채점 입력값}의 문자열을 stdin으로 전달해 사용자가 작성한 코드를 실행한다. printf 함수로 출력되는 문자열이 {채점 출력값}과 같은 문자열이 나오면 정답을 반환하고, 다른 문자열이 나오면 오답을 반환한다.

우선 우리가 의도한 데로 작동하는 코드를 작성해 보았다.

/*0번줄*/ #include <string.h> /*1번줄*/ #include <stdio.h> /*2번줄*/ /*3번줄*/ int main() { /*4번줄*/ char str[50]; /*5번줄*/ int length; /*6번줄*/ printf("문자열 입력 : "); /*7번줄*/ fgets(str, sizeof(str), stdin); /*8번줄*/ /*9번줄*/ printf("%s", str); /*10번줄*/ /*11번줄*/ length= strlen(str); /*12번줄*/ str[length-1]='\0'; /*13번줄*/ /*14번줄*/ printf("%s", str); /*15번줄*/ /*16번줄*/ return 0; /*17번줄*/ }

간단하게 설명하면, fgets로 받아온 문자열에서 '\n'을 없에는 코드이다. 실행을 하면 기대하는대로 문제 없이 잘 작동한다. 그럼, 위 코드를 채점기에게 넘겨 채점하록 {제출}버튼을 눌러보겠다.

  1. 6번 줄에서 문자열을 출력한다. [문자열 입력 : ]

  2. 7번 줄에서 채점기는 {채점 입력값}을 stdin으로 전달해, 최종적으론 {채점 입력값}이 str배열로 전달된다. [sunrin internet] ( 다만 의문점이 드는 것은 fgets함수는 stdin에 '\n'이 들어올 때, 지금까지 받은 문자열값들을 모두 지정한 배열에 저장시키는데 {채점 입력값}에는 '\n'이 없다. 위 안내문에 적혀있는 채점 방식중 하나인 것으로 추정)

  3. 9번 줄에서 str배열의 값들을 문자열 형태로 출력한다. [sunrin internet] (sunrin internet\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0)

  4. 11, 12번 줄에서 str배열의 맨 마지막 인덱스 '\0'의 바로 왼쪽에 있는 값을 '\n'으로 바꾼다.

  5. 14번 줄에서 str배열의 값들을 문자열 형태로 출력한다. [sunrin interne] (sunrin interne\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0)

  6. 16번 줄에서 return을 하므로 프로그램이 종료된다.

printf함수가 출력한 문자열을 그대로 나열하면,

[문자열 : sunrin internetsunrin interne]

이 나온다. 그리고 채점 결과는 오답이 나온다. 눈치를 챘을 수도 있는데, 맨 마지막에 문자 't'가 '\0'으로 바뀌었다. 프로그램 출력의 결과값이 {채점 출력값}과 같은가? 문자 한 개 차이이지만 다르다. 그러니 당연하게도 오답을 반환했던 것이다.

이번엔 반대로 정답이 나오게 하는 코드를 작성해보았다. 채점 결과는 정답이지만 출력 결과는 우리가 원하는 결과가 아니다.

/*0번줄*/ #include <string.h> /*1번줄*/ #include <stdio.h> /*2번줄*/ /*3번줄*/ int main() { /*4번줄*/ char str[50]; /*5번줄*/ int length; /*6번줄*/ printf("문자열 입력 : "); /*7번줄*/ fgets(str, sizeof(str), stdin); /*8번줄*/ /*9번줄*/ printf("%s", str); /*10번줄*/ /*11번줄*/ //length= strlen(str); /*12번줄*/ //str[length-1]='\0'; /*13번줄*/ /*14번줄*/ printf("%s", str); /*15번줄*/ /*16번줄*/ return 0; /*17번줄*/ }

달라진 점은 11, 12번 줄이 주석처리되어 실행되지 않게 되는 것이다. (11번 줄의 주석처리는 12번 줄이 주석처리되었으므로 주석처리 하든 말든 별 의미 없다. 주목해야하는 건 12번 줄의 주석처리다.) 그럼, 채점기의 시점으로 다시 실행해보자.

  1. 6번 줄에서 문자열을 출력한다. [문자열 입력 : ]

  2. 7번 줄에서 채점기는 {채점 입력값}을 stdin으로 전달해, 최종적으론 {채점 입력값}이 str배열로 전달된다. [sunrin internet]

  3. 9번 줄에서 str배열의 값들을 문자열 형태로 출력한다. [sunrin internet] (sunrin internet\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0)

  4. 14번 줄에서 str배열의 값들을 문자열 형태로 출력한다. [sunrin internet] (sunrin internet\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0)

  5. 16번 줄에서 return을 하므로 프로그램이 종료된다.

printf함수가 출력한 문자열들을 모두 나열하면,

[문자열 입력 : sunrin internetsunrin internet]

이 된다. 위의 출력값은 {채점 출력값}과 완전히 같으므로 채점기는 정답을 반환하게 된다.

[2. 문제 해결]

우리가 의도한 것은 fgets함수가 입력 받은 문자열은 맨 마지막에 항상 '\n'을 포함하게 되니 '\0'의 바로 왼쪽 (또는 문자열의 맨 마지막 문자), 즉 '\n'을 '\0'으로 바꾸어 줄내림을 없에자는 의도로 12번 줄 코드를 작성하였고 의도대로 출력하였으나 채점기는 {입력 예시}문자열, 즉 {채점 입력값}을 fgets함수를 통해 str배열에 저장하는데 채점기의 경우엔 str배열의 맨 마지막에 '\n'가 없다. 그 상태에서 맨 마지막 문자를 '\0'으로 대입했으므로 문자 하나가 지워지게 된다.

이 문제의 해결은 단순하다. {입력 예시}에 줄내림을 추가해주면 된다. (정말 그러기를 바란다.)

[3. 주제 조금 이탈]

채점기의 채점 방식과 여러 조건들을 코드를 실행해 결과값을 보고 비교해가며 알아내느라 글이 길어졌고 오래 걸렸다.

만약 이전 년도에서도 지금과 똑같은 코드로, 채점기 설정 하나 달라진 것 없이 교육할 때 위의 문제가 전혀 없었다면 그냥 버그라고 할 수도 있겠다. 하지만 이유 없는 버그는 없으니 항상 디버깅...

정말 글이 긴 것 같다. 실은 채점기의 {채점 입력 예시}에 줄내림 하나 추가해보거나 채점기의 설정을 바꿔가며 디버깅 하는게 더 정확하고 조금 더 십게 해결할 수 있었겠지만 학생이라 테스트케이스만 가능하다.

수업시간에 쌤에게 직접 물어가며 채점기 설정값을 바꿔보며 디버깅하는 게 더 빠를 것이다.

그동안의 문제들은 scanf함수를 사용했었고 이런 문제가 전혀 발생하지 않았었다. scanf함수는 '\n'을 포함하지 않기 때문에 {입력 예시}의 마지막에 줄내림을 주지 않아도 상관 없었던 것으로 판단된다.

단지 줄내림 하나로 인해 발생한 문제인 만큼 상당히 단순한 문제이다. 근데 글이 좀 길다.

이 글을 작성하고 나서, 수학 문제도 이랬으면 좋겠다는 생각이 들었다.

이 글의 제목을 "아래 코드의 채점 결과가 오답이 나오는 이유와 그 해결 방법을 1200자 이내로 서술하시오. [80점]"이라고 짓고 싶었지만 꾹 참았다.

위의 디버깅은 사실 몇개의 코드를 더 돌리며 {테스트케이스 출력값}을 여럿이 비교해 가며 전제 조건을 세웠다. 몇개의 조건들을 빼고 설명해서 이해가 안되는 부분이 있을 수도 있는데, 그 코드들을 작성하고, 테스트케이스 출력값을 비교 및 설명하고, 코드 풀이를 해버리면 가독성이나 집중력이 떨어지고 독자가 읽기를 거부할 수 있으며 쓰기 매우 귀찮아요.(시간 없어요)

만약 {채점 입력 예시}에 줄내림을 넣어도 오답이라고 나오면 참 재밌겠다

[4. +]

궁금한 점이나 오류, 다른 의견 등등 있다면 알려주세요.

저도 다른 의견 궁금해요.

138
0
Like4