카테고리 없음

Lecture 8: Function

용학사 2024. 12. 2. 22:16

8-1) 함수 정의

함수는 함수 정의(header)와 본체(body)로 이루어진다.

함수정의는 리턴 타입, 이름, 매개변수를 선언한다.

만약 void이면 리턴 값이 없는 함수 즉 프로시저이다.

 

타입 없는 함수 정의

Lisp/Scheme, JavaScript, Python 등과 같은 언어에서는 변수의 타입을 선언하지 않고 바 로 사용할 수 있다.

 

C/C++,java,S

오직 함수만 있고 프로시저는 리턴 타입이 void인 함수이다.

 

Pascal, Ada 

프로시저와 함수를 구분한다. 프로시저 정의는 procedure로 시작하여 함수 정의는 function으로 시작한다.

 

Modula-2

함수와 프로시저를 모두 프로시저라고 부른다. 

 

8-2) 매개변수 전달

함수 호출에서 사용된 수식(expr)들이 인자이다.

예를 들어 다음과 같이 max함수 호출에서 사용된 a,b는 실매개변수(actual parameter) 혹은 인자(argument)이다.

c=max(a,b);

 

값 전달(pass by value)

가장 간단한 매개변수 전달 방법으로 거의 모든 언어가 기본적인 매개변수 전달 방법으로 제공

1. 수식(expression)인 인자 값들을 계산한다.

2. 계산된 값들을 대응되는 매개변수에 전달한다.

     매개변수는 전달된 값으로 초기화 된다.

3. 함수 본체를 실행한다.

x와 y는 값이 교환되지만 a,b는 값이 바뀌지 않는다.

 

참조 전달(pass by reference)

참조 전달은 함수를 호출할 때 인자의 값이 아니라 인자에 대한 참조를 전달하는 방법으로 참조라는 용어는 주소 혹은 포인터와 비슷한 의미이지만 사용할 때마다 그 주소를 자동으로 따라가는 자동 주소참조(automatic dereferencing)가 이루어진다는 면에서 약간 다르다.

교환가능

문제점:

예제8
void ack(int& x, int& y){
	x=2;
    a=3;
    y=4;
}

최종적으로 a는 4가 된다.

이명이 존재하면 프로그램의 의미를 쉽게 파악하기 힘들다

 

값-결과 전달(pass by value-result)

참조 전달의 효과를 내면서 이명의 문제점을 해결하기 위한 매개변수 전달 방법

(결국 값-> 참조-> 값-결과 순으로 업그레이드 되는 방법임)

값 결과 전달의 기본 아이디어는 함수를 호출할 때와 함수로부터 리턴할 때 다음과 같이 두 번 매개변수 전달을 하는 것이다. 첫 번째 전달이 값 전달(pass by value)이고 두 번째 전달 이 결과 전달(pass by result) 이다.

void swap(int x, int y)
{ //본체
	int temp=x;
    x=y;
    y=temp;
}

int a=10, b=20;
swap(a,b);

 

이름 전달(pass by name)

위에 방법들은 함수를 호출할 때 값이든 참조든 무엇인가 전달하였으나 이름 전달은 함수를 호출할 때는 아무것도 저달하지 않는다.

예제10

int i;
int a[10];
void p(int x)   //p(a[1]) (x=a[1]) 입력
{
	i=i+1;  //x=a[1]에서 x=a[2]로 바뀜
    x=x+10;//a[2]=a[2]+10; 바뀜 따라서 a[2]=30
}

main()
{
	i=1;
    a[1]=10;
    a[2]=20;
    p(a[i]);
}

 

 

잘 쓰이지 않는 이유는 상호교환을 하지 못 하기 때문이다.

ex) swap(i,a[i]);

 

사례 연구

Ada 

-매개변수 전달 방법으로 값 전달과 값-결과 전달을 제공한다. 값 전달을 하기 위해서는 다음과 같이 매개변수 앞에 in으로 선언하며 이러한 매개변수 전달을 copy-in이라고 한다. 값-결과 전달을 하기 위해서는 매개변수 앞에 in out으로 선언하며 이러한 매개변수 전달을 copy-in/copy-out이라고 한다 

 

C

매개변수 전달 방법으로 값 전달만 제공, 자료형으로 제공하는 포인터를 이용하여 참조 전달의 효과를 낼 수 있음

 

C++, Pascal, Modula-2

