본문 바로가기
[ C언어 ]/- C언어 문법공부

C언어 17장_2(포인터, 메모리, 메모리할당, 정적변수, 리틀엔디안, malloc, memcpy, memcmp, memset, strstr)

by MRG 2020. 5. 13.
728x90
반응형
728x90

▣ 안녕하세요^^
저번장에 포인터 숙제 열심히 해보셨나요??
꼼꼼하게 읽으면서 따라오셨다면 충분히 하셨을 거라 생각합니다.
그럼 오늘은 포인터 두번째로 공부해보겠습니다.



먼저 저번장에서 이야기했던 부분을 한번 보충해서 설명해보겠습니다.

 

▣ 저번장에 이 코드를 기억하시나요?
제가 *pList안에 number에 주소를 넣고 우리가 디버 그해서 pList메모리 주소에 있는 값을 확인했습니다.
그때 제가 주소값이 거꾸로 들어간 것에 대해 다시 설명해드린다고 했습니다.
이 부분을 설명해보겠습니다.



▣엔디언 이라는 말이 있습니다.
위키백과에서는 엔디언(Endianness)은 컴퓨터의 메모리와 같은 1차원의 공간에 여러 개의 연속된 대상을 배열하는 방법을 뜻하며, 바이트를 배열하는 방법을 특히 바이트 순서(Byte order)라 한다.
엔디언은 보통 큰 단위가 앞에 나오는 빅 엔디언(Big-endian)과 작은 단위가 앞에 나오는 리틀 엔디언(Little-endian)으로 나눌 수 있으며, 두 경우에 속하지 않거나 둘을 모두 지원하는 것을 미들 엔디언(Middle-endian)이라 부르기도 한다.
라고 이야합니다. 
쉽게 이야기하면
바이트 배열방법(바이트순서대로)
메모리 배열 방법
1차원 공간에 여러 개의 연속된 대상을 배열하는 방법
이라고 생각하시면 될꺼같습니다.



▣ 먼저 위에서도 이야기했지만 좀 자세하게 이야기하면 빅엔디언(MSB)이라는 것이 있는데
메모리와 동일하게 배치하는 방법을 이야기합니다.
그리고 최상 바이트를 차례대로 저장합니다.
사람과 같이 숫자를 배치하기 때문에 사람이 읽기 편하다는 장점이 있습니다.
계산도 리틀엔디언보다 빠릅니다.



▣ 리틀엔디언(LSB)은 메모리에서 반대로 배치하는 방법입니다.
최하 바이트부터 차례로 저장합니다.
낮은 자릿수부터 계산하기 때문에 수치를 계산할 때는 빅 엔디언보다 빠릅니다.



▣ 이걸 다 외우실 필요는 없습니다. 
우선 이런 형식이 있다고만 생각해주시면 될꺼같습니다.
그리고 아까 결론적으로 pList안에 number에 주소 값이 리틀 엔디언 방식으로 저장이 된다.
이렇게만 기억해주세요. ^^
이 부분은 나중에 더 깊이 있는 내용이 필요할 때 다시 설명하겠습니다.

 

 

▣ 자 먼저 프로그램이 실행 과정 그리고 메모리는 어떻게 사용하는지를 한번 알아보겠습니다.
그림을 보시면 먼저 유저가 실행을 하겠죠? 운영체제를 통해서 요청을 하겠죠?
운영체제라는 건 우리가 OS, 윈도 같은 소프트웨어를 이야기합니다. 



그럼 보조기억장치를 통해 프로그램 정보를 로드하게 됩니다.
HDD, SSD는 보조기억장치입니다.
보조기억장치는 컴퓨터가 꺼져도 소멸되지 않고 저장되어있는 메모리를 이야기합니다.
이 부분은 나중에 따로 다룰 수 있으면 다루겠습니다. 우선 넘어가고  



그리고 하드웨어 CPU가 프로그램 코드를 가지고 와서 연산을 하겠죠?
그걸 보고 메모리 관리와 코드에 있는 명령들을 연산하게 
실행됩니다.
컴퓨터가 연산하는 코드들은 RAM 주기억 장치를 통해서 합니다.
RAM은 컴퓨터가 꺼지게 되면 메모리가 삭제됩니다. 
그 대신 보조기억장치보다 처리속도가 빠릅니다.
그건 이유가 있는데 간단하게 이야기하면 
보조기억장치는 순차 접근 sequential access 방식이고
주기억장치는 Random access 방식입니다. 
이건 나중에 다룰 수 있으면 다루도록 하겠습니다. 



