메모리와 c++에 관한 얘기

2021. 11. 14. 22:00c,c++ 기본/next step

320x100

안녕하세요 대학생개발자입니다.

이번 글에서는 메모리에 대한 얘기를 하려고합니다.

아참 예전에 메모리가 있지않았나요? 라고 생각하실 분들을 위해서 말씀드리지만

이 메모리는 예전의 메모리에 비해 많이 심도가 깊어졌습니다...

그럼 우선 시작하겠습니다.

 

우선 저번 글에서 좀 적었으면 싶었는데 너무 피곤한 탓에... 빨리 정리를 해서 못 정리한 내용에 대해서 간략하게 정리를 좀 하고 넘어가도록 하겠습니다.

저번 글에서는 file io에대해서 얘기를 해봤는데요 그럼 결국 다른 프로세스에 접근하는것도 file io이지 않겠는가 입니다.

다른 프로세스의 메모리에 접근하는것과 파일에 접근하는것 두개가 다른게 전혀없다는거죠 결국 메모리에 올려서 값을 쓰고 그걸 다시 돌려놓고를 할거기 때문이죠..

windows에서는 WriteProcessMemory라는 함수와 ReadProcessMemory라는 함수를 지원합니다. 다른 프로세스에 있는 값을 얻어내거나 수정하는거죠... 그럼 일단 프로세스를 가져오는 법을 알아야되는데요... 뭐 그것도 생각보다 단순합니다. OpenProcess라는 함수가 있습니다. 다른 인자는 문제가 없는데 ProcessID라는걸 넣어야합니다... 과연 프로세스 아이디라는게 무엇일까 고민을 하게되는데요... 프로세스아이디는 작업관리자에서 쉽게 확인하실 수 있습니다.

작업관리자의 Details... 한글로는 상세정보..? 자세히..? 뭐지 어째뜬 그런게 있을겁니다.

거기에 보시면 PID라는게 있을텐데요

이 값을 요구하는겁니다. 이걸 구하는건 어떻게 해야될지 감이안오구요... 이값을 넣게되면 그에 해당하는 프로세스 핸들을 반환해줍니다. 당연한거지만 우리가 직접 접근은 불가능합니다. 윈도우에서는 커널 접근을 일체 막고있고 접근하려고 하면 터트려 버리니까요...

 

그럼 이제 다른 대상에게 쓸수도있고 거기서 가져올수도있습니다.

이제 중요한건 하나가 남았죠... 그 프로세스의 어디에 쓸것이냐에 대한문제입니다.

즉 그 프로세스의 가상메모리상 특정 지점을 찾아야 한다는겁니다.

이런 객체가 존재합니다.

"MEMORY_BASIC_INFORMATION"

이 객체를 VirtualQueryEx 함수의 인아웃 파라미터로 전달하게 되면 여기에 그 가상 메모리에 접근이 가능한 방법이 주어지게 되는데요... 이걸 그대로 WriteProcessMemory나 ReadProcessMemory와 같은 함수를 써서 접근을 하면 된다는겁니다.

 

음... c언어에서는 솔직히 저는 이정도의 지식만 있어도 충분하다고 판단합니다...

다음부터는 c++에 대해서 정리를 해볼겁니다. 물론 이거보다 더 중요한 c에대한 내용이 있겠지만 저는 이 정도밖에 아는게 없고 필기내용도 사실 여기서 c가끝나버려요 ㅋㅋㅋ... 그러니까... c++로 넘어갈겁니다.

 

그럼 이어서 바로 c++에대한 내용을 쓸겁니다... 아주 기본부터 생각 안해본 부분까지 해볼거기 때문에 초반부분은 스킵하셔도 좋습니다.

음... 그럼 시작하겠습니다!!

게임 서버를 꿈꾸는 사람들은 C++은 거의 필수적이죠 !(거의 대부분 큰 게임회사는 c++을 쓰니까요)

