김펭귄 관찰일기
article thumbnail

1. Stack overflow error (스택오버플로우 에러)


백준 알고리즘 문제를 풀던 중, 스택오버플로우 에러를 만나게 되었다. 지금보다도 훨씬 코린이었던 시절에 자주 만났던 친구인데 아직까지도 서먹한 사이다. 그만 만나고 싶은 어색한 사이랄까?

 

Stack overflow

 

예외가 처리되지 않음
0x003A19D9에(cPractice.exe의) 처리되지 않은 예외가 있습니다.
0xC00000FD: Stack overflow(매개 변수: 0x00000000, 0x00702FFF).

 

 

2. Stack overflow의 발생원인



Stack overflow

스택 포인터가 스택의 경계를 넘어갈 때 발생한다.


Stack overflow는 각 단어의 의미를 생각하면 쉽게 발생원인을 찾을 수 있다. '무더기, 더미'라는 뜻을 가진 Stack은 자료구조 배울 때 얼핏 들었던, 그 'Stack'이다. (아직 Stack 개념을 잘 모르는 상태라면 아래 다시 한 번 정리해두었으니 지금은 넘어가자.) 거기에 '넘치다'는 뜻을 가지는 overflow를 더해주면 "데이터를 담아두는 더미를 넘어섰다." 라는 뜻이 된다.

 


Stack

스택은 자료 구조 (Data Structure)의 한 종류이며, 두 개의 포인터로 많은 양의 데이터를 효과적으로 관리하는 이론. C언어에서 함수 안에 선언한 지역 변수를 관리할 때 스택이 사용된다.

 

 우리가 함수에서 지역 변수 (int player_Stats 같은 친구들)를 선언하게 되면 보통 Stack에 할당된다. 함수들이 호출될 때마다 각 함수에 선언된 지역 변수들은 Stack에 할당되었다가 해제되는데

 

해당 변수의 크기가 Stack보다 크거나, 함수를 무한으로 호출하고 있을 때, 혹은 Stack을 넘어가 다른 곳에 위치하고 있는 경우 Stack overflow가 발생한다.


 역시 직접 보고 이해하는 것이 가장 빠르고 효과적이니, 바로 본론으로 들어가자. 일단 스택 오버플로우가 발생한 코드를 가져왔다.

 

#include <stdio.h>
#include <string.h>
int main () {
    int i, len;
    int result = 0;
    
    // index 101의 문자열 배열 word를 선언하고 0으로 초기화
    char word[101] = { 0, };
    
    // scanf로 입력을 받아 word에 값 저장
    scanf ("%s", word);
    
    // len은 문자열 word의 길이값
    len = (int)strlen (word);
    result = len;
    
    for (i = len - 1; i != 0; i--) {
    	if (word[i] == '=') // Stack overflow 발생 부분!! {
        	if (word[i - 1] == 'c' || word[i - 1] == 's') {
        		i -= 1;
                result -= 2;
            }
            
            ~~~

 word라는 문자열 배열의 각 인덱스 값을 확인하기 위해 for문을 사용했다. 위 코드에서 i값은 문자열 배열 word의 인덱스를 가르킨다.

 

 1번 인덱스를 체크하고 조건을 만족한다면 3번 인덱스 값을 체크하고 싶었다. 그래서 저 아래 코드를 보면 'if (~~) { i -= 2;} 이런 식으로 i 값 자체를 건들도록 코드를 짰는데, 바로 이 부분이 스택 오버플로우를 야기했다.

 

 조건식 (i != 0)을 통과한 i는 계속해서 증감식 (i--)으로 인해 20, 19, 18 한 자리씩 줄어갈 것이다. i는 for문이 반복될 때마다 1씩 줄어들 것이고, if문 아래 { i -= 1; } 로 인해 또 다시 1만큼 내려가버리게 된다. 결국에는 word[-1], word[-2], ... 이렇게 존재하지도 않는 인덱스를 체크하니 오버 플로우가 발생한 것이다.

 

 

3. Stack overflow의 해결 방법


    1) 존재하지 않는 인덱스나 주소를 찾고 있지는 않은지 확인하기

 사실 내가 지금까지 만나온 Stack Overflow 에러의 대부분은 이 경우였다. 내 두뇌의 암산 수행 능력을 항상 의심하자.

 


 

    2) 해당 변수의 크기를 Stack보다 작게 만들거나, 함수 무한 호출 막기

 잘하는 개발자들이 Stack Overflow를 만났다면, 아마 이 경우일 것이다. 해당 변수의 값이 스택보다 크거나, 함수가 무한으로 호출되는 경우 Stack overflow가 발생한다고 앞서 말했다. 그럼 대체 스택은 또 무엇이고 스택이란 녀석의 크기란 무엇이며, 그 값을 넘어선다는 것은 또 무엇인가.

 

 먼저, 우리 컴퓨터의 메모리 구조를 알아야 한다.

 

 메모리는 크게 명령문이 들어가는 Text, 전역변수와 정적변수가 담기는 Data, 우리가 동적할당할 때 사용되는 Heap, 그리고 지역변수가 담기는 Stack, 이렇게 4가지로 이루어져 있다.

 Stack은 높은 주소 (High Address)부터 낮은 주소 (Low address)로 메모리에 할당되며, LIFO (Last In First Out) 방식으로 데이터를 관리한다. 처음 들으면 많이 어려울 수 있다. Visual Studio 기준으로 Stack의 기본 사이즈는 1MB이니 쉽게 생각하면, 1MB 용량의 'Stack'이라는 상자에 그릇을 차곡차곡 쌓고 필요할 때마다 위에서부터 하나씩 그릇을 꺼내는 것과 같다.