매개변수 전달 방법으로 값 전달과 참조 전달 방법을 제공한다. 참조 전달을 사용하려면 매개변수 앖에 C++의 경우에는 &로 표시하고 Pascal의 경우에는 var로 선언하면 된다.

 

Java

Java언어도 매개변수 전달 방법으로 값 전달과 참조 전달 방법을 제공한다 .

참조 전달 방법을 제공한다기 보다는 객체에 대한 변수는 모두 참조 변수이므로 객체를 전달하면 자연스럽게 객체에 대한 참조가 전달되게 된다.

 

FORTRAN

매개변수 방법으로 참조 전달만을 제공

 

8-3)함수와 바인딩

정적 유효범위(static scope)규칙

-선언된 이름은 선언된 블록 내에서만 유효하다

-대부분 언어에서 표준 규칙으로 사용되고 있다.

 

동적 유효범위(dynamic scope)규칙

-선언된 이름은 선언된 블록의 실행이 끝날  때까지 유효하다.

-실행 경로에 따라 유효범위가 달라질 수 있다.

 

예제12
int x = 0; //변수 x가 전역 변수로 선언됨
fun void g(int y)
	x = x + y * y; //정적 유효범위 규칙을 생각하면 x=25로 변경되고 동적 유효범위 규칙을 생각하면 x=35로 변경된다.             
let int x = 10; in //변수 x가 let문 내에 지역 변수로 선언
	g(5);
end;

 

예제13
int x = 0;
fun void g(int y)
	x = x + y * y;
fun void f(int z)
	let int x = 10; in
    	g(z);
     end;
f(5);

정적 유효 범위를 지키면 x=25로 변경된다.

동적 유효 범위를 지키면 x=35로 변경된다.

 

바인딩과 심볼 테이블

바인딩(binding)은 이름을 어떤 속성과 연관(Association) 짓는 것을 말하며, 보통 변수, 상수, 함수 등의 이름(식별자)을 속성과 연관 짓는 것을 말한다.

 

이러한 바인딩은 바인딩이 이루어지는 시간에 따라 정적 바인딩(static binding)과 동적 바인딩(dynamic binding)으로 구분 가능하다.

 

정적 바인딩

컴파일 시에 한번 바인딩이 이루어지고 실행 동안 변하지 않고 유지된다. 정적 바인딩되는 속성은 정적 속성이라고 한다.

 

동적 바인딩

실행 중에 이루어지는 바인딩으로 실행 중간에도 속성이 변경될 수 있다. 동적 바인딩되는 속성은 동적 속성이라고 한다.

 

속정 정보를 유지 관리할 때 는 변수 이름뿐만 아니라 함수 이름의 속성 정보도 유지 관리해야 한다. 보통 식별자에 대한 속성 정보는 심볼 테이블(symbol table)에 유지 관리한다. 심볼 테이블은 유효한 속성 정보를 유지 관리하기 위한 자료구조라고 할 수 있다.

예제14
int y;
fun int square(int x) return x * x;
y = square(5);
print y;
식별자 타입 유효범위
y int 전역  25
square 함수 int->int 전역 square의 AST
x int 지역(square) 5

 

 

8-4)함수의 타입 검사

함수 정의를 위한 타입 규칙

1. 함수는 함수 헤더에 선언된 것처럼 정의되어야 한다.

 

재귀 함수 정의의 타입 규칙 

재귀 함수의 경우에는 함수 정의의 내부에서는 매개변수 id 뿐만 아니라 함수 이름 f도 유효해지므로 다음과 같이 이들에 대한 타입 정보를 타입 환경에 추가한 후에 함수 본체 S에 대한 타입을 검사해야 한다.

예제 17
fun int fact(int n)
    if(n==1) then return 1;
    else return n * fact(n-1);

 

함수 호출의 타입 규칙

1, 호출된 함수가 유효한 함수 이름이어야 한다.

2, 함수 호출에서 인자는 선언된 함수의 매개변수 타입과 같아야 한다.

3. 함수 호출의 결과 값의 타입은 함수의 리턴 타입이 된다.

예제16
fun int square(int x) return x * x;
int y;
y= square(5);

 

8-5)함수 구현

함수정의

함수는 리턴 타입, 함수 이름, 매개변수 선언, 실행 문장으로 구성되므로 함수의 AST는 다음과 같이 정의 가능하다.

이 AST는 다음과 같이 클래스 Function으로 구현 가능

