09. Subroutines and Control Abstraction
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과는 다르다.
- call by value: 값을 전달하면 복사해서 지역 변수로 전달
- but) formal parameter가 참조하는 객체를 수정하면 서브루틴이 반환된 후 프로그램은 actual parameter를 통해 이러한 변경 사항을 확인할 수 있다.
- call by reference: 객체의 값을 수정할 수 있다.
- but) a, x가 2라는 오브젝트를 가리키는 경우일 때, call by reference는 x가 다른 오브젝트를 가리킬 수 있지만, call by sharing에서는 불가능하다.
- 값을 전달하면 값을 지역 변수로만 사용 -> 전역에 있는 값은 변경 되지 않음 (Value)
- 객체의 인자를 던지면 그 객체가 가지고 있는 속성의 값을 변경은 가능 (Reference)
- 함수 내부로 할당 받은 객체에 새로운 메모리 값으로 할당은 불가능
참고
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
- Python -
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언어는 클로져 개념은 없지만 서브루틴의 포인터를 반환하는 방식이 있다.
Author And Source
이 문제에 관하여(09. Subroutines and Control Abstraction), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@jimin3263/09.-Subroutines-and-Control-Abstraction저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)