ABOUT ME

이백사십 아니고 이사영 개발 초보의 성장 일기

Today
Yesterday
Total
  • C언어 코딩도장 38.8 지뢰찾기 문제
    개인 공부/C언어 공부 2020. 7. 30. 08:19

    별거 아닌 것처럼 보였지만 다양한 개념들을 정확하게 알고 있어야 풀 수 있는 문제여서 생각보다 복잡하고 어려웠다.

    문제에 "이 문제는 지금까지 심사문제 중에서 가장 어렵습니다. 처음 풀어보는 경우 대략 두 시간은 걸립니다. 시간을 두고 천천히 고민해서 풀어보세요."라고 써있어서 '무슨 두 시간까지나 걸린대ㅋㅋ;;'하고 코웃음 쳤는데... 정말... 또 다시 나는 한 톨의 먼지라는 것을 다시금 깨닫게 만든 문제. ㅠㅠ

     

    처음 짠 코드(오답)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    #include <stdio.h>
    #include <stdlib.h>
     
     
    int main(){
        int row, col;
        int i, j;
        int a, b;
        char **matrix;
     
        scanf("%d %d"&row, &col);
     
        matrix = malloc(sizeof(char ** row);
     
        for (i = 0; i < row; i++)
            matrix[i] = malloc(sizeof(char* (col + 1)); // 널 문자 포함
     
        for (i = 0; i < row; i++)
            scanf("%s", matrix[i]);
     
        for (i = 0; i < row; i++) {
            for (j = 0; j < col; j++){
                if (matrix[i][j] != '*'){
                    matrix[i][j] = '0';
                }
            }
        }
     
        for (i = 0; i < row; i++){
            for (j = 0; j < col; j++){
                if (matrix[i][j] != '*'){
                    for (a = i-1; a <= i+1; a++){ // 오류가 발생한 부분
                        for (b = j-1; b <= j+1; b++){
                            if (matrix[a][b] == '*' && (a >= 0 && a < row && b >= 0 && b < col))
                                matrix[i][j] += 1;
                        }
                    }
                }
            }
        }
     
     
        for (i = 0; i < row; i++){
            printf("%s\n", matrix[i]);
        }
     
        for (i = 0; i < row; i++)
            free(matrix[i]);
     
        free(matrix);
     
        return 0;
    }
    cs

     

    먼저 메모리 할당하는 것은 문제에서 널 문자 자리를 포함하라고 언급되어 있어서 실수하지 않고 무사히 끝낼 수 있었다. 코드를 다 작성하고 호기롭게 실행해봤는데, 행렬 크기와 문자열을 입력하는 것까지는 문제가 없었는데, 다 입력을 받고 나면 그냥 프로그램이 종료되어 버렸다. 

     

    내가 사용하는 IDE에서는 에러 메세지는 뜨지 않길래 온라인 컴파일러에서 실행해보았고, 역시나 입력을 다 받은 후 프로그램이 종료되었고, 'segmentation fault'라는 메세지가 함께 출력되었다. 

     

    이 메세지를 또 검색해보니 잘못된 메모리를 건든 경우에 발생하는 놈임을 알 수 있었다. 이에 코드를 작성할 때도 왠지 찜찜했던 

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
        for (i = 0; i < row; i++){
            for (j = 0; j < col; j++){
                if (matrix[i][j] != '*'){
                    for (a = i-1; a <= i+1; a++){
                        for (b = j-1; b <= j+1; b++){
                            if (matrix[a][b] == '*' && (a >= 0 && a < row && b >= 0 && b < col))
                                matrix[i][j] += 1;
                        }
                    }
                }
            }
        }
     
     
    cs

    이 친구를 잠깐 빼고 실행해보았더니 문제없이 프로그램이 작동했다. 그렇다면 이 부분이 잘못되었다는 게 확실한데... 정확히 어떤 부분이 잘못되었는지 알기가 너무 힘들었다.

     

    처음부터 a, b를 i-1, j-1로 두면 matrix[-1][-1]처럼 존재하지 않는 곳에 접근할 수 있다는 사실을 인지하고는 있었고, 이를 방지하기 위해 내 딴에는 if (matrix[a][b] == '*' && (a >= 0 && a < row && b >= 0 && b < col)) 라는 조건을 만들었다. 이 조건문 덕분에 실제 배열의 원소의 값만 건들기 때문에 문제가 없을 거라고 생각했다. 

     

    그런데 오류는 사라질 생각을 안 하고 나도 마땅히 다르게 고칠 방법이 떠오르지 않고... 결국 코딩 도장 사이트에 있는 질문 게시판을 뒤적여봤다. 자주 묻는 질문에 "루프를 돌면서 인덱스를 벗어난 접근을 하게 되면, 주로 m[-1][-1]과 같은 접근을 하게 되면 메모리 오류가 발생합니다."라고 언급이 되어있었고 다른 분들이 질문한 내용도 이와 관련된 것들이 많았다. 그러다"i, j 루프 다음에 y, x 루프에 진입하기 전에 값을 검사해서 루프를 건너 뛰는 게 필요합니다. 보통은 continue를 사용합니다."라는 답변을 보았고, 아예 a, b를 활용한 for 루프를 본격적으로 활용하기 전에 a, b가 조건을 만족하는지를 검사해야 하나? 라는 생각이 들었다. 나도 continue를 활용하는 방향으로 코드를 수정해봤고...

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    #include <stdlib.h>
     
     
    int main(){
        int row, col;
        int i, j;
        int a, b;
        char **matrix;
     
        scanf("%d %d"&row, &col);
     
        matrix = malloc(sizeof(char ** row);
     
        for (i = 0; i < row; i++)
            matrix[i] = malloc(sizeof(char* (col + 1));
     
        for (i = 0; i < row; i++)
            scanf("%s", matrix[i]);
     
        for (i = 0; i < row; i++) {
            for (j = 0; j < col; j++){
                if (matrix[i][j] != '*'){
                    matrix[i][j] = '0';
                }
            }
        }
     
        for (i = 0; i < row; i++){
            for (j = 0; j < col; j++){
                if (matrix[i][j] != '*'){
                    for (a = i-1; a <= i+1; a++){
                        if (a == -1 || a == row)
                            continue;
                        for (b = j-1; b <= j+1; b++){
                            if (b == -1 || b == col)
                                continue;
                            if (matrix[a][b] == '*')
                                matrix[i][j] += 1;
                        }
                    }
                }
            }
        }
     
     
        for (i = 0; i < row; i++){
            printf("%s\n", matrix[i]);
        }
     
        for (i = 0; i < row; i++)
            free(matrix[i]);
     
        free(matrix);
     
        return 0;
    }
                        
     
    cs

    결국 성공!!!!

     

    처음에 continue 조건에서 row와 col을 row+1, col+1로 착각해서 또 성공적으로 실행되지 않았는데, 이것만 제대로 고쳐주니까 잘 작동했다. 야호!

     

    그래서 나는 이미 인덱스를 벗어난 곳을 가지 않도록 조건까지 만들었는데 왜 문제가 발생하는 건지, continue를 활용하지 않고 나처럼 if문을 활용해서 작성하면 안 되는지 궁금했고, 이전에 짰던 내 코드를 디버거를 이용해서 하나씩 검사해보았다. 그러다 문득... 내 코드 속 조건 if (matrix[a][b] == '*' && (a >= 0 && a < row && b >= 0 && b < col)) 에 있는 matrix[a][b] == '*'가 이상함을 알아챘다. 확실히는 잘 모르겠지만 이 조건을 체크할 때 matrix[-1][0] 이런 식으로 인덱스를 벗어난 배열을 어쩔 수 없이 검사하게 되기 때문에 조건문이 거짓으로 판별되더라도 잘못된 메모리에 접근하게 되기 때문에 오류가 발생한 것 같다.

     

    이렇게 보니 정말 간단한 걸 가지고 끙끙거렸던 것 같은데... 그래도 아직은 조금 많이 어려운 메모리, 포인터 개념을 다시 한 번 정리하는 데 큰 도움이 된 문제였다. 너무 짜증나서 그냥 포기하거나 답을 구글링해볼까 생각도 했는데 그래도 이렇게 내 힘으로 문제를 풀어서 정말 뿌듯하다! 이런 게 바로 코딩의 참맛일까. ꉂꉂ(ᵔᗜᵔ*) 

     

    + 답 설명과 비교

     

    메모리를 할당하고 또 사용자의 입력을 받기 전에 memset 함수를 이용해서 메모리를 0으로 초기화해줘야 한다고 한다... memset 함수의 사용 또한 아직 익숙하지 않아서 잘 쓰지 않게 되는데 메모리 초기화하는 것도 많이 써봐야겠다.

     

    또 해설에서는 "단, 주변을 탐색할 때 배열의 범위를 벗어나면(y < 0 || x < 0 || y >= m || x >= n) 요소에 접근하지 않고 건너뛰어야 합니다." 즉, 나처럼 for 루프 - a, for 루프 - b 각각에서 한 번 씩 검사하는 게 아니라 한번에 검사하는 것으로 설명하고 있다. 생각해보니 그게 더 깔끔하고 괜찮은 것 같다... 해설처럼 코드를 짠다면

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
        for (i = 0; i < row; i++){
            for (j = 0; j < col; j++){
                if (matrix[i][j] != '*'){
                    for (a = i-1; a <= i+1; a++){
                        for (b = j-1; b <= j+1; b++){
                            if (a < 0 || b < 0 || a >= row || b >= col)
                                continue;
                            if (matrix[a][b] == '*')
                                matrix[i][j] += 1;
                        }
                    }
                }
            }
        }
    cs

    이렇게 되겠지? 

     

    꽤나 힘들었던... 문제풀이 끝!

    댓글

Designed by Tistory.