그래서 c++책에 있는 내용들 중에서 스킵이 되는 내용도있고 별로 중요해 보이지 않지만 매우 딥하게 파고드는 부분도 다소 있으니... 필요에 따라 걸러서 보시면 될 것 같습니다.

 

아 그리고 여전히 참고하셔야될게 제가 말씀드리는 정보는 컴파일러에 따라 다소 조금 차이가 있긴 하기때문에

microsoft visual studio 2019 버전을 기준으로 말씀드린다는점 미리 전제로 깔고 가도록 하겠습니다.

 

그럼 시작하겠습니다.

아... 순서는 그냥 윤성우의 열혈 c++ 책에 나오는 순서대로 쭉 적겠습니다.

 

C++

일단 c++을 하기전에 꼭 네트워크 개발을 하실때나 그뿐 아니라 다른 무언갈 개발하신다면 꼭 참고하셨으면 하는게

첫번째로 변수의 재사용은 절대적으로 줄이라는겁니다... 고작 4바이트 인트하나 아끼겠다고 디버깅때 변수를 또쓰고 또쓰고해서 어떤거에대한 리턴값인지도 모른다면... 이건 진짜 낭패입니다... 그냥 4바이트 인트하나 더쓰세요 만약 구조체가 20바이트라도 그냥 쓰세요 20바이트가 중요합니까? 아니면 디버깅이 중요합니까?

 

그리고 디버깅에 있어서 또 중요한게 변수를 스코프안에 넣지마라입니다. 함수의 스코프안에 넣지말라는게 아닙니다. 지역내 지역에 넣지 말라는겁니다. 만약 지역내에서 지역에 또 들어가게된다면... 그 스코프를 벗어나게되면... 당연한거겠지만 조사식에서도 안나오고 메모리 뷰로만 볼 수 있습니다. 물론 메모리뷰만 잠깐 봐서 판단이 될정도면 그렇게 큰 건도 아니었겠지만... 큰건이라면 메모리뷰만 가지고는 절대 해결이 불가능하겠죠... 당연히 모든 변수는 용도에 맞춰서 한번만 쓰시고 스코프안에 넣지마세요.

 

그럼 시작하겠습니다.

 

먼저 함수오버로딩이 나오는군요.

게임 서버 개발자가 되기위해서 네트워크를 공부하고 계신분들이라면 함수오버로딩이 뭔지 모르시는 분은 없을겁니다.

하지만 다시한번 정리를 해본다면

 

함수의 반환타입과 이름이 동일할때 함수의 파라미터 갯수 혹은 파라미터 타입을 가지고 함수를 구분한다 정도입니다. 근데 왜 하필 반환 타입은 되지 않을까에 대해서 고민해 보신적 있을겁니다.

물론 앉아서 1분정도 고민하면 나오는 답이지만... 정리를 하고 간다면

 

우리는 함수 call을 할때

와 같이 call을 하게됩니다. 물론 여기까진 문제없습니다.(컴파일문제요)

하지만 이 경우에는 문제가 생깁니다.

왜 이런 문제가 생기는 걸까요?

해답은 호출부분에 있습니다. 우리는 함수를 호출할 때 반환타입에 대한 명시를 하지 않습니다. 그렇기 때문에 컴파일러 입장에서는 어떤 함수를 호출해야 되는지 알 수 없고 그렇기 때문에 반환타입은 함수 오버로딩의 조건이 되지 못한다.라는 결론이 나오게 되는겁니다.

 

음... 그리고 인라인 함수가 나옵니다. 아마 inline은 예전에 전처리기에 대한 얘기를 하면서 이름정도는 나왔던걸로 기억합니다.

인라인함수가 뭘하는거냐..? 그냥 말그대로 그 코드를 복사붙여넣기 해주는겁니다 물론 컴파일러가요.

아마 대부분 c++을 처음 공부하시는 분들은 Debug모드로 빌드를 하실겁니다... 이경우에는 최대 최적화기능이 꺼지게되는데요... 물론 + 로 증분링크... 그리고 잘못된 포인터 접근을 막기위해서 어마어마한 스택을 할당해버리는 점도있습니다. 다시 본론으로 돌아와서 인라인함수는 컴파일러에게 부탁하는겁니다. 하지만 하지 말아달라는건 강제할수있습니다 바로 디버깅 모드로 빌드를 함으로써 입니다. 디버깅 모드의 목적은 당연한거지만 문제가 있을때 코드를 한줄한줄 보면서 넘어갈 수있게 하기위함입니다. 이상황에서 최대최적화가 되어서... 인라인화가 되어버린다면 문제가 되겠죠 당연히...

 

뭐 그리고 방금 말씀드렸지만 인라인은 강제로 할순 없습니다. inline을 적어도 컴파일러가 봤을때 이건 콜하는게 더 낫다 라고 판단한다면 그냥 call인겁니다. 반대로 inline을 안적어도 컴파일러가 이건 그냥 복붙해도 되겠다 싶은건 그냥 복붙해버립니다. 물론 디버그빌드에서는 적용되지 않습니다.

 

그리고 인라인함수는 선언과 정의가 떨어지게되면 자동으로 인라인화가 되지 않습니다. 왜냐면요... 컴파일타임에 함수의 정의와 선언이 있어야 복붙이 가능하지 않겠습니까..? 뭐... 너무 당연한 얘기기 때문에 따로 더 말씀 안드리겠습니다.

 

물론 내가 inline이라는 키워드를 붙인경우에만 inline을 시키는 기능도 프로젝트설정 -> c/c++ -> 최적화 에 들어가시면 하실수있습니다.

 

다음으로 네임스페이스가 나오는군요

네임스페이스는 솔직히 처음 배우는 입장이거나 아니면 혼자서만 개발을 해오신 분이라면 잘 필요성을 못느끼실수도 있습니다. 하지만 다른사람들과 같이 개발할때 함수이름이 겹치거나 하게되면... 당연히 네임스페이스가 들어가는게 좋겠죠...

 

그리고 다음으로 레프런스 변수가 나옵니다.

아마 레프런스 변수또한 예전 글에서 길게 나열해 놓은게 있을겁니다.

 

아마 c++을 처음 배우신분들은 이렇게 배우셨을겁니다. 레프런스 변수의 경우에는 공간할당을 하지 않는다라고 말이죠..

이런 말도안되는 개소리를 믿으실건가요?

 

C언어에서 값을 변경하는 방법은 2가지 밖에없습니다. 실제 그변수의 이름을 이용해서 접근하거나 포인터를 이용하거나 두가지입니다. c++에서는 갑자기 그 두개를 빼고도 다른 경우가 생긴걸까요?

음... 그렇게 생각하셨다면 다시한번 책을 펼쳐셔 보시기 바랍니다.

당연히 레프런스 변수또한 공간을 먹습니다.

할당받구요 포인터크기로요 그리고 어셈블리를 보시면 포인터와 동작이 매우 똑같습니다. 당황스러우리만큼 똑같아요.

레프런스 변수라는건 그냥 컴파일러 선에서 포인터를 좀더 편하게 쓸 수있게 일반 변수의 모양으로 만들어주는거지...

절대로 그 이상도 그 이하도 아닙니다.

물론 컴파일러에 따라서 같은 지역에 있는 변수의 경우에는 그냥 바로 변수이름을 박아버리는 경우도 있습니다.

하지만 vs19기준으로는 그냥 그것또한 공간을 할당해버립니다.

그게 획일화 된거니까요... 어째뜬 레프런스를 쓰는이유는 같은 지역에서 쓰려고 만든거이진 않을거아니에요..? 그냥 값을 변경하면 끝인데 뭐하러... 같은 지역에서 레프런스나 포인터를 쓰겠어요...

흠... 그렇습니다 레프런스 변수란건 c++의 입장에서는 그 변수의 별명을 붙이는거라고 했습니다.

 

맞습니다 하지만 언제까지나 이건 언어에서 그런것 처럼 인터페이싱을 해주는것일 뿐입니다.

아마 다들 실험해보셨을거에요 

이것또한... 언어에서 이렇게 맞춰주는거지 실제로 ra는 주소가 존재합니다.

하지만 언어상에서 공간할당을 안하는것 처럼 보여줘야되기 때문에... 이렇게 되었습니다.

 

하지만 실제 메모리와 어셈블리를 본다면...

a에 10을 집어넣고

lea eax [a] a의 주소를 eax에 넣은뒤...

mov [ra], eax

ra라는 공간에 eax를 집어넣어버립니다. eax는 a의 주소였죠? a의 주소를 왜 ra라는 공간에 넣는걸까...

뭐 이유는 단순합니다... 당연히 포인터 연산을 하려고 그런거겠죠 그게아니라면 왜 넣겠어요?

 

네 int* ptr = &ra까지 넣은 구간입니다.

분명 &ra는 0xCFFAA4이지만... 그냥 언어에서 어셈블리를 뽑을때

lea한 주소값이나 mov에서 []연산을 안빼고 넣는게 아니라... 분명 걔가 가지고 있는 값을 전달합니다.

그렇기 때문에 ra의 주소와 a의 주소가 동일하게 나오는 거죠...

 

뭐 정말 믿기 어려우시겠지만 우리는 이때까지 언어에 속아온겁니다.

언어에서 이렇게 뽑아주는거에요... 물론 모든 컴파일러가 지역변수에 대해서는 이렇게 행동하지 않습니다만...

c++ 에서도 이건 Unspecified라고 정의하고있습니다. 그냥 구현자 마음이라는 의미입니다.

만약 주소연산없이 저게 가능하다고 한다면... 뭐 그렇게 만드셔도 좋습니다 하지만 이게 가장 일반적이면서 가상 로우코스트이기 때문에 그런것 같습니다.

 

네... 그럼 포인터는 레프런스냐 라고 묻는 분 계실텐데요... 음... 정확히는 똑같다 라고는 할 수 없습니다... vs의 경우에는 거의 완벽하게 동일하게 작동하지만 아까 말씀드렸듯... c++에서 레프런스의 구현은 Unspecified라고 정의하고 있습니다. 그렇기때문에 똑같다 라고 한다면 답은 아닙니다.

 

추가로 레프런스는 포인터로 구현된다 라고 하시는분들 계신데요... 음... 이거는 어디부터 잘못된건지 모르겠습니다...

레프런스와 포인터가 똑같은 원리를 가지고 만들어진다는거지 포인터를 이용해서 레프런스를 만들진 않습니다.

 

음... 이런느낌이겠네요 소나타와 k5를 비교해봅시다... 둘은 같은 엔진을 쓰고있습니다. 성능도 거의 비슷하죠 하지만 소나타를 가지고 k5를 만들었다고는 안하잖아요??

일단은... 초반부니까 너무 머리쓰지말고 이정도로 넘어가자구요 허허

 

그러면 여기서 슬슬 마감을 해야될 것 같습니다.

오늘도 긴글 읽어주신 여러분들 정말 감사드립니다.

그러면 다음번엔 더 좋은 내용으로 찾아뵙도록 하겠습니다.

감사합니다 안녕히계세요

320x100

'c,c++ 기본 > next step' 카테고리의 다른 글

template와 class에 대한 얘기(1)  (3) 2021.11.23
new와 delete에 대한 얘기  (0) 2021.11.20
DLL,LIB, FILEIO에 대한얘기  (0) 2021.11.13
게임서버 이론1  (0) 2021.11.09
시간과 랜덤에 관한 얘기  (0) 2021.11.08