컴퓨터사이언스 부트캠프 with 파이썬 책을 공부하며 정리한 포스팅입니다.
개인 공부후 정리용으로 남기는 포스팅으로 내용상에 오류가 있을수 있습니다.
예제 코드는 Computer-Science 를 통해 실습하고 있습니다.
핵심 개념
- call by value(값에 의한 전달)
- call by reference(참조에 의한 전달)
- call by assignment(객체 참조에 의한 전달)
call-by-value(값에 의한 전달)
- 인자를 전달할 때 값을 복사해 전달하는 경우를 뜻함.
#include <iostream>
using namespace std;
void change_value(int x, int value)
{
x = value;
cout << "x:" << x << "in change_value" << endl;
}
int main(void)
{
// 지역 변수 x에 10 대입
int x = 10;
change_value(x, 20);
cout << "x:" << x <<" in main" << endl;
return 0;
}
// 실행결과
x : 20 in change_value
x : 10 in main
-
main()함수에서 change_value()함수를 호출하면서 value인자로 20을 전달했지만
지역 변수 x값은 함수안에서만 변경되고 호출한 쪽에서는 변경이 안됨.
-
그 이유를 알기 위해선 자료구조-스택 에서 스택 프레임에 대한 개념을 알아야 함.
-
스택 프레임(Stack Frame): 함수 호출시 할당되는 메모리 블록(지역변수가 존재하는 영역)
- 데이터 저장시 순차적으로 저장하고 꺼낼때는 맨 위부터 차례대로 꺼냄.
- 접시를 밑에서 하나씩 쌓고 위에서 하나씩 꺼내는 걸 생각하면 이해하기 편하다.
-
위의 함수 동작을 스택프레임 측면에서 표현하면 아래와 같이 표현할수 있다.
1. x = value; 가 실행되기 직전
change_value() 스택 프레임 |
---|
x = 10 |
value = 20 |
main() 스택 프레임 |
---|
x = 10 |
2. x = value; 가 실행된 시점
change_value() 스택 프레임 |
---|
x = 20 |
value = 20 |
main() 스택 프레임 |
---|
x = 10 |
3. change_value() 함수 호출 완료 후 change_value() 스택 프레임 사라짐.
main() 스택 프레임 |
---|
x = 10 |
- 함수 호출로 x 값을 바꾸기 위해서는 call-by-reference로 인자를 전달하면 됨.
call-by-reference(참조에 의한 전달)
- 인자를 전달할 때 참조를 전달하는 방식.
#include <iostream>
using namespace std;
void change_value(int *x, int value)
{
*x = value;
cout <<"x : " << *x << " in change_value" << endl;
}
int main(void)
{
int x = 10;
change_value(&x, 20);
cout << "x : " << x << " in main" << endl;
return 0;
}
// 실행결과
x : 20 in change_value
x : 20 in main
- call by value코드와 비교했을 때의 차이점.
- *연산자 추가 : int x -> int *x , x = value; -> *x = value;
- &연산자 추가 : change_value(x, 20) -> change_value(&x, 20)
- 스택 프레임 측면에서 본 코드
1. change_value()를 실행하고 *x = value; 가 실행되기 직전
change_value() 스택 프레임 |
---|
x = 0x1111 1111 |
value = 20 |
main() 스택 프레임 |
---|
x = 10 <- address: 0x1111 1111 |
- &x는 main() 스택 프레임의 x가 위치한 메모리 공간의 첫 번째 바이트 주소 값 전달.
- *x는 포인터 변수
- 포인터 변수는 메모리 주소 데이터를 저장.
- change_value() 안의 x가 main()의 x의 메모리주소 값을 가리킨다는 의미.
- 가리킨다는 말 = 참조(reference)
2. *x=value; 를 실행한 후
change_value() 스택 프레임 |
---|
x = 0x1111 1111 |
value = 20 |
main() 스택 프레임 |
---|
x = 20 <- address: 0x1111 1111 |
- *x를 역참조(dereference)라고 하며 x에 저장된 주소 값(0x1111 1111)으로 접근.
- value를 대입하여 x 값을 20으로 변경
3. change_value() 함수 호출 완료 후 change_value() 스택 프레임 사라짐.
main() 스택 프레임 |
---|
x = 20 |
call by assignment(객체 참조에 의한 전달)
- 파이썬의 함수 호출 방식
- 파이썬에서는 모든 것이 객체이고, 객체에는 2가지 종류가 있음
- immutable object(변경 불가능 객체) - int, float, str, tuple
- mutable object(변경 가능 객체) - list, dict, set
변경 불가능 객체를 전달할 때
def change_value(x, value):
x = value
print("x : {} in change_value".format(x))
if __name__ == "__main__":
x = 10
change_value(x, 20)
print("x : {} in main".format(x))
# 실행결과
x : 20 in change_value
x : 10 in main
- 스택 프레임 측면에서 본 코드
1. x = value를 실행하기 전
change_value() 스택 프레임 | 메모리 |
---|---|
x | 10 |
value | 20 |
main() 스택 프레임 | |
x |
-
파이썬은 C 언어처럼 변수라는 메모리 공간에 값을 직접 저장하지 않는다
-
값을 가리킴(reference)
- main(), change_value()의 x는 메모리의 10을 가리키고
- change_value()의 value는 메모리의 20을 가리킴
2. x = value를 실행한 후
-
change_value()의 x가 메모리의 20을 가리킴
-
변수 이름이 가리키는 메모리 공간의 값을 직접 바꾸는게 아니라
바꾸고자 하는 상수 객체를 참조 하는것.
3. change_value() 함수 호출 완료 후
- change_value() 스택 프레임과 메모리에 있던 20이 사라짐.
- change_value()안의 변수 x, value가 20을 가리키고 있었으나
- 호출과 함께 사라지면서 메모리에 있던 20도 레퍼런스 카운트가 0이 되면서 사라짐.
변경 가능 객체를 전달할 때
참조한 리스트에 접근해 변경을 시도
def func(li):
# 리스트 li의 0번 인덱스 값을 변경
li[0] = 'I am your father!'
if __name__ == "__main__":
li = [1, 2, 3, 4]
func(li)
print(li)
# 실행결과
['I am your father!', 2, 3, 4]
- 스택프레임 측면에서 보면
- main()의 스택 프레임과 func() 스택 프레임이 li = [1,2,3,4]를 같이 참조.
- 따라서 li의 0번째 값이 1 -> ‘I am your father!’로 바뀜.
- 값을 변경하기 위해 값 객체만 새로운 공간에 만들어 참조하면 됨.
다른 리스트를 메모리 공간에 새로 만든 다음 이를 참조해 리스트를 변경
def func(li):
li = ['I am your father', 2, 3, 4]
if __name__ == "__main__":
li = [1, 2, 3, 4]
func(li)
print(li)
# 실행결과
[1, 2, 3, 4]
- main()스택 프레임은 li = [1, 2, 3, 4]를 참조
- func()스택 프레임은 li = [‘I am your father’, 2, 3, 4]를 참조
- func()함수 호출이 끝나면 스택 프레임이 사라지면서 참조하던 리스트가 사라짐
- 따라서 [1, 2, 3, 4]가 출력 됨.
call by assignment(객체 참조에 의한 전달 방식)를 최종적으로 정리하면 다음과 같다.
-
함수 인자로 변경 불가능 객체를 전달해 값을 변경할 수 없음.
-
함수 안에서 새 객체를 만들어 참조하여 값을 바꾸려 하면
함수 호출이 끝나고 스택 프레임이 사라지면서 참조도 사라지기 때문
-
-
함수 내부에서 객체를 새롭게 할당해야한 값을 변경할수 있는 객체
-
변경 불가능 객체인 상수, 문자열, 튜플 뿐.
-
리스트나 딕셔너리 같은 변경 가능 객체도 함수안에서 새로운 객체를 만들면
함수 호출이 끝나면서 객체는 사라짐
-
그러므로 인자로 전달된 객체에 접근하여 변경해야만 함수를 호출한 쪽의
객체를 변경할 수 있음.
-
Comments