int Loop(){
	Loop();
}

 

 이러한 코드의 Loop 함수가 있다고 가정해보자. 위 Loop 함수는 재귀함수 (자신을 재참조하는 함수)로 실행하면 다시 또 Loop 함수를 실행시킨다. 한 마디로, Stack이라는 상자에 Loop라는 그릇을 계속해서 쌓는 함수이다. Loop를 쌓고 쌓고 또 쌓다보면 언젠가 상자를 넘어서 쌓게 될 것이다. 바로 넘어서는 그 순간이 Stack 사이즈인 1MB를 넘는 순간이고, 바로 그 때 Stack overflow가 발생한다.

 

 TMI지만, 해커들이 보통 Stack Overflow를 사용해 해킹을 시도한다고 한다. 계속해서 Stack에 접시를 쌓아서 Stack Overflow가 발생되도록 만든 다음, 넘쳐나는 접시를 사용해 다른 곳에 접근하는 방식이라는데 꽤나 흥미로웠다.

 

 그렇다면 해결책은? 아주 간단하다. Stack이라는 상자가 넘치지 않는 선까지만 그릇을 쌓으면 된다.

 


 

    3) Stack이 아닌, Heap에 동적할당하기

 우리가 지역 변수를 선언하면 해당 변수는 Stack에 자리 잡는다. Stack Overflow가 Stack이 가득 차서 발생하는 거라면 Stack에 저장하지 않고 다른 곳에 저장하면 되잖아?

 바로 그럴 때 '동적할당'이라는 것을 사용한다. 동적할당을 사용하여 저장하게 되면, 우리의 컴퓨터는 Stack이 아닌 Heap라는 곳에 그릇을 쌓는다. 당연히 Stack이 가득 찰 걱정은 안 해도 되니까 전부 동적할당으로 접시 올려버리자! 라고 생각할 수 있지만 반은 맞고 반은 틀리다. 자세한 내용은 아래 링크를 참조하자.

 

https://penguin-kim.tistory.com/36

 

동적할당 (malloc)

1. 동적할당 (malloc)  동적할당이란 말 그대로 "동적으로" 할당하는 것을 말한다. 2. 동적할당 예제 #include <> 내용 code 내용 3. 메모 내용 4. 출처

penguin-kim.tistory.com

 


    4) Stack의 크기 자체를 조절하기

 마지막으로 Stack의 크기 자체를 늘려 Stack overflow를 막는 방법이 있다.

 

 Visual studio 기준으로 해당 프로젝트 상단에 프로젝트 - xx 속성을 누른다. 구성 속성 - 링커 - 시스템 - 스택 예약 크기에 필요한 크기만큼 적어주면 말끔하게 해결할 수 있다. Visual Studio의 기본 스택 크기는 1MB (1024 * 1024)이니, 참고하여 적어주도록 하자. 예를 들어 기본 Stack의 크기를 100MB로 만들고 싶다면 104,857,600 (1024 * 1024 * 100)를 적어주면 된다.

 

프로젝트 - 속성

 

구성 속성 - 링커 - 시스템 - 스택 예약 크기

 

 이렇게 4가지 방법으로, Stack overflow를 해결할 수 있다.

 

 

4. 출처


메모리 구조 이미지 : https://st-lab.tistory.com/198

profile

김펭귄 관찰일기

@Penguin.Kim