성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Roll A Lisp In C - Reading ; https...
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>프로그래밍에서 borrowing의 개념</h1> <p> 아래와 같은 글에서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 왜 많은 사람들이 Go 언어를 싫어할까? ; <a target='tab' href='http://yisangwook.tumblr.com/post/100383515974/why-everyone-hates-go'>http://yisangwook.tumblr.com/post/100383515974/why-everyone-hates-go</a> </pre> <br /> Go에 없는 것으로 "there's no borrowing" 이라는 말이 나옵니다. 위의 글을 쓴 사람도 그걸 인용하면서 borrowing이 뭔지 궁금해 하는데요. 저도 궁금했습니다. ^^ (처음 봤습니다.)<br /> <br /> 모르는 개념을 하나씩 익히는 것도 좋으니 이참에 한번 찾아봤는데요. 다음의 글이 검색됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Rust 언어 튜토리얼 - 11 빌린 포인터 ; <a target='tab' href='http://sarojaba.github.io/rust-doc-korean/doc/tutorial.html'>http://sarojaba.github.io/rust-doc-korean/doc/tutorial.html</a> </pre> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> "<br /> Rust의 빌린 포인터는 범용의 참조 타입이다. 소유된 박스와는 대조적으로, 소유된 박스의 홀더는 메모리 참조의 소유자이다. 빌린 포인터는 암묵적인 소유권이 절대 아니다. 포인터는 어떤 객체로든 빌려질 수 있고, 컴파일러는 객체의 생명주기보다 오래 살 수 없다는 것을 검증한다.<br /> "<br /> </div><br /> <br /> "<a target='tab' href='http://yisangwook.tumblr.com/post/100383515974/why-everyone-hates-go'>왜 많은 사람들이 Go 언어를 싫어할까?</a>" 글에서 Go 언어를 비판하는 사람들에 "Rust" 언어를 사용하는 층이 있다고 하니, 아마도 Rust 언어의 이 개념을 두고 한 이야기가 맞는 것 같습니다.<br /> <br /> 근데, 솔직히 "<a target='tab' href='http://sarojaba.github.io/rust-doc-korean/doc/tutorial.html'>Rust 언어 튜토리얼 - 11 빌린 포인터</a>"의 설명으로는 무슨 개념인지 감이 안 옵니다. 제가 이해력이 부족한 듯합니다. 그래서 좀 더 검색해 보니 다음의 글이 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Who Needs Garbage Collection? 글의 덧글 "Borrowing, or keeping" ; <a target='tab' href='http://lambda-the-ultimate.org/node/5007'>http://lambda-the-ultimate.org/node/5007</a> </pre> <br /> 덧글의 설명이 제법 충실합니다.<br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> Borrowing, or keeping<br /> <br /> I'd been down this road before, years ago, in the context of trying to make C++ pointer-safe via reference counting without killing performance.<br /> It's possible, but may be too unwieldy. I called Rust's "borrow" concept "keeping". "keep" becomes a qualifier on parameters, like "const". <br /> <br /> More specifically, function parameters which are references or pointers would have four access permissions - read, write, keep, and delete. "Read" and "Write" are implied; "const" turns off write permission. That's standard C/C++. "Keep" permission means that a function can keep a copy of a parameter after the function returns. "delete" permission means the function can delete the object pointed to.<br /> <br /> Lack of "keep" permission has several implications. Any copy of a pointer or reference must be to a scope that will not outlive the source scope. So you can copy a non-keep pointer/reference for use in an inner block, or pass it to another non-keep parameter. Rust does much the same thing.<br /> <br /> In a reference counted system, it is not necessary to update reference counts for non-keep pointers. They must have had a non-zero reference count at scope entrance, and they will have the same reference count at scope exit. So there's a big overhead reduction for non-keep parameters.<br /> <br /> It's possible to infer that a local copy of a pointer is non-keep. Iterators, for example, are almost always non-keep. Recognizing this eliminates most reference counting in inner loops. <br /> <br /> All parameters to the standard C library functions and the Linux API are "non-keep". This is also true of most math libraries. "Non-keep" is the normal case for functions. <br /> <br /> This is thus a way to do reference counting without excessive overhead. Most of the things that are done very frequently are done via non-keep parameters to functions. <br /> <br /> A long, long time ago I tried to talk the people who were designing what would become Java into this. I was not successful. <br /> </div><br /> <br /> 이 글과 함께 다음의 소스 코드가 담긴 설명을 보니 그나마 이해되기 시작합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > A 30 minute introduction to Rust - Ownership ; <a target='tab' href='http://words.steveklabnik.com/a-30-minute-introduction-to-rust'>http://words.steveklabnik.com/a-30-minute-introduction-to-rust</a> </pre> <br /> <hr style='width: 50%' /><br /> <br /> 이쯤에서 제 나름대로 다시 정리해 볼까요? ^^ 다음의 코드를 보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > int GetValue() { return 5; } </pre> <br /> 이를 깊게 들어가 보면 GetValue 함수가 반환될 때 "MOV EAX, 5", "ret" 라는 코드가 실행되는 것을 볼 수 있습니다. 즉, 반환값이 EAX 레지스터에 담기는 것입니다. GetValue 예제처럼 CPU 워드(WORD) 단위의 반환값이라면 상관없지만, 그것이 워드 범위를 넘어가면 문제가 됩니다. 그런 경우에는 CPU 레지스터에 담을 수 없기 때문에 메모리에 값을 보관 후 그 메모리를 가리키는 주소를 EAX에 담아 넘기는 방법이 사용됩니다.<br /> <br /> 일례로 다음과 같은 경우입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #include "stdafx.h" char *GetValues1() { <span style='color: blue; font-weight: bold'> char buf[10] = "test1"; return buf;</span> } int _tmain(int argc, _TCHAR* argv[]) { char *result1 = GetValues1(); printf("%s\n", result1); return 0; } /* 이 코드를 Visual C++ 2013 / Debug 빌드로 했을 때 화면에는 "test1"이 아닌 쓰레기 값이 출력됩니다. (Release 빌드시 최적화로 인해 극적으로 ^^; 정상값이 출력됩니다.) */ </pre> <br /> buf 변수는 10바이트의 영역이 확보되지만 이것은 CPU 레지스터에 담길 수 없는 용량입니다. 그래서 메모리에 상주하게 되고 EAX에는 buf의 메모리 주소가 담겨 반환됩니다.<br /> <br /> 여기서 문제는 그 메모리 주소가 스레드의 스택이라는 점입니다. 스택은 함수가 불릴 때마다 가변적으로 사용되는데, GetValues1 함수가 불렸을 때 C/C++ 컴파일러는 스택에 10바이트 공간을 예약하는 기계어를 출력해서 실행시 스택 공간을 확보하는 작업을 합니다. 하지만, GetValues 함수의 마지막 - "return buf"를 하는 시점에 확보된 10바이트 스택 영역은 다시 차감되고 이후의 메서드 호출에서 그 영역은 덮어 써질 수 있습니다. 쓰레기 값이 출력되는 것은 그 이유입니다.<br /> <br /> C/C++에서는 이런 문제를 해결하기 위해 반드시 동적 할당을 해야 합니다. <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > char *GetValue() { char *pBuf = new pBuf[5]; strcpy_s(pBuf, 5, "test"); return pBuf; } </pre> <br /> 그리고, 이렇게 반환받은 메모리는 반드시 해제해야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > char *pValue = GetValue(); // ... pValue 사용 delete [] pValue; // 반드시 해제 </pre> <br /> 말은 쉽지만, 이 때문에 C/C++ 개발자는 메모리 할당/해제에 따른 적지 않은 고통을 겪게 됩니다. 게다가 모든 코드를 자신이 작성한 경우라면 상관없지만 그렇지 않은 경우는 반드시 매뉴얼을 읽어봐야만, 그것이 반환받는 값을 호출 측에서 해제를 해야 하는지 알 수 있습니다. 예를 들어, 어떤 C/C++ 개발자는 호출자가 해제를 안해도 되게끔 다음과 같이 함수를 작성할 수도 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > char *GetValues2() { <span style='color: blue; font-weight: bold'>static </span>char buf[10] = "test1"; return buf; } </pre> <br /> static이기 때문에(또는 전역 변수를 사용했을 수도 있는!) 이런 경우는 호출 측에서 메모리 해제를 해서는 안됩니다.<br /> <br /> 참고로, MSDN 문서에서 Win32 API 설명 중에 OUT 인자로 명시되는 경우를 볼 수 있는데요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > GetEnvironmentVariable function ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getenvironmentvariablea'>https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getenvironmentvariablea</a> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > DWORD WINAPI GetEnvironmentVariable( _In_opt_ LPCTSTR lpName, <span style='color: blue; font-weight: bold'>_Out_opt_</span> LPTSTR lpBuffer, _In_ DWORD nSize ); </pre> <br /> 마이크로소프트의 경우, 이런 OUT 인자는 호출 측에서 반드시 메모리를 확보해서 전달하는 식으로 처리하고 있습니다. 즉, 다음과 같이 사용하라는 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > wchar_t buf[4096]; GetEnvironmentVariable(L"VAR", buf, 4096); </pre> <br /> 따라서 C/C++ 언어에서 포인터 변수가 다뤄질 때는, 반드시 그 할당의 주체를 확인해서 사용해야만 안전을 보장할 수 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이런 문제를 Rust 언어에서는 borrow 개념을 통해 해결하고 있습니다. "<a target='tab' href='http://words.steveklabnik.com/a-30-minute-introduction-to-rust'>A 30 minute introduction to Rust - Ownership</a>" 글의 예제를,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > fn dangling() -> &int { let i = 1234; return &i; } fn add_one() -> int { let num = dangling(); return *num + 1; } </pre> <br /> C/C++ 코드로 바꿔 보면 이럴 텐데요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > int *dangling() { int i = 1234; return &i; } int add_one() { int *num = dangling(); return *num + 1; } </pre> <br /> 스택에 있는 값을 반환한다는 의미에서 이 코드는 위험한데도 불구하고 C/C++은 정상적으로 컴파일하는 반면, Rust 언어에서는 이를 감지하고 다음과 같은 컴파일 오류를 낸다고 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > temp.rs:3:11: 3:13 error: <span style='color: blue; font-weight: bold'>borrowed value does not live long enough</span> temp.rs:3 return &i; temp.rs:1:22: 4:1 note: borrowed pointer must be valid for the anonymous lifetime #1 defined on the block at 1:22... temp.rs:1 fn dangling() -> &int { temp.rs:2 let i = 1234; temp.rs:3 return &i; temp.rs:4 } temp.rs:1:22: 4:1 note: ...but borrowed value is only valid for the block at 1:22 temp.rs:1 fn dangling() -> &int { temp.rs:2 let i = 1234; temp.rs:3 return &i; temp.rs:4 } error: aborting due to previous error </pre> <br /> 그리고, 이 컴파일 오류를 접한 Rust 개발자는 (제가 몰랐던 바로 그 "borrowing"이라고 알려진) "빌린 포인터(borrowed pointer)" 구문을 이용해 다음과 같이 해결할 수 있다는 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > fn dangling() -> <span style='color: blue; font-weight: bold'>~int</span> { let i = <span style='color: blue; font-weight: bold'>~1234</span>; return i; } fn add_one() -> int { let num = dangling(); return *num + 1; } </pre> <br /> 이렇게 "~" 연산자를 이용해 "unique pointer"를 사용하면 Rust 컴파일러는 해당 변수 i의 값을 스택에 할당하지 않고 그것의 사용 해제 시점을 계산해 자동으로 할당/해제하는 코드를 (개발자 대신) 넣어주는 것입니다.<br /> <br /> "borrowing"이란 것이 어느 상황을 가리키는 용어인지 이제 이해하시겠죠? ^^<br /> <br /> 여기서 다시 "<a target='tab' href='http://lambda-the-ultimate.org/node/5007'>Who Needs Garbage Collection? 글의 덧글 "Borrowing, or keeping"</a>" 글의 내용을 보면 이런 문구가 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > I called Rust's "borrow" concept "keeping". </pre> <br /> '빌려온다'라는 것보다는 '유지한다'는 것이 훨씬 좋은 설명이라는 것에 공감합니다. Rust의 "빌린 포인터(borrowed pointer)"는 컴파일러가 자동으로 해당 변수를 필요한 시점까지 유지해 주는 기능이라고 해석되는 것이 더 자연스럽습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이 개념이 GC가 도입된 언어를 사용하는 개발자에게는 낯설을 수밖에 없습니다. 예를 들어, (자바도 마찬가지이고) C#으로 다음의 코드를 만들면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > char[] GetValues() { char[] buf = { 't', 'e', 's', 't' }; return buf; } </pre> <br /> buf 인스턴스는 GC 힙에 할당되고, 이후 GC에 의해 관리되어 사용되지 않는 시점에 다음번 가비지 컬렉션 수집에서 자동으로 해제되기 때문입니다. C/C++과 같은 해제의 부담이 없기 때문에 애당초 "borrowing" 개념이 필요없는 것입니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1440
(왼쪽의 숫자를 입력해야 합니다.)