함수 내 지역변수의 생존 여부에 관해 질문드립니다.
글쓴이: parkyh8618 / 작성시간: 수, 2020/01/01 - 1:51오후
vector<string> convert(string s, int n) { vector<string> v; for(int i = 0 ; i <s.length() ; i+=n) { v.push_back(s.substr(i,n)); } return v; }
에서 vector v를 반환하는데 함수 밖에서 v를 쓸수가 있나요?
함수가 끝나면 v는 함수 내 지역변수니까 함수 밖에선 없어져야 하는 거 아닌가요?
함수 밖에서 쓰려면 vector를 함수 인자로 받아서 써야 될거 같은데 저렇게 써봐도 작동이 되서 질문드립니다!
Forums:
Any problem?
Any problem?
이 맥락에서 지역 변수는 함수를 호출한 쪽으로 그 값이 복사/이동 됩니다.
물론 본격적으로 파고 들면 더 복잡하지요. (Named) return value optimization이라고 찾아 보시면 됩니다.
pass by reference, pass by
pass by reference, pass by value 를 검색해보세요.
https://www.mathwarehouse.com/programming/passing-by-value-vs-by-reference-visual-explanation.php
값이 남는거지 v가 남아있는게 아니죠
값이 남는거지 v가 남아있는게 아니죠
Return Value Optimization, move semantics
https://shaharmike.com/cpp/rvo/
https://stackoverflow.com/questions/3106110/what-is-move-semantics
위의 링크에 잘 설명되어 있습니다.
문제의 핵심은 뭐냐면, 자바나 파이썬과 달리 C++의 user-defined class 타입의 variable은 object는 포인터나 레퍼런스가 아니라는 겁니다.
위의 c++ code는 나이브하게 보면, x만큼, 즉 500메가 만큼 메모리를 할당한 뒤, y만큼, 그러니까 또 다른 500 메가 만큼의 메모리를 추가로 할당해야 합니다.
위의 JAVA 코드는 다릅니다. 어딘가(HEAP)에 MyClass 객체를 딱 하나만 생성하고 x는 그 객체를 가리킬 뿐입니다. y = x 부분은 C++과 달리 그냥 x가 가리키는 객체를 y도 가리키게 할 뿐입니다. 즉 y는 x의 복사본이 아니라 x, y가 같은 객체를 가리키고 있습니다.
C++에서는 이걸 피하려면 포인터 연산을 명시적으로 사용했어야 합니다. 그런데 또 C++은 garbage collector도 없었고 해서 포인터를 사용하면 프로그래머의 생산성이나 프로그램의 유지 보수 비용에는 안 좋은 영향을 줄 수 있습니다.
여전히 나이브하게 생각해 보죠. 본문의 convert 안에서는 일단 v가 생성됩니다. 대충 사이즈가 1M 라고 해보죠. int 변수가 리턴될 때는 그 값을 갖는 임시 int 객체가 만들어집니다. 마찬가지로 나이브하게 보면, v를 카피해서 임시 객체를 만들고, 그걸 리턴합니다. 그게 r에 assign이 되니까 또 카피가 일어납니다.
v가 매우 긴 string을 vector로 바꾼 거니까, 이 경우에 copy는 string에 있던 문자값 전체를 일일이 카피하는 거고 오래 걸리겠죠. 임시 객체를 만들 때 카피 한 번, r에 어사인 할 때 또 한 번 일어나니까 낭비가 심합니다. 더구나 메모리 할당 측면에서도 v, 임시 객체, r 이렇게 vector 사이즈의 세 배의 용량이 필요합니다.
RVO는 이걸 해결하는 최적화의 하나입니다. v와 임시객체를 만드는 대신, 컴파일러가 알아서 convert 코드를 고칩니다. 그래서 메모리는 r만큼만 잡고, convert에는 r의 레퍼런스/포인터를 넘겨줍니다. 리턴을 할 때는 카피하는 대신 아무 것도 안 합니다. 이러면 r만 생성하면 되고, 카피는 따로 일어나지 않죠.
문제는 vector 생성자에 side effect가 있을 수도 있다는 점입니다. RVO 최적화 전에는 생성자가 예컨대 3번 불립니다. RVO 후에는 한 번 불리고요. 그런데 생성자에 사이드 이펙트가 있다면, 뭔가가 달라져서 프로그램 최적화 전후가 다른 결과를 가져오게 됩니다. 그건 컴파일러가 일반적으로는 해서 안 될 일이죠.
하지만 C++ 스탠다드를 만든 사람도 이 중복 카피 등등이 막장인 상황임을 잘 알기 때문에 RVO에 대해서는 사이드 이펙트가 있더라도 최적화를 허용해 줍니다. 즉 언어 표준은 RVO 최적화로 인해 생성자, 소멸자 등에 들어 있는 사이드 이펙트가 프로그램을 다르게 만들어도 괜찮다고 허용해 주는 거죠.
그밖에 variable이 heap object의 reference가 아니라 variable과 같은 scope에 있는 object 그 자체라서 생기는 불필요한 카피를 줄이기 위해 move semantics라는 게 도입되었습니다. 문제는 이 move semantic을 명시적으로 리턴할 때 사용하는 경우인데, 위의 링크 첫 번째를 보시면 그게 왜 비효율적인지 설명되어 있습니다.
댓글 달기