프로그래밍 언어/C

C언어 백준 1152번 단어의 개수 해설 {런타임 에러(Segfault) }

happy_life 2021. 9. 10. 11:58

https://www.acmicpc.net/problem/1152

 

1152번: 단어의 개수

첫 줄에 영어 대소문자와 띄어쓰기로 이루어진 문자열이 주어진다. 이 문자열의 길이는 1,000,000을 넘지 않는다. 단어는 띄어쓰기 한 개로 구분되며, 공백이 연속해서 나오는 경우는 없다. 또한

www.acmicpc.net

 

문제

영어 대소문자와 띄어쓰기만으로 이루어진 문자열이 주어진다. 이 문자열에는 몇 개의 단어가 있을까? 이를 구하는 프로그램을 작성하시오. 단, 한 단어가 여러 번 등장하면 등장한 횟수만큼 모두 세어야 한다.

 

 

 

풀이과정

 

사용되는 개념

1) 문자열 배열 

2) scanf 

 

 

idea

1)공백 앞 뒤에 있는 경우를 예외처리하고 구하는 방법이 있을지도 모르겠다는 생각이 듦.

 

2) scanf는 띄어쓰기 앞에서 입력받는 것을 멈추는데 어떤 것으로 이를 해결할 수 있을까? 그 해결방안이 바로 이 문제 해결의 key라는 생각이 듦

 

즉 , 이번 문제의 출제 point는 scanf에 대한 이해를 바탕으로 띄어쓰기를 활용하여 문제를 해결하는 것임.

 

 

공백에서 멈추는 scanf의 한계

#include <stdio.h>


int main() {

    char Big_words[10001];

    
    scanf("%s", Big_words);
    printf("%s", Big_words);

    return 0;
}

 

▲이런식으로 scanf는 띄어쓰기 앞까지만 입력을 받기 때문에, 출력해보면 The만 나오는 것임.

나머지 'Curious Case of Benjamin Button' 은 버퍼에 있고 scanf 를 다시하면 Curious가 나올텐데

이 아이디어를 활용해서 문제를 푸는 것 외에는 다른 방안이 떠오르지 않음. 

 

초기 아이디어의 한계

1)재귀함수를 사용해서 scanf가 호출이 몇번이나 되는지를 구해야함

 

2) 단어마다 계속 문자열 배열에 덮어쓰기 될텐데 이를 방지하기위해

문자열 배열을 여러 개 생성해주려고 하면,

입력받을 문자열 덩어리 개수가 몇인지도 모르기 때문에

scanf가 호출 될 때마다, 넣을 문자열 배열의 개수를 설정해주기 어려움.

 

따라서 다른 아이디어는 내가 모르는 부분에 있다고 생각하고 구글링.

 

구글링 이후 공백을 포함해 한번에 scanf 할 수 있는 방법을 알아냄.

 

 

풀이과정

 1)scanf("%[^\n]") 활용 

이 방식을 활용하면, 공백을 입력 받지 못하던 scanf가 공백을 포함한 입력을 받을 수 있음.

이 뜻은 문자열을 읽는데 \n은 제거하고 읽으라는 것이다.

참고로 저기 1000001은 문자열 길이가 1000000이니, 마지막 \n  공백자리까지 추가해서 1000001인 것이다.

(입력한 문자열이 1000000인 경우 생각해보기) 

 

#include <stdio.h>


int main() {

    char Big_words[1000001];

    
    scanf("%[^\n]s", Big_words);
    printf("%s", Big_words);

   
    return 0;
}

실행결과

 

2)입력된 문자열에 있는 띄어쓰기 개수 세기

//2)입력된 문자열에 있는 띄어쓰기 개수 세기
    
    for (i = 0; Big_words[i] != '\0' ; i++)
    {
        if (Big_words[i] == ' ')
        {
            cnt++;
        }
    }
    printf("띄어쓰기 개수:%d\n", cnt);;

결과값

Q1)for 문에 Big_word[i] != '\0'을 쓴 이유

i가 ++ 되면서 인덱스 0부터 문자열을 판단해 나가는데, 결국 마지막엔 '\0'=NULL이 나올 것이므로

이를 활용하였다. (여기에 직접strlen 쓰면 메모리 초과된다. for문이 돌 때마가 strlen함수가 호출되어서 메모리 가 커짐)

 

Q2) cnt 변수를 global로 선언해준 이유

3번째 풀이일 때 -해줘야하므로 지역변수가 아닌 global 선언을 해줌

 

3)앞 뒤 띄어쓰기 예외가 있는 경우 띄어쓰기 개수 -해주기

 

 //3) 앞 뒤 띄어쓰기 예외가 있는 경우 띄어쓰기 개수 -해주기
    if (Big_words[0] == ' ') //첫번째
    {
        cnt--;
    }

    if (Big_words[i - 1] == ' ') //마지막
    {
        cnt--;
    }

    printf("띄어쓰기 개수:%d\n", cnt);
    printf("단어 개수:%d\n", cnt+1);

결과값

Q1) if (Big_words[i - 1] == ' ')를 사용한 이유

풀이과정 2번째를 참고하면 Big_words[i]일때 '\0'이 나온다

따라서 문자열의 마지막은 Big_words[i-1] 위치에 있을 것이다.

 

 

4)디버깅

그러나 런타임 에러(Segfault)가 일어났다.

 

세그멘테이션 결함

프로그램이 허용되지 않은 메모리 영역에 접근을 시도하거나, 허용되지 않은 방법으로 메모리 영역에 접근을 시도할 경우 발생한다. (예를 들어, 읽기 전용 영역에 어떤 내용을 쓰려고 시도하거나, 운영 체제에서 사용하는 영역에 다른 내용을 덮어쓰려 하는 경우를 의미한다.

-출처: 위키백과

 

메모리 범위를 좀 늘려줬다.

런타임 에러가 났던 코드와 맞은 코드를 비교해보니 메모리의 범위가 달랐는데,

에러발생은 실수로 내가 Big_word[10001] 로 너무 작게 범위를 설정했었기 때문이었다.

 

배운점

1)scanf 공백 무시하고 출력하기

2) 메모리 설정의 중요성