이 정도까지만 알고 우선 RAM 메모리 영역을 공부해보겠습니다. 

 

 

 

 

▣ 메모리에는 이렇게 저장이 됩니다.
저번장에서 한 것처럼 모두 다 같은 곳에 똑같이 저장되면
관리하기 어렵겠죠?
이렇게 영역이 나누어져 관리가 됩니다.



▣ 오른쪽에 보면 주소에 관한 화살표가 있습니다.
낮은 주소와 높은 주소에 차이는 메모리 숫자 영역에 숫자가 높고 낮은 것도 있지만
이런 이유도 있습니다.
맨 밑에 가장 높은 주소에 있는 스택 영역 주소를 넘으면 커널 영역입니다.
커널은 운영체제를 이야기합니다.
그래서 스택 메모리를 보면 높은 주소에서 낮은 주소로 저장되는 걸 볼 수 있습니다.
쉽게 생각해서 보안등급이 높은 쪽이라고 생각하시면 될까요?
더 높은 쪽은 운영체제 쪽이기 때문에 위험하겠죠?
건드리면
어쨌든 관리에 용도, 사용 순도, 범위 등등을 고려해서 저렇게 낮은 주소와 높은 주소로 관리하고 나누어져 있습니다.
이렇게만 생각하고 하나하나 살펴보겠습니다.



▣ 프로그램 코드메모리 텍스트 영역부터 보겠습니다.
우리가 작성한 소스코드를 저장하는 영역입니다.
실행할 프로그램의 코드가 저장되는 영역으로 프로그램코드 영역 텍스트 영역이라고도 부르기도 합니다.
코드 영역은 실행 파일을 구성하는 우리가 작성했던 함수, 제어문, 상수, 조건문 등이 여기에 저장됩니다.



▣ 데이터 영역은 전역 변수와 static변수가 할당되는 영역입니다.
프로그램의 시작과 동시에 할당되고, 프로그램이 종료되면 메모리가 소멸됩니다.
이 부분이 정말 중요합니다. 프로그램이 끝나야 소멸되는 겁니다.
그런데 전역 변수는 우리가 기억하죠?
그럼 static변수는 무엇일까요?

 

 

▣ 자 이렇게 숫자를 늘리는 함수를 3개 실행했다고 가정해봅시다.
그런데 분명 Number함수에서 number변수에 숫자를 늘렸는데도,
모두 다 0이 나옵니다.



그건 number변수는 스택 영역에 있는 지역변수이기 때문입니다.
스택영역에 있는 메모리는
함수가 끝나면 지역변수는 소멸되기 때문에 다음에 실행을 해도 초기값인 0이 나오게 됩니다.
함수에 끝은 } 화살 괄호 스코프 범위 끝부분까지를 이야기하겠죠? 



이럴 때 static 데이터 영역을 사용해서 변수에 값을 소멸되지 않게 하면 됩니다.

 

 

▣ number변수를 static 변수로 데이터 영역에 저장을 하고 
함수를 3번 순차적으로 진행하면 
데이터 영역은 프로그램이 끝날 때까지 유지가 되기 때문에,
0에서 1을 더한 값이 남아있어서 저렇게 하나하나 값이 나오게 됩니다.
이걸 데이터 영역이라고 합니다.
우리가 나중에 게임을 만들 때에도 많이 사용하게 될 겁니다.



▣ 꼭 직접 한번 실습해보세요.
숙제입니다.




▣ 자 이번에는 힙 영역입니다.
프로그래머가 직접 메모리 공간이 동적으로 할당하고 해제하는 영역입니다.



▣ 쉽게 이야기하면 우리가 필요한 메모리를 쓰고 안 쓰고를 결정한다라는 말입니다.
힙 영역은 낮은 주소에서 높은 주소의 방향으로 할당이 됩니다.



▣ 힙 영역에서 메모리 할당하는 것을 동적 할당(Dynamic Memory Allocation)이라고 부릅니다.



▣ 그리고 힙 영역에서는 런타임(실행) 시에 크기를 결정하게 됩니다.
스택 영역처럼 자동으로 관리해주는 게 아니라 우리가 책임지고 
운영체제한테 요청을 해서 메모리를 가지고와 사용하는 구조입니다.
이 메모리 관리를 통해 프로그램에 최적화를 향상하고 할 수 있겠죠?
동적 할당하는 함수는 스택 영역까지 설명하고 해 보겠습니다.



▣ 스택 영역은 프로그램이 자동으로 사용하는 임시 메모리 영역입니다.
Auto메모리도 있습니다. 
우리가 사용하는 지역변수, 매개변수를 저장하는 영역입니다.
함수 호출이 완료되면 스택영역 메모리는 소멸됩니다.

 

 

▣ 우리가 함수를 진행했을 때 이 코드를 살펴보았습니다.
스택 영역은 이렇게 지역변수와 매개변수를 이야기합니다.


▣ 그래서 함수로 매개변수에 값으로 전달받고 다시 그 값을 바꿔도
리턴했을 때 값이 변화하지 않을 것을 보았습니다.


그건 스택 영역에서 함수가 끝나면 소멸되기 때문입니다. 
조금 이해가 되시죠?


그래서 제가 그때 함수를 할 때 포인터를 사용해서 값을 바꾼 겁니다. 
기억이 안 나시면 함수 부분을 다시 보고 와주세요.
그럼 아 하시고 그 부분을 포인터로 바꾸실 수 있을 겁니다. ^^



▣ 그리고 힙과 스택 영역은 같은 공간을 공유합니다.
힙이 메모리 위쪽 주소부터 아래로 할당하고 스택이 아래 주소부터 위로 할당되죠?
그래서 각 영역이 상대 영역을 침범하는 일이 챙기게 됩니다.
그걸 힙오버플로우, 스택 오버플로우라고 합니다. 
스택 영역이 크면 클수록 힙 영역이 작아지게 되겠죠?
그럼 그 반대로도요.
이건 그냥 알아두시면 될꺼같습니다. 
더 자세한 건 우리가 게임을 만들 때나? 
아니면 다룰 수 있으면 다루겠습니다.



▣ 자 너무 이론적인 부분만 이야기했네요 ^^
그래도 메모리 구조 이론적인 부분을 알아야 코딩을 하면서 이해가 되고 
코딩을 하고 읽을 수 있으니 꼭 한 번씩 꼼꼼하게 읽고 필요한 부분은 암기해주세요.
이해하시면 더 좋고요.
그렇지 않더라도 코딩을 하시면서 하면 됩니다.
걱정하지 마세요.

 

▣ 자 먼저 힙 영역에 우리가 메모리를 할당해보겠습니다. 
메모리를 할당하려면 할당할 함수를 사용해야 합니다.
그건 stdlib.h 파일을 추가해주고
malloc함수를 사용해야 합니다.



▣ 처음에 pList를 만들어서 대입 연산자를 넣자고 그 포인터 안에
malloc함수를 사용해주는데 함수 안에 어떤 자료형으로 메모리를 몇 개 생성할지 넣어줍니다.
그래서 코드에서 sizeof함수로 char형 5개를 만들어라고 지정했습니다.



▣ 그리고 밑에 보면 free라는 함수가 있습니다.
이건 다시 메모리를 해제한다라고 생각해주세요.
malloc함수는 메모리를 우리가 직접 딱 가져와서 사용하는 게 아니라
malloc함수로 운영체제한테 메모리를 이만큼  빌리겠다고 이야기를 해서 
빌리고 그걸 다시
free로 다시 돌려준다라고 생각하시면 될꺼같습니다.



▣ 그럼 돌려주지 않으면 그 메모리가 소실됩니다.
우리가 도서관에서 책을 빌리고 다시 안 돌려주는 것과 같다고 생각하시면 될꺼같습니다.
그럼 그 책을 잃어버리면 다른 사람이 그 책을 읽을 수 없겠죠?
그것과 동일합니다. 다시 사용을 못하겠죠?



▣ 그러니 메모리를 할당하면 꼭 할당만 메모리를 free로 해제해주세요.



▣ 자 그럼 디버깅해서 메모리를 확인해보겠습니다.
cc와 다르게 할당받은 곳에 cd라고 표시가 되어 있습니다. 
왜 cd인지는 저도 잘 모르겠습니다.
어쨌든 우리가 char 1byte를 5개 할당받았기 때문에.
cd 5개 메모리 공간을 가지게 되었습니다.



▣ 그런데 이상하게 cd 근처에 fd라고 위아래로 감싸져 있습니다.
이건 우리가 빌린 메모리 영역에 바리케이드라고 생각하시면 될꺼같습니다.
fd 부분을 넘게 사용하면 오버플로우가 됩니다.
꼭 기억해주세요. 



▣ 이 부분은 숙제를 하면서 한번 경험해보시면 좋을 거 같습니다.
틀러야 코딩 실력이 늘어납니다. ^^;;

 

 

 

▣ 자 그리고 realloc라는 함수가 있습니다.
이건 rea를 보면 예상되시겠죠?
네 맞습니다.
재할당하는 함수입니다.
realloc함수에 안 처음에 재할당할 주소를 넣고 그리고 size를 다시 정해주면 됩니다.
메모리를 보면 cd가 10개가 되는 걸 볼 수 있습니다. 
만약 할당한 게 없다면 malloc 함수와 동일하게 작동합니다.



▣ 꼭 두 가지 직접 해보세요.
다른 값으로 다른 자료형으로 할당하는 걸 해보시기 바랍니다. 
숙제입니다.

 

 

 

▣ 이렇게 코딩을 해보겠습니다. 
pList포인터에 메모리를 할당하고 문자를 넣어보겠습니다.
그럼 이렇게 하나하나 출력이 되죠?
메모리 안을 직접 살펴볼까요?

 

 

▣ 할당한 cd부분에 H, I 값이 하나하나 들어있는 걸 확인할 수 있습니다.
그럼 이번에는 배열과 함께 같이 사용해보겠습니다.

 

 

 

▣ 이렇게 코딩을 하고 디버깅을 하게 되면 이런 식으로 오류가 나오게 됩니다. 
왜 그런 걸까요??
pList = aList; 이게 먼가 틀렸겠죠?
배열을 잘 생각해보시면 아실 수 있을 겁니다.
한번 고민해보시고 밑에 정답을 봐주세요.

 

▣ 자 이렇게 배열에 주소를 넣는 건 shallow copy라고 합니다.
배열에 이름은 주소죠?
변수처럼 값이 아닙니다.
그럼 pList주소 안에 aList주소가 들어가게 됩니다.
잘 들어갔죠?
틀린 건 아닌 거 같습니다. 하지만 
디버깅으로 pList를 출력하면 값은 잘 나오지만 
할당한 메모리보다 값을 오버했기 때문에 오류가 나게 되는 것입니다.
왜냐? 우리는 주소를 복사하려는 게 아니라 안에 있는 값만 복사하기를 하려고 했던 겁니다.
그런데 주소를 넣었기 때문에 char을 저장할 곳에 값이 오버되겠죠?
이 부분은 꼭 기억해주세요.
자 그럼 값을 복사해서 전달하려면 어떻게 해야 할까요?

 

 

▣ 이렇게 memcpy함수를 사용하면 되는데 여기서 string.h를 추가해주세요.
memcpy 함수를 사용하려면 처음에 복사한 값을 넣을 메모리 주소를 넣고 그다음에 
복사할 메모리 주소를 넣습니다. 



그리고 어느 정도 사이즈로 복사를 할지 지정해주면 
Deep Copy가 됩니다.
이건 값만 복사한다고 생각하시면 될꺼같습니다. 
그럼 이렇게 디버깅을 해도 오류가 나지 않습니다.

 

 

▣ 이렇게 직접 메모리를 확인해보면 할당한 cd부분에 HI\0이 들어가는 걸 볼 수 있습니다.
문자열 끝에는 \0이 있는 거 잊지 마세요.
혹시나 할당을 2로 하면 어떻게 될까요?
fd를 침범하기 때문에 오버플로우가 일어나겠죠?
직접 확인해보세요.


▣ Shallow Copy, Deep Copy 꼭 기억해주세요.
정말 많이 실수하는 부분입니다.
전 세계 모든 프로그래머가 마찬가지고 저도 마찬가지입니다. ^^
값을 복사할지 주소를 복사할지를 말이죠. 
특히 = 대입 연산자를 사용해서 대입할 때 가장 많이 헷갈려합니다.


▣ 이 부분은 꼭 꼭 오류가 나게도 해보시고 오버플로우도 직접 경험하시고 
Deep Copy, Shallow Copy를 꼭 꼭 한 번씩 해보세요.


▣ 숙제입니다. 너무 중요합니다.

 

 

 

▣ 자 이건 할당한 메모리를 초기화 값을 넣어주는 함수입니다.
memset인데 처음에 초기화할 메모리 주소, 그리고 초기화할 값, 사이즈를 넣어주시면 됩니다.
간단하죠?
이건 더 설명하지 않겠습니다.


▣ 직접 한번 할당한 메모리를 초기화해보세요.


▣ 숙제입니다.

 

 

▣ 이번에는 memcmp이라는 함수를 알아보겠습니다.
이건 두 개에 메모리 바이트, 크기 사이즈 값이 같거나 누가 더 큰지를 비교해주는 함수입니다.
너무 좋은 함수죠?


우리가 하나하나 바이트를 비교하려면 정말 힘들었을 겁니다. ㅠ.ㅠ
제가 각각 상황을 만들어서 넣어보았습니다.
두 메모리가 같으면 0을 반환하고
왼쪽이 크면 1 오른쪽이 크면 -1을 반환하는 함수입니다.


▣ 직접 한번 해보세요. 

 

 

 

▣ 자 이건 위에서처럼 바이트가 아닌 strcmp라는 글자를 보시면 아시겠지만
string에 약자로 문자열에 길이를 비교해주는 함수입니다.


아까와 동일하게 각각 오른쪽 왼쪽에 문자열로 저장된 메모리 주소를 넣고 비교를 해서 
반환해주는 함수입니다. 
두 문자열이 같으면 0, 왼쪽 문자열이 더 크면 1, 오른쪽 문자열이 더 크면 -1을 반환해줍니다.


▣ 우리가 조건문을 따로 만들 거 없이 이걸 통해서 메모리 크기와 문자열 길이를 비교할 수 있겠죠?
꼭 두 가지를 해보세요.



▣ 숙제입니다.

 

 

▣ 이번에는 마지막으로 str 문자열을 찾아주는 함수를 사용해보겠습니다.
아까와 동일하게 string.h를 추가해주고 



strstr함수 안에 처음에 찾을 곳 메모리 주소와 찾아야 하는 문자열을 각각 입력해줍니다.
그리고 디버깅을 하게 되면 해당 값이 있다면 주소로 반환해주게 됩니다.



▣ 그런데 제가 찾은 메모리 주소에서 기준 주소를 뺀 연산이 있죠?
그건 왜 그런 걸까요?
간단합니다. 

 

 

▣ 위에 그림에서 처럼 배열이 만들어지고 기준 주소 있다고 생각해보겠습니다.
하지만 16진수로 표기하면 너무 어려우니깐



▣ 기준 주소가 100번지라는 10진수가 주소라고 예를 들겠습니다.
그럼 l에 위치 주소는 102번지가 되겠죠?
2칸 옆에 있으니깐요.
그럼 해당 인덱스 번호를 찾으려면
찾은 주소에 기준 주소를 뺀
102 - 100을 하게 되면 2가 남게 됩니다.
그럼 l이 시작하는 인덱스 번호를 알 수 있게 됩니다.
그래서 제가 위에서 저렇게 연산을 한 겁니다.
아시겠죠?



▣ 이것도 숙제입니다. 
직접 문자열을 만들어주세요.
배열로 만들든 
포인터로 만들든 
아니면 할당을 하든 상관없습니다.
그래서 직접 strstr함수를 활용하여 저렇게 문자열을 검색해서 찾아주세요.


▣ 숙제입니다. 



▣ 자 오늘도 이렇게 포인터와 메모리 구조 그리고 메모리를 할당하는 걸 배웠습니다.
오늘 너무 많은 내용을 했죠 ^^;;
죄송합니다. ^^
좀 어려우셨을 거라 생각합니다.
여기까지 꼼꼼하게 읽으셨고 숙제를 하셨으면 여러분들은 정말 
제가 지금까지 했던 장들을 열심히 훈련하신 겁니다. 
정말 잘하셨습니다. 



그리고 제가 같이 공부하고 싶다는 생각에 이번장은 좀 많이 준비했습니다.
쉽게 쉽게 이야기하려고 했지만 
그래도 어려운 부분이니 꼭 꼼꼼하게 공부해주시고
숙제 꼭 해주세요.



그리고 제 블로그로만 공부가 끝나면 안 됩니다.
더 좋은 강사님들 자료도 찾아보시고 함께 공부해주세요.
질문 있으시거나 필요한 코딩이 있으시면 댓글 남겨주세요.
여러분들에 공감이랑 댓글 너무 소중합니다. 
그럼 다음장에서 뵙겠습니다.



▣ 포기하지 마세요!!! 저도 했습니다!! 파이팅!!!

728x90
반응형

댓글