class Function extends Command{
	#Identifier id;
    #Decls params;
    #Stmt stmt;
    
    Fuction(String s, Type t){
		id = new Identifier(s);
        type = t;
        params = null;
        stmt = null;
    }
}

 

함수호출

함수 호출의 구문법은 다음과 같으며 구문법에 따라 다음과 같이 파싱 가능

#id(<expr> {, <expr>}); //함수 호출

private Call call(Identifier id) {
	match(Token./*LPAREN*/);
    Call c = new /*Call*/(id, /*arguments()*/);
    match(Token./*RPAREN*/);
    match(Token./*SEMICOLON*/);
    return c;
 }

 

리턴문

funId는 리턴문을 포함하는 함수 정의를 파싱할 때 저장해둔 함수 이름이다.

return <expr>;

private Return returnStatement() {
	match(Token./*RETURN*/);
    Expr e = expr();
    match(Token./*SEMICOLON*/);
    return new /*Return(funId, e)*/;
}

 

 

 

함수 정의의 타입 검사

함수 헤더에 선언된 것처럼 매개변수 id가 t1 타입일 때 함수 본체 S가 t2타입이면 이 함수는 선언된 것처럼 t1 타입의 매개변수를 받아 t2 타입의 값을 반환한다. 따라서 이 함수의 타입은 선언된 것처럼 t1->t2가 된다.

 

함수에 대한 타입 검사 구현은 먼저 함수의 선언된 타입(ProtoType) 및 매개변수들(f.params)의 타입을 타입 환경에 추가한다(2~4번줄). 이후 5번 줄에서 함수의 본체(f.stmt)의 타입을 검사(Check)하고 6번 줄에서 이 타입이 선언된 리턴 타입과 일치하는지 확인한다. 그리고 8~10번 줄에서 매개변수와 함수의 타입 정보를 제거한다.(te.pop()).

마지막으로 정의 된 함수에 대한 확인된 새로운 타입 정보(ProtoType)를 타입 환경에 추가한다.

 

함수 호출의 타입 검사

함수 호출의 타입 규칙은 함수 이름 f가 타입 환경에 있는 유효한 함수 이름인지 먼저 검사하고 그 타입 정보(t->t')를 이용하여 함수 호출에서 사용되는 인자의 타입과 대응되는 매개변수 타입이 같은지 검사하며 이 조건이 만족되면 함수 호출의 결과는 함수의 리턴 타입인 t' 타입이 된다.

이는 다음과 같이 구현할 수 있다. 먼저 2〜4번 줄에서 호출된 함수의 이름(c.fid)을 이용하 여 타입 환경에서 해당 함수가 정의되어 있는지 확인하고 7번 줄에서 함수의 타입 정보를 가 져온다(te.get(c.fid)). 그리고 12번 줄에서 인자들(c.args)의 타입을 하나씩 검사(Check) 하고 13〜14번 줄에서 해당 함수의 대응되는 매개변수 타입과 일치한지 확인한다. 일치하면 이 함수 호출의 결과 타입은 해당 함수의 리턴 타입(p. result)이 된다(8, 19번 줄)

static Type Check(Call c, TypeEnv te){
	if(te.contains(c.fid)){//2~4번 줄에서 호출된 함수의 이름(c.fid)을 이용하여 타입 환경에서 해당 함수가 정의되어 있는지 확인하고 
		error(c,"undefined function: " + c.fid);
        	return c.type;
        }
        Exprs args = c.args;
        ProtoType p = (ProtoType)te.get(c.fid);//7번줄에서 함수의 타입 정보를 가져온다. (te.get(c.fid))
        c.type = p.result;
        if(args.size() == p.params.size()) {
 			for (int i=0; i<args.size(); i++){//인수와 매개변수 비교
            	Expr e = (Expr) args.get(i);
                Type t1 = Check(e,te);//12번 줄에서 인자들(c.args) 타입을 하나씩 검사(Check)하고 
                Type t2 = ((Decl) p.params.get(i)).type;//13~14번 줄에서 해당 함수의 대응되는 매개변수 타입과 일치한지 확인한다
            	if(t1 != t2)   // 이 함수 호출의 결과 타입은 해당 함수의 리턴 타입(p.result)이 된다. (8,19번 줄).
                	error(c, "argument type does not match parameter");
            }
       }else
       		error(c,"do not match numbers of arguments and params");
       return c.type;
 }