09. Subroutines and Control Abstraction

Jimin Lim·2021년 12월 5일
0

Programming Language

목록 보기
8/9
post-thumbnail

Control Abstraction

  • Data abstraction: 목적 - 정보 전달
  • Control Abstraction: 목적 - 잘 정의된 연산 실행
  • Subroutines: 대부분의 프로그래밍 언어에서 제어 추상화를 위한 주요 메커니즘이다.
  • Most subroutines are parameterized: 인자 값에 따라 결과가 달라진다.
  • Arguments: actual parameters(실질적인), formal parameters(형식적인)
  • Procedure: 값 리턴하지 않는 함수
  • Function: 값 리턴하는 함수
  • 대부분 언어는 함수 호출 전, 정의가 되어있어야 한다.

9.2.4 In-Line Expansion

: 함수의 바디에 있는 코드를 복사해둔다.
: 함수의 기능인 파라미터 체크, 컴파일할 때의 기능등은 유지하면서 최적화하는 작업이다.

  • 왜 사용하는데?
    • activate record를 stack에 공간 할당한다는 것은 생각보다 시간이 많이 걸려서
    • call and return으로 옮겨다니는 과정(branch)이 시간이 오래걸릴 수 있다.
    • 어떤 함수 실행 중, 이전 함수에 대한 연결을 위해 chain을 사용하는데 관리하는 것이 시간이 많이 걸리는 작업이다.
    • 함수를 호출한다면 다른 코드를 수행하는데, 레지스터의 내용을 다른 곳에 저장을 또 해야한다.
  • 모든 함수가 inline으로 실행되는 것은 아니다.
    • C++ 의 경우 inline 키워드를 붙여야 실행한다.
    • In Ada: pragma
    • 컴파일러가 판단 후 inline으로 만들어준다.
    • (참고로 반복문이 포함되어있으면 inline으로 만들지 않는다.)
  • macro와 비슷하지만, macro는 타입 체크를 하지 않지만 inline은 타입 체크를 해준다.
  • real function보다 실행속도는 빨라질 수 있지만, 코드 크기가 커지고 recursive인 경우 복잡해진다.

9.3 Parameter Passing

: 대부분 함수는 매개변수가 존재한다.
: 어떤식으로 진행하는지 방향 결정, 필요한 데이터를 전달할 때 사용

  • formal parameters: 함수에 선언되는 파라미터
  • actual parameters: 실제로 전달되는 인자 (값으로 이해하면 된다.)

9.3.1 Parameter Modes

  • C, Fortran, ML, Lisp: parameter passing 에 대해 한 가지 규칙을 제공한다.
    • C 언어는 call by value 방식만을 사용한다. (주소 전달 시, 주소 표현식을 넘긴다.)
  • Pascal, Modula, and Ada: 두 개, 그 이상 제공

p(x); 구현 관점에서 두 가지로 전달할 수 있다.
1. x의 값을 copy 해서 전달하기. (= call by value, 값 복사이므로 별개 변수)
2. x의 주소를 전달한다. (= call by reference, 변수는 새로운 alias )

 x : integer
   procedure foo(y : integer)
   		y := 3
    	print x
   ...
   x:= 2
   foo(x); 
   print x
  • By value: 2, 2
  • By reference: 3, 3
  • By-value/result: 2, 3 (리턴할 때 y의 값을 x로 값을 복사해준다.)

call-by-value/result

: 서브루틴 실행되기 직전에 call by value처럼 actual parameter를 복사하고, 서브루틴이 리턴할 때 값을 변경해서 리턴해준다.

In Pascal

  • pass by value
  • keywor var 를 붙인다면 call by reference

In c

  • 항상 pass by value
  • Array인 경우, pass by pointer
  • caller의 스코프에 있는 변수를 수정하고 싶을 때, 포인터를 넘겨줘야한다.

Fortran

  • pass by reference
  • 모든 actual 파라미터가 l-value 일 필요가 없다.
  • 컴파일러가 임시 변수를 복사한다
  • formal parameter가 변경되어야 하는 경우, 로컬에 카피하고 그것을 제공해야한다.
    f(a,b)인 경우 a를 수정해야한다면 c=a 저장해둔 후, c를 이용해서 수정해야한다.

Call by Sharing

: variable 자체가 reference인 언어에서는 도움이 되지 않아서 이용하는 방식(Smalltalk, Lisp, ML, or Ruby)
: alias 개념이 아닌, 같은 오브젝트를 공유한다는 개념이다.
: call by value, call by reference과는 다르다.

  1. call by value: 값을 전달하면 복사해서 지역 변수로 전달
    • but) formal parameter가 참조하는 객체를 수정하면 서브루틴이 반환된 후 프로그램은 actual parameter를 통해 이러한 변경 사항을 확인할 수 있다.
  2. call by reference: 객체의 값을 수정할 수 있다.
    • but) a, x가 2라는 오브젝트를 가리키는 경우일 때, call by reference는 x가 다른 오브젝트를 가리킬 수 있지만, call by sharing에서는 불가능하다.
  1. 값을 전달하면 값을 지역 변수로만 사용 -> 전역에 있는 값은 변경 되지 않음 (Value)
  2. 객체의 인자를 던지면 그 객체가 가지고 있는 속성의 값을 변경은 가능 (Reference)
  3. 함수 내부로 할당 받은 객체에 새로운 메모리 값으로 할당은 불가능
    참고

In Java

  • primitive types은 passed by value이다.
  • Object type은 call by sharing이다.
    • pass by value로 이해할 수 있다.
//pass by value
 void f(A a){ //a는 b의 주소를 복사
 	a = new A(); //새로운 주소 할당
 }
 
 void main(){
 	A b = new A(); //b에 A를 가리키는 주소값
 	f(b);
 }
//pass by sharing
class Temp2 {
  Object o2 = new Integer(5);
  
  void f(Object o) { 
      System.out.println("1: o2 = " + o2); //5
      o = new String("temp"); 
      System.out.println("o = " + o); //temp
      System.out.println("2: o2 = " + o2); //5
      
   }
   
   void f2() { 
       f(o2); 
    } 
    
    void printO2(int n) {
    	System.out.printf("%d: o2 = %s\n", n, o2); //3, 5 (temp의 경우 f함수에서 o로 새로 할당되지 않는다.)
     }

     public static void main(String[] args) { 
	Temp2 t = new Temp2();
	t.f2();
	t.printO2(3); 
    }
}

In C#

  • pass by value가 default
  • ref, out 으로 call by reference 가능
    • ref: 초기화 필요, out: 초기화 필요 없음

The Purpose of Call-by-Reference

  • 호출되는 함수에서 actual parameter의 값을 전달해야한다 -> pass by reference
  • 호출하는 곳에서 값을 변경하지 못하도록 하고 싶을 때 -> pass by value
    • call by value인 경우 시간이 오래걸릴 수 있다는 단점 존재, reference를 사용하는 경우가 있다.
    • f(&a) void f(b* : type ): c언어처럼 사용한다면 직접 엑세스 하려면 *b를 사용해야한다 (extra level of indirection) -> 자주 사용한다면 비효율적이다.
  • Pascal: var 을 사용해서 call by reference 방법을 사용한다. (arg가 크거나, 수정되어야하는 경우)
  • C: (&) 사용 -> 값이 커서 전달했는데 수정되는 buggy 발생할 수도 있다. 따라서 Read-Only 방식이 존재.

Read-Only Parameters

Modula-3

  • reference 로 제공하되, value 처럼 수정하지 못한다.
  • Small READONLY(작은 메모리 공간): by passing a value
  • Larger READONLY: by passing an address
  • 컴파일러는 임시 저장 변수를 만든 후 값을 가지고 있도록 하고, large read only 파라미터로 넘긴다.

C

  • const를 이용해서 수정되는 것을 막을 수 있다.

읽기 전용일 때, callee 가 formal parameter를 수정하는 것을 허용하는가? 수정한다면 actual parameter에도 반영이 되는가?

Ada
: 3가지 passing 모델을 제공한다.

스칼라, 포인터인 경우, copying values로 구현되어있다.

  • in (실질적 read only): caller에서 callee로 값을 전달하기 위함
    • callee 입장에선 값을 읽기만 가능하다.
    • call by value (actual -> formal 값 복사)
  • out: callee에서 caller로
    • (Ada83) callee에 의해서 written은 가능하지만 read는 불가
    • (Ada95) 둘 다 가능
    • call by result (formal -> actual)
  • int out: 양방향
    • 읽기 쓰기 가능
    • actual parameter에 영향을 미친다.
    • call by value/result

constructed type인 경우, value, reference 두 가지 제공한다.
-> 두 가지 방식을 제공한다면 문제 발생

  • in out 으로 주소를 전달한다면, callee에서 수정한다면 caller의 변수도 수정이 된다.
  • value/result 로 전달한다면, 값을 수정하면 바로 영향을 미치지 않는다.

References in C++

  • C 언어는 reference 방식이 없다.
  • C++ 의 경우, reference 방식을 제공한다.
void swap(int& a, int& b) { 
int t = a; a = b; b = t; } 
  • *로 dereferencing 하는 과정이 필요없다.
  • const를 붙여서 readonly 변수를 사용할 수 잇다.
    • spped, safety 보장
int i;
int &j = i;
  • alias 방식 사용
  • 포인터가 아니다.
  • Uses of references
    • 인자 넘길 때
    • Function return 값을 복사하는 방식을 사용했는데, 이 때 reference를 넘긴다.
    • 포인터를 return하는 것을 가능하다. (*, dereference) 표현을 사용하지 않아도 된다.
 cout << a << b << c; // reference 리턴
 *(*(cout.operator<<(a)).operator<<(b)).operator<<(c); //reference 리턴하지 않는다면

Closures as Parameters

: 클로져는 함수에 대한 참조에 함께 전달되는 referencing environment를 함께 보따리처럼 가져가는 것을 의미한다.

In ada

1. type int_func is access function (n : integer) return integer;
2. type int_array is array (positive range <>) of integer;
3. procedure apply_to_A (f : int_func; A : in out int_array) is
4. begin
5. for i in A'range loop
6. A(i) := f(A(i)); //이때 k값도 같이 가져와야 함
7. end loop;
8. end apply_to_A;
		...
9.  	k : integer := 3; -- in nested scope 
		...
10. 	function add_k (m : integer) return integer is
11. 	begin
12. 		return m + k; //k가 유지되어야 한다.
13. 	end add_k;
		...
14. 	apply_to_A (add_k'access, B);

In C

 void apply_to_A(int (*f)(int), int A[], int A_size) { //함수 참조
	int i;
	for (i = 0; i < A_size; i++) A[i] = f(A[i]); 
 }
  • C/ C++: nested 된 함수가 불가능하고, 예시의 ada처럼 k 변수를 사용한다고 친다면 k는 전역이므로 클로져가 필요가 없다.

9.3.3 Special Purpose Parameters

Default (Optional) Parameters: 동적 스코프의 주요 용도는 서브루틴의 기본 동작을 변경하는 것이며, 이는 기본 파라미터로도 수행될 수도 있다.

int default_width = 16;
int default_base = 2;
void put(int item, int width = default_width, int base = default_base);
  • parameter가 caller에 존재하지 않으면 default 값으로 지정된 값을 사용한다.
  • Ada, C++, C#, Common Lisp, Fortran 90, Python
    • Python - print("gkk", end="") : end가 default value

Named Parameters: 파라미터를 순서대로 지정하지않고, 이름으로 지정한다.

print("abc", 30, sep = ',', end = ' ') #python

Variable Numbers of Arguments: 개수가 정해져 있지 않는 파라미터

  • Lisp, Python, C 지원
  • C#, Java는 typesafe manner로 지원해준다.
//java
static void print_lines(String foo, String ... lines) { 	
	System.out.println("First argument is \"" + foo + "\"."); 
	System.out.println("There are " + lines.length + " additional arguments:");
	for (String str : lines) { // lines는 배열처럼 취급됨
		Sysem.out.println(str); 
        }
}
//c#
 static void print_lines(String foo, params String[] lines) { 
 	Console.WriteLine("First argument is \"" + foo + "\"."); 
    	Console.WriteLine("There are " + lines.Length + " additional arguments:"); 
        foreach (String line in lines) {
		Console.WriteLine(line); 
        }
}

9.3.4 Function Returns

: 함수가 반환할 값을 나타내는 syntax는 매우 다양하다.

  • Lisp, ML, and Algol 68과 같이 expression과 statement를 구별하지 않는 언어는, 함수의 값은 body의 value에 해당하는 값이다.

: 초창기 언어 (Algo60, Fortran, and Pascal)의 함수는 왼쪽이 함수의 이름인 할당 문을 실행하여 반환 값을 지정했다.

  • 함수의 이름이 다른 변수에 의해서 가려질 수도 있기 때문에 문제가 발생하기도 했다.
  • return 이라는 표현이 등장하게 되었다. (함수 종료 + 값 리턴)
func A_max(A []int) (rtn int) { 
	rtn = A[0]
	for i:=1;i<len(A);i++ { 
    	if A[i]>rtn{rtn=A[i]}
	}
	return 
}
  • rtn은 지역변수로 사라지는게 맞지만 위처럼 변수를 저장할 때까지 값을 유지하는 방식도 있다.

: 초창기 언어에서 함수에서 반환할 수 있는 변수의 타입을 제한하기도 했다.

  • Algol 60 and Fortran 77: scalar
  • Pascal: scalar, pointer 반환
  • Algo 68, Ada, C, Fortan 90, imperative 언어는 composite type도 반환 가능
  • ML, 후손 언어는 tuple 도 반환할 수 있다.
def foo(): 
    return 2, 3
... 
i,j=foo()
  • 함수형 언어에서는 함수를 클로져로 리턴하는 것은 흔한일이다.
    • C언어는 클로져 개념은 없지만 서브루틴의 포인터를 반환하는 방식이 있다.
profile
💻 ☕️ 🏝 🍑 🍹 🏊‍♀️

0개의 댓글