Part 08 함수 ©우균, 창병모 이 슬라이드는 부산대학교 우균이 작성하였습니다. 오류나 수정할 사항 있으면 woogyun@pusan.ac.kr로 연락 주세요.
이 장의 내용 함수 개요 프로시저 함수호출 메커니즘 변수와 유효범위 재귀함수 매크로 함수
8.1 함수 개요
함수 개요 함수란? 함수 주변 상황 함수(function; 函數): 상자 수; 상자에 수를 넣으면 수가 나옴 자동판매기와 유사함 수학에서 함수는 대응관계(mapping)를 의미함 함수 주변 상황 인수: 함수에 들어가는 값 정의구역(domain)의 원소 리턴 값: 함수가 되돌려주는 값 공변역(co-domain)의 원소
C 언어에서 함수 프로그래밍 분야에서 함수란? 수학 함수와 다른 점 printf의 부수효과 예: 작은 프로그램(subprogram) 인수를 받아서 리턴 값을 내는 모듈 수학 함수와 다른 점 프로그래밍에서 함수는 부수효과(side-effects)를 낼 수 있다. 심지어 리턴 값이 아예 없을 수도 있다. 리턴 값이 없는 함수를 프로시저(procedure)라고 부른다. printf의 부수효과 예:
함수 정의 방법 C 함수 정의 형태 C 함수 정의 예 예쁘게 들여 씁시다. 리턴타입 함수이름(매개변수목록) { 문장들 } int add1( int x ) { return x + 1 ; } 예쁘게 들여 씁시다. int add1(int x) return (x + 1); 함수 헤더(function header) 함수 본체(function body) 매개변수 매개변수 타입 리턴타입 리턴 값 x add1 x+1
return 문과 리턴타입 return 문 수학함수와 비교 형식: return 수식; 함수가 어떤 값을 돌려주는지 명시함 리턴 값(return value)은 수식으로 주어지는데 수식에 괄호를 사용하면 이해하기 쉽다. 리턴 값은 리턴타입(return type)과 일치해야 한다. 수학함수와 비교 인수 자료형: 정의구역 리턴타입: 공변역 double half(int x) { return (x / 2.0); } half: int double where half(x) = x / 2.0
add1half.c (1/2) 인수보다 하나 큰 값을 구하는 함수 인수 값의 반을 구하는 함수
add1half.c (2/2) 사용자 정의 함수 호출
함수 프로토타입 함수 프로토타입(prototype)이란? 왜 함수 프로토타입이 중요한가? 프로토타입 작성 예 함수 사용방법만 명시한 것 어떤 자료형의 인수를 몇 개 받고 어떤 자료형의 리턴 값을 내는지 명시함 함수의 정의구역과 공변역을 명시함 왜 함수 프로토타입이 중요한가? 함수를 사용(호출)하기 전에 함수 정의나 함수 프로토타입 선언 중 최소한 하나는 나타나야 함 프로토타입 작성 예 매개변수 이름 생략 가능 int add1(int x); double half(int x); int add1(int); double half(int);
prototype.c (1/2) add1과 half의 prototype 함수 프로토타입 선언 후에는 함수 정의가 나타나지 않았어도 선언한 함수를 사용할 수 있다. add1과 half의 prototype
prototype.c (2/2) add1과 half 정의
8.2 프로시저
프로시저 C 함수 분류 연산자, 명령어와 함수의 유사성 값 리턴 함수(value-returning function): 값을 리턴하는 함수, 수학적 함수와 유사함 void 함수(void functions): 값을 리턴하지 않는 함수, 부수효과만을 이용하여 어떤 작업을 수행 void 함수를 “프로시저(procedure)”라고 부르기도 한다. 연산자, 명령어와 함수의 유사성 값 리턴 함수(≈ 연산자): 사용자 정의 연산자 void 함수(≈ 명령어): 사용자 정의 명령어 프로그래밍이란 해법을 연산자와 명령어를 이용하여 작성하는 것 프로그래밍 언어에서 제공하는 연산자와 명령어가 충분하지 않다면 나름대로 연산자와 명령어를 정의하여 사용한다!
void.c (일부) 함수 add1과 half를 이용하는 void 함수. 이 함수 때문에 main이 간단해졌다. 실행결과: 정수를 하나 입력하세요. 123 123 의 바로 다음 정수는 124 이고 123 의 반은 61.5 입니다. 제가 맞게 계산했나요 ? 함수 add1과 half를 이용하는 void 함수. 이 함수 때문에 main이 간단해졌다.
작명법에 관한 도움말 이름은 구체적으로 작성한다 여러 단어로 이루어진 이름은… 적절하지 못한 이름: process(); test(); control(); 적절한 이름: dot_product(); 두 벡터의 내적을 구하는 함수 all_positive(); 컨테이너 원소가 모두 양수인가 검사 rotate_right(); 순차 컨테이너 고리형태로 간주하고 원소를 우측으로 이동시킴 여러 단어로 이루어진 이름은… 밑줄을 이용하는 방식: 전통적인 C 프로그래밍 방식 낙타체: 대소문자를 번갈아 사용해가며 단어를 구별하는 방식 Java에서 사용하는 방식 dotProduct(); allPositive(); rotateRight();
하향식 프로그래밍 하향식 프로그래밍(Top-Down Programming) 단계 하향식 프로그래밍의 특징 문제를 한 문장으로 기술한다. 주어진 문제를 해결하기 위해 필요한 세부 작업을 기술한다. 더 이상 수행할 세부 작업이 없을 때까지 다음을 반복한다. C에서 기본적으로 제공하는 기능을 이용하여 세부 작업을 바로 구현할 수 있다면 구현한다. 세부 작업이 여전히 큰 규모라면 단계 2를 반복하여 다시 작업을 나눈다. 하향식 프로그래밍의 특징 바로 구현할 수 있을 단계까지 문제를 점진적으로 세분화하여 내려가는 방식(stepwise refinement) 한 문장으로 나타낸 문장을 “전체문제(top)”라고 한다.
하향식 프로그램밍 예 Top: 정수를 하나 읽어 들인 후, 그 수의 바로 다음 정수와 그 수를 2로 나눈 수를 출력한다. 단계별 세분화
topDown2.c (1/2) '전체문제'를 기술하는데 필요한 함수들 함수 main에는 '전체문제'를 기술한다. '전체문제'를 기술하는데 필요한 '명사'는 변수나 상수로 선언한다. '전체문제'를 기술하는데 필요한 '동사'는 함수를 이용한다.
topDown2.c (2/2)
추상화 추상화(abstraction) 함수의 중요성 중요한 것만 남기고 불필요한 것은 과감히 생략하는 기법 프로그래밍의 핵심 키워드(keyword): 프로그래밍에서 가장 중요한 기법임 함수의 중요성 구체적인 동작을 추상화하기 위한 기본적인 방법 하향식 프로그래밍의 기초가 됨 추상화
8.3 함수호출 메커니즘
함수 호출과 복귀 개요 기본적인 방식 복귀지점 호출자 함수 A는 함수 B를 호출하면 수행을 멈춘다 호출된 함수 B는 1. return 문을 만나거나 2. 함수 몸체 끝에 도달할 때까지 수행한다 호출된 함수 B의 수행이 끝나면 호출지점 바로 다음으로 복귀한다. 복귀지점 복귀지점은 문장 단위가 아닐 수도 있다. 예를 들어, double result = add1(2) * half(3); printf("result = %f\n", result); 에서 half()가 add1()보다 먼저 호출되었다면 그 복귀지점은 add1(2)가 된다.
제어흐름과 자료흐름 제어흐름 자료흐름 프로그램 수행 순서를 제어흐름(control flow)라고 함 한 함수 내에서 제어흐름은 제어구조에 의해 결정됨 함수 사이의 제어흐름은 함수호출에 의해 결정됨 자료흐름 데이터가 전달되는 순서를 자료흐름(data flow)라고 함 한 함수 내에서 자료흐름은 대입문에 의해 결정됨 함수 사이의 자료흐름은 인수전달(parameter passing)과 반환값 전달에 의해 결정됨 함수호출 메커니즘 함수 호출, 복귀 시에는 제어흐름과 자료흐름이 함께 변경되므로 주의해야 한다.
제어흐름 예 main 함수에서 f를 호출했다고 하면 f는 두 가지 방식으로 복귀할 수 있다
자료흐름: 인수전달 함수 인수(argument)는 값을 구하여 매개변수(parameter)에 "복사"하여 전달한다. 예: main 함수에서 print_sign(5);를 호출했을 경우
자료흐름: 임시변수 사용 인수가 수식인 경우에는 호출자의 임시공간(임시변수)를 활용하여 수식 값을 구한 후 전달한다. 예: 다음과 같이 호출한 경우 수식 b - a 값은 main의 임시공간에서 계산된다.
자료흐름: 리턴 값 전달 값 반환 함수 호출 값 반환 함수 호출은 수식으로 간주하기 때문에 리턴 값도 역시 호출자의 임시변수에 저장한다. 값 반환 함수는 사용자 정의 '연산자'라는 사실을 기억하자. 예: 다음과 같이 호출한 경우 read_int() 값과 num*num 값은 임시공간에 저장된다.
sign.c 제어흐름 자료흐름
함수호출 메커니즘 정리 함수 f가 함수 g를 호출했을 때, 함수호출 메커니즘 f의 본체 내에서 g의 실인수 값을 계산한다. g의 본체 끝에 도달하거나 수행 중 return 문에 도달할 때까지 g의 본체 내 문장을 수행한다. 리턴 수식이 있는 return 문에 도달한 경우 리턴 값을 구한다. 제어흐름이 g에서 f로 이전되며 리턴 값이 있으면 f에 전달된다. g 영역은 소멸된다. f의 본체가 이전에 멈추었던 위치 바로 다음부터 계속 수행된다.
8.4 변수와 유효범위
유효범위 유효범위(scope)란? 변수 유효범위의 결정 어떤 변수를 사용할 수 있는 프로그램 내의 범위를 그 변수의 유효범위라고 한다. 유효범위를 두지 않는다면 모든 변수를 프로그램 전체에서 사용할 수 있어야 하므로 매우 복잡해진다. 변수 유효범위의 결정 변수의 유효범위는 선언된 위치에 따라 결정됨 유효범위에 따라 지역변수(local variable), 비지역 변수(global variable)를 결정함
유효범위에 따른 변수 분류 지역변수(local variable) 비지역변수(nonlocal variable) 함수 내에 선언된 변수 함수 내부에서만 볼 수 있음 함수가 호출될 때 생성되므로 동적변수(dynamic variable)임 주의: 지역변수이지만 정적변수로 선언되는 경우도 있음 비지역변수(nonlocal variable) 함수 밖에 선언된 변수 여러 함수에서 볼 수 있음 전역변수(global variable): 프로그램 전체에서 볼 수 있음 파일범위변수(file scope variable): 같은 파일 내의 함수에서만 볼 수 있음 프로그램이 시작할 때 생성되므로 정적변수(static variable)임
비지역 변수를 통한 데이터 전달 가능하다. 하지만… 매개변수를 통해 데이터를 전달하는 것이 바람직함. 비지역 변수를 통해 데이터를 전달하면 데이터를 전달하는 함수와 데이터를 전달받는 함수 외의 함수도 그 데이터를 볼 수 있음 불필요한 의존성(dependency)이 발생하므로 바람직하지 않음 매개변수를 통해 데이터를 전달하는 것이 바람직함. '호동' 함수가 '선영' 함수에게 사랑을 고백하려면, 칠판에 고백하는 것보다 '선영'의 다이어리에 몰래 써 두는 것이 더 안전하고 로맨틱하다.
nonlocal.c 비지역 변수 GX는 세 함수에서 모두 볼 수 있다. 이렇게 선언된 GX는 전역변수다. 다른 파일에서 GX를 볼려면 extern int GX; 선언을 해 주어야 한다. 실행결과: [main 에서] GX = 12345 [f 에서] [g 에서]
외부변수란 무엇인가요? 아주 오랜 옛날… 그러나 지금은… 초창기 C에서는 함수 밖에 선언된 변수를 모두 '외부변수(external variable)'라고 불렀습니다. 그래서 아직도 다음과 같이 선언할 수 있습니다. int main() { extern int GX; printf("GX = %d\n", GX); … } 그러나 지금은… 다른 파일에 정의된 변수를 외부변수라고 하는 것이 보통입니다. 즉, extern 선언이 꼭 필요한 경우에 한해서 extern으로 선언된 변수를 보통 외부변수라고 합니다.
파일범위 변수 함수 밖에 선언된 변수 중 static으로 선언된 변수 파일범위 변수는 전역변수와는 달리 다른 파일에서는 볼 수 없음 nonlocal1.c nonlocal2.c 볼 수 있음 볼 수 없음
정적 지역변수 정적변수(복습) 지역 정적변수 프로그램이 시작될 때 생성되어 프로그램이 종료될 때까지 지속되는 변수 모든 비지역변수는 정적변수(static variable)임 지역 정적변수 지역변수를 선언할 때, static 키워드를 붙이면 이 변수도 정적변수가 됨 정적변수는 함수 영역이 사라진다고 해도 사라지지 않음 따라서 이전 호출 종료시 저장되어 있던 값을 다음 호출 시 사용할 수 있음 이런 변수를 '역사에 민감한 변수(history sensitive variable)'라고 함
count.c 정적변수 초기화 모든 정적변수는 초기화해야 한다. 초기화하지 않았을 경우엔 0으로 초기화 된다. 지역 정적변수 타입이 생략되었으므로 자동으로 int형이다. 호출 사이에 값이 보존된다. 실행결과: count = 1 count = 2 count = 3 count = 4 count = 5
여러 파일로 구성된 프로그램 빌드 MSVC IDE에서… ‘솔루션 탐색기’에서 ‘소스 파일’ 폴더모양을 마우스 우측버튼으로 클릭한 후 ‘추가’ ‘새 항목’ 선택
변수 분류 유효범위(scope): 해당 변수를 볼 수 있는 범위 지속시간(lifetime): 변수가 생성과 소멸 사이의 시간 지속시간이 길어짐 지속시간 유효범위 동적변수 (dynamic variable) 정적변수 (static variable) 지역변수 (local) 함수 본체 내부에 그냥 선언 블록 내부에 static으로 선언 파일범위변수 (file-scope) 선언 불가 함수 외부에 전역변수 (global) 유효범위가 넓어짐
8.5 재귀함수
재귀함수 재귀호출(recursive call) 재귀함수(recursive function) 재귀적인 그림 어떤 함수가 종료되지 않은 상황에서 자신을 다시 호출하는 것 재귀함수(recursive function) 재귀호출을 이용하는 함수 재귀적인 그림 Niklaus Wirth, Algorithms + Data Structures = Programs (발췌)
fact.c 계승(factorial) 정의 재귀적인 계승 정의 n ! = n × (n – 1) × … × 1 0 ! = 1 재귀호출 실행결과: 자연수를 하나 입력하세요: 10 fact(10) = 3628800 입니다.
재귀함수의 제어흐름 재귀함수의 호출과 리턴 재귀함수의 장단점 재귀함수의 경우에도 호출 수와 리턴 수는 같다. 따라서 재귀함수는 여러 번 호출되었다가 다시 여러 번 리턴된다. 재귀함수의 장단점 장점: 프로그램 구조가 깔끔하다. 단점: 재귀함수의 호출과 리턴에 따른 부담으로 인해 수행 속도가 느려질 수 있다.
8.6 매크로 함수
max.c : 매크로 함수 매크로 함수 인수를 받는 매크로 함수와 유사하지만 실제로 함수는 아님 실행결과: MAX(i, j) 는 전처리기를 거치면 (i > j)? i: j 로 확장된다. 실행결과: 두 수를 입력하세요. 2 3 MAX(2, 3) = 3
매크로 오류 매크로 오류 매크로 오류 예 매크로 함수는 실제로 함수가 아니므로 예기치 못한 오류를 발생시킬 수 있음 이러한 오류를 매크로 오류(macro anomaly)라고 한다. 매크로 오류 예 int i = 2, j = 3, max = 0; max = MAX(++i, ++j); printf("i = %d, j = %d, max = %d\n", i, j, max); 어떤 값이 출력될까? 이유는? i = 3, j = 5, max = 5 max = (++i > ++j)? ++i: ++j;
Key Point
Key Point 1 프로그래밍에서는 작은 프로그램을 함수라고 한다. 함수는 주로 값을 리턴하기 위해 사용하지만 이 외에도 다른 부수효과를 일으킬 수 있다. 함수 헤더는 함수를 호출하기 위한 방법(호출 인터페이스)을 나타내고 함수 본체는 실제로 함수가 수행되는 방법(구현)을 나타낸다. 함수 헤더에서 인수 자료형과 리턴타입 등의 정보를 알 수 있다. return 명령어는 함수 결과 값을 리턴하기 위해 사용된다. 리턴 값을 계산하기 위한 수식을 괄호로 묶을 필요는 없지만 괄호로 묶으면 프로그램을 더 이해하기 쉽다. 함수 프로토타입이란 함수 호출 방법만을 기술한 것을 뜻한다. 결국 함수 헤더 부분의 정보만 따로 떼어 내어 부를 때 프로토타입이라고 한다. 함수 선언이란 것은 함수의 프로토타입을 명시하는 것을 뜻한다. 함수에 대해서도 ‘사용전 선언(declaration before use)’ 규칙이 적용되는데, 구체적으로 말해서 함수 f를 사용하기 위해서는 그 전에 미리 f의 정의나 f의 선언이 나타나야 한다.
Key Point 2 값을 리턴하지 않고 부수효과만 일으키는 함수를 프로시저라고 한다. 이에 대하여 항상 값을 리턴하는 함수를 값 리턴 함수라고 한다. C 프로그램에서 프로시저는 void 함수로 나타낸다. 함수 이름은 함수가 하는 일을 구체적으로 나타낼 수 있도록 작성해야 한다. 여러 단어로 함수 이름을 구성해야 하는 경우에는 밑줄 _을 이용하거나 대소문자를 이용하여 각 단어를 구별한다. 해결해야 할 문제를 하나의 간단한 문장으로 기술하고 문제의 하위 작업을 점진적으로 세분화하여 구현해 나가는 방식을 하향식 프로그래밍이라고 한다. 수행할 차례를 넘기는 것을 제어이전이라고 한다. return 문은 값을 리턴하는 역할을 할 뿐만 아니라 제어흐름을 넘기는 역할도 수행한다.
Key Point 3 함수에 전달되는 값을 인수(argument)라고 하고 인수를 저장하기 위한 변수를 매개변수(parameter)라고 한다. 어떤 경우에는 인수와 매개변수 용어를 뒤섞어 사용하기도 하는데, 이 경우에는 인수를 실인수 또는 실매개변수라고 하고 매개변수를 형식인수 또는 형식매개변수라고 부르기도 한다. 함수 호출 메커니즘은 호출자와 피호출자 사이의 제어흐름과 자료흐름을 관리하는 메커니즘이다. 함수가 호출되고 리턴될 때 제어흐름이 변경된다. 제어흐름이 변경됨에 따라서 피호출자의 데이터 영역이 생성되고 소멸된다. 동적 변수는 프로그램 수행 중에 생성되거나 소멸되는 변수를 말하고 정적 변수는 프로그램 수행 시작 시 생성되었다가 프로그램 종료 시 소멸되는 변수를 말한다. static 표시가 없는 지역변수는 동적 변수이고 전역변수와 파일 범위 변수는 정적 변수이다.
Key Point 4 비지역 변수를 이용하여 함수들 사이에 데이터를 전달할 수 있다. 그러나 외부변수를 이용하면 함수들 사이의 자료 흐름이 명확하게 드러나지 않고, 함수들 사이에 불필요한 의존성이 발생하므로 이런 방식은 자제하는 것이 좋다. 지역변수를 선언할 때 static 키워드를 붙이면 정적 변수가 된다. 정적 변수에 대한 초기화는 한 번만 수행된다. 재귀함수란 함수 본체를 수행하는 도중에 자신을 다시 호출하는 함수를 말한다. 매크로 인수를 그대로 복사함으로 인해서 발생되는 매크로 함수 오류를 매크로 오류라고 한다. 매크로 오류는 완벽히 제거할 수 없다. 매크로 오류를 방지하려면 일반 함수를 사용해야 한다.
요약(1/4) 함수 함수 프로토타입 함수는 원래 대응관계를 의미한다 프로그래밍에서 함수는 '인수'에 '리턴 값'을 대응시키는, 독립된 프로그램을 의미한다 프로그래밍에서 함수는 '부수효과(side effect)'를 발생시킬 수 있으며 리턴 값을 돌려주지 않을 수도 있다. 함수 프로토타입 함수 헤더에는 함수 호출에 필요한 정보가 담겨 있다. 함수 헤더 부분만 따로 떼어 선언한 것을 '프로토타입(prototype)'이라고 한다. 함수 프로토타입이 먼저 나타나면 함수를 정의하기 전에 함수 호출 구문을 사용할 수 있다.
요약(2/4) 리턴 값에 따른 함수 분류 하향식 프로그래밍 추상화 값을 리턴하는 함수를 '값 반환 함수(value-returning function)'라고 하고 값을 리턴하지 않는 함수를 'void 함수(void function)'이라고 한다. void 함수는 부수효과에만 의존하는 함수로서 '프로시저(procedure)'라고도 부른다. 하향식 프로그래밍 '전체문제(top)'를 점차 세분화하여 바로 구현할 수 있는 단계까지 기술해 내려가는 프로그래밍 방식을 하향식 프로그래밍(top-down programming)이라고 한다. 함수는 하향식 프로그래밍에 사용되는 기본적인 추상화 장치다. 추상화 중요하지 않은 것을 생략하는 것을 추상화(abstraction)라고 한다. 추상화는 프로그래밍에서 가장 중요한 요소다.
요약(3/4) 함수 호출 메커니즘 변수의 지속시간과 유효범위 변수 분류 함수 호출 및 복귀 시에는 제어흐름이 변경되며 자료흐름이 진행된다. 함수가 호출되면 함수 고유의 데이터 영역이 생성된다. 인수는 실인수 값을 매개변수로 '복사하여' 전달한다. 리턴 값은 호출자의 데이터 영역에 '복사하여' 전달한다. 변수의 지속시간과 유효범위 변수를 볼 수 있는 범위를 '유효범위(scope)'라고 하고 변수가 지속되는 시간을 '지속시간(lifetime)'이라고 한다. 변수 분류 유효범위에 따라: 지역변수, 파일범위 변수, 전역변수 지속시간에 따라: 동적변수, 정적변수 파일범위 변수와 지역 정적변수를 선언할 때에는 static 키워드를 사용한다.
요약(4/4) 분리 컴파일 재귀함수 매크로 함수 여러 파일로 하나의 프로그램을 작성할 수 있으며, 이 때, 컴파일러는 파일 하나씩 컴파일한 후 링킹한다. 재귀함수 자신을 호출하는 것을 재귀호출(recursion)이라고 하며, 재귀호출을 이용하는 함수를 재귀함수(recursive function)이라고 한다. 매크로 함수 매크로 인수를 이용하여 함수 효과를 내는 매크로를 '매크로 함수(macro function)'라고 부른다. 매크로 함수는 진짜 함수가 아니므로 예기치 않은 오류를 발생시킬 수 있다. 이러한 오류를 '매크로 오류(macro anomaly)'라고 한다.
프로그래밍 실습
▶ 프로그래밍 실습 1 매달 초에 금액 a를 n개월 동안 정기적금에 불입하고자 한다. 정기적금의 이율이 r이라고 할 때 만기 후 원리금 합계를 계산하는 프로그램을 작성하라. 기간이 n일 때 원리금 합계 산출 공식은 다음과 같다. 이 공식을 이용하여 원리금 합계를 산출하는 프로그램을 작성하라. 월초 불입액이 a이고 월이율이 r일 때 1개월 후의 원리금 합계는 a(1+r)이 된다. 다시 2개월 초에 a만큼 불입한다면 2개월 후의 원리금 합계는 (a(1+r) + a)(1+r)이다. 이런 식으로 n 개월만큼 불입액을 계산할 수 있다. 이 방식에 따라서 원리금 합계를 계산하는 프로그램을 작성하라.
▶ 프로그래밍 실습 2 하노이 탑은 다음 그림처럼 원반 n 개로 구성된 탑이다. 첫 번째 막대기에 있는 하노이 탑을 세 번째 막대기로 옮기고자 한다. 원반은 한 번에 하나씩만 옮길 수 있으며 옮기는 과정 중에 작은 원반 위에 큰 원반을 올리면 안 된다. 이 때, 어떻게 원반을 움직여야 하는지 출력하는 프로그램을 작성하라. 막대기 1의 맨 위 원반을 2의 맨 위로 옮기는 것은 다음과 같이 출력한다. 1 -> 2 프로그램은 하노이 탑 높이 n만 입력으로 받고 막대기 1의 높이 n인 하노이 탑을 막대기 3으로 옮기는데 필요한 원반 이동을 출력한다.