티스토리 뷰

반응형

 

 

주어진 학습목표를 이루기 위해서 이해를 우선으로 두고 공부할 것이다.

들어가기 전에 학습목표, 핵심 단어는 강의 자료를 가져올 것이고 그 이후로는 내가 이해하고 공부한 부분을 직접 적고, 마지막에는 배운 점이나 느낀 점을 적도록 하겠다. 

 

 


 

 

들어가기 전에

컴퓨터는 우리가 작성한 프로그램을 구동하기 위해 다양한 물리적 장치를 사용합니다. 그중 하나는 메모리로, 프로그램이 필요한 정보가 저장되는 곳입니다. 메모리의 용량은 무한하지 않기 때문에, 때때로 프로그램에서 우리가 의도하지 않은 오류가 발생하기도 합니다.

 

학습 목표

메모리 용량이 프로그램의 구동에 미치는 영향을 설명할 수 있습니다. 

 

핵심 단어

  • 메모리
  • 오버플로우

 

 


 

메모리

 

RAM은 모든 프로그램이 실행 중 저장되는 곳이다. 그리고 모든 파일들이열려있는 동안 저장되는 곳이다. 컴퓨터가 여러 일들을 한 번에 할 때 기억하기 위해 사용되는 것이다.

 

하지만 이 역시 어디까지나 하드웨어이기 때문에 그 성능은 유한하다. 이 말은 컴퓨터의 메모리 용량은 유한하다는 것이고, 즉 컴퓨터가 할 수 있는 일에는 한계가 있다는 말이다. 저장공간이 유한하므로 저장 가능한 숫자 역시 한계가 있다. 

 

 

 

 

 

부동점 소수의 부정확성

 

아래와 같이 실수 x, y를 인자로 받아 x 나누기 y를 하는 프로그램을 작성해 본다. 나눈 결과를 소수점 50자리까지 출력되게 한다. 

#include <cs50.h>
#include <stdio.h>

int main(void)
{
    // 사용자에게 x 값 받기
    float x = get_float("x: ");

    // 사용자에게 y 값 받기
    float y = get_float("y: ");

    // 나눗셈 후 출력
    printf("x / y = %.50f\n", x / y);
}

 

이 때 정확한 결과는 0.1이 되어야 하지만, float에서 저장 가능한 비트 수가 유한하기 때문에 다소 부정확한 결과를 내게 된다. 

x: 1
y: 10
x / y = 0.10000000149011611938476562500000000000000000000000

 

float는 32bit를 사용하는데 그 두 배인 64bit를 사용하는 double을 사용하면 좀 더 정확한 결괏값이 나온다. 하지만 중요한 것은 결국에는 컴퓨터의 저장 값에는 한계가 있고, 완벽하지 않다는 것이다. 그래서 컴퓨터가 최대치까지 계산해서 1/10에 가장 가까운 값을 저장하는 것이다. 

 

 

 

 

 

정수 오버플로우

 

비슷한 오류로, 1부터 시작하여 2를 계속해서 곱하여 출력하는 프로그램을 작성해 본다. 무한루프가 되어 1초동안 쉬도록 하는 sleep() 함수를 실행시킨다. 이 함수를 실행하기 위해서는 <unistd.h> 파일을 포함시켜야 한다. 

 

#include <stdio.h>
#include <unistd.h>

int main(void)
{
    for (int i = 1; ; i *= 2)
    {
        printf("%i\n", i);
        sleep(1);
    }
}

 

 

 

우리가 변수 i를 int로 저장하기 때문에, 2를 계속 곱하다가 int 타입이 저장할 수 있는 수를 넘은 이후에는 아래와 같은 에러와 함께  0이 출력된다.

...
1073741824
overflow.c:6:25: runtime error: signed integer overflow: 1073741824 * 2 cannot be represented in type 'int'
-2147483648
0
0
...

 

 

아래는 cough함수를 원하는 횟수만큼 출력할 수 있게 한 코드이다. 맨 아래의 void cough(int n){...} 에서 cough( ) 안의 int n 은 함수가 입력값을 받아서 int 형식을 갖는 n이라는 변수에 저장하겠다는 의미이다. 이 함수를 통해 n번동 안 cough를 출력하는 for 루프가 만들어진다. 

#include <stdio.h>

void cough(int n);

int main(void)
{
    cough(3);
}

void cough(int n)
{
    for (int i = 0; i < n; i++)
    {
        printf("cough\n");
    }
}

 

십진수 7을 이진수로 표현하면 111 이다. 십진수 8은 1000이다. 만약 숫자 혹은 비트가 3개뿐이라면 8은 어떻게 표현될까? 이때 8에서 오버플로우가 생기고 앞으로 가져왔던 1이 사라지면서 실제로 저장된 값은 000이 된다. 

 

이처럼 정수를 계속 키우는 프로그램에서 10억을 넘기자 앞으로 넘어갈 1의 자리가 없어지게 된 것이다.

int에서는 32개의 비트까지 실행할 수 있기 때문에 그 이상의 숫자는 저장할 수 없다.

 

이런 오버플로우 문제는 실생활에서도 종종 발견된다. 

 

1999년에 큰 이슈가 되었던 Y2K 문제는 연도를 마지막 두 자리수로 저장했던 관습 때문에 새해가 오면 ‘99’에서 ‘00’으로 정수 오버플로우가 발생하고, 새해가 2000년이 아닌 1900년으로 인식될 것이라는 문제였다. 다행히도 세계는 수백만 달러를 투자해서 프로그래머들에게 더 많은 메모리를 활용해서 이를 해결하도록 했다. 이는 통찰력 부족으로 발생한 아주 현실적이고 값비싼 문제였다.

 

또 다른 예시는 비행기 보잉 787이다.  보잉 787에서는 구동 후 248일이 지나면 모든 전력을 잃는 문제가 있었다. 이 일수가 지나면 강제로 안전 모드로 진입했기 때문이다. 이는 소프트웨어의 변수가 248일이 지난 뒤에 오버플로우가되어 발생한다는 뜻이다.  248일을 1/100초로 계산하면 대략 2의 32제곱이 나온다. 보잉을 설계할때 사용한 변수보다 너무 커진 게 원인이었다. 이를 해결하기 위해 보잉은 248일마다 주기적으로 재가동을 하여 변수를 다시 0으로 리셋했다. 이런 문제는 오늘날 세상에서도 일어날 수 있다. 

 

따라서 다루고자 하는 데이터 값의 범위를 유의하며 프로그램을 작성하는 것이 중요하다.

 

 


 

 

생각해보기

 

Y2K와 보잉787과 같은 문제를 방지하기 위해서는 프로그램을 어떻게 설계해야 할까요?

 

프로그램 설계 시 데이터 값의 범위를 잘 생각해서 충분한 메모리 공간을 확보해야 한다. 또 오버플로우 방지를 위해 한계치에 가까워질 때 초기화가 될 수 있도록 프로그래밍한다. 

 

 

며칠 전 알고리즘을 풀다가 결괏값에서 마지막에 1234567로 나누어 떨어지는 값을 반환하라는 문제가 있었다. '왜 굳이 이렇게 나누라고 하지?'라는 생각을 했는데 그때 정수 값에 한계가 있다는 것을 알았다. 며칠 지나지 않아 이렇게 메모리 한계에 대해 다루게 되어 더 기억에 남을 것 같다.

 

 

 

 

 

 


Reference : CS50

 

반응형