Lecture 3 of Introduction to Computer science

Lecture 3

이번 강좌에서는 메모리 할당과 포인터에 대한 내용이 주를 이룬다.




처음 도입부에서는 드라마에서 나오는 장면을 가지고 시작한다. 위에 보이는 인물이 찍힌 CCTV를 분석하는 장면인데 언듯 보기에도 잘 보이지 않는 상황. 하지만 주인공들은 선글라스를 확대하여 선명하게 해서 반사된 옷의 로고를 실마리로 찾기에 이른다. 뭐 드라마려니 하면서 넘어가는 David.


CS50 Sandbox에서 CS50 IDE를 활용하기 위한 interface를 설명하는 장면이다. 크게 다른 것은 없으며 좀 더 프로그래밍 환경과 비슷한 UI를 제공한다. (고양이는 덤) 크게 다른 점은 컴파일하기 전 무조건 저장을 해야 한다고 알려주고 있다.


마찬가지로 workspace에서 작업을 할 때 tilde(~)의 의미는 home directory를 칭한다.


이전에 계속 사용해왔던 명령어를 보았을 때 프롬프트창에서 './hello'와 같이 명령을 입력하는데 이는 현재폴더의(./) hello라는 파일을 실행시킨다는 의미이다. 위에서는 test라는 폴더를 만들고(mkdir) 삭제하는(rmdir) 명령어를 입력하고 있는 모습이다.


여기서는 check50라는 명령을 통해 본인의 코드가 제대로 구성이 되었는지 확인 할 수 있는 기능을 활용해본다. Github 계정은 필수인 듯 하다.


진행이 잘 된다면 위와 같이 Smile 표시:) 가 나타나며 진행사항에 대해 출력된다. 만약 정상 기능이 되지 않았다고 판단이 된다면 Unhappy 표시 등이 나타난다고 한다.


이번엔 귀여운 고양이를 통해 debugging을 하는 방법을 알아 본다. 컴파일을 먼저 진행한 뒤 'debug50' 명령을 통해 debug mode를 활성화 한다. 프로그램을 한줄한줄 통과하며(pause, break) 어떤 부분에서 문제가 발생했는지 알 수 있다. 이때 위에서 보이는 것처럼 break point에서 어떤 상태인지 확인할 수 있고 Step over를 통해 프로세싱을 진행한다.


프롬프트에서 'Name: '이 뜨면 이름을 David라고 입력하게 되면 오른쪽에서 Value가 David로 변경되는 것을 확인할 수 있다. 그리고 Step over를 클릭하면 다음단계인 printf가 실행되는 것을 알 수 있다.


위에 적힌 것들은 이제까지 다뤘던 function들이며 원하는 type의 데이터를 얻기위한 기능들을 가지고 있으며 이 중에 약간 다른 것은 get_string이다. 이유는 아래에서 차근차근 설명을 들어보자.


'compare.c'파일을 통해 'get_int'의 성능 검증을 하는데 위에서 보는 코드만으로는 확실히 틀린점이 없으며 실제로 실행을 해봐도 잘 동작하는 것을 알 수 있다.


이번에는 'get_int'대신 'get_string' 함수를 검증해본다. 다른 글자일 때는 당연히 다르다고 판별하지만 같은 글자들을 아무리 비교해도 같다고 나오지 않는다.


왜냐하면 string이라는 type은 실제로 존재하는 것이 아니기 때문이라고 설명한다. 다른 type의 데이터들은 그 값들을 실제로 담고있지만 string은 그렇지 않고 주소값을 가지게 된다.


위는 'Brian'과 'Veronica'를 각각 변수에 저장할 때 나타나는 현상이다. 위와 같이 각각의 주소에 글자들이 하나씩 들어가고 이를 변수에 저장하면 변수에는 글자들이 들어가는 것이 아닌 주소값의 시작점이 저장된다. 따라서 같은 'David'라는 글자를 비교하더라도 실제로는 다른 주소값들을 비교하였으니 당연히 결과는  다르다고 나타나는 것이다.


그래서 이를 해결하고자 어떤 문자열을 비교할 때 각 주소에 접근하여 한글자씩 비교하는 방법을 채택하여 코드를 짠다. 'compare2.c'에서 볼 수 있듯 글자수가 다르면 false, 각 글자 중 하나라도 다르면 false, 그 이외의 경우에만 true를 반환하도록 함수를 작성하였다.


여기서 하나 더 도입되는 정의를 볼 수 있다. 'char *'의 의미는 string과 대동소이하나 기본적으로 pointer의 역할로써 주소를 저장하게 된다. 값을 받아보면 데이터가 저장되어 있는 주소들의 첫 바이트의 값을 보게된다. (cf. char* a, char * a, char *a 모두 같은 내용이나 명확하게 하기 위해 char *a와 같은 포맷을 지향한다)


그러면서 유용한 함수인 'strcmp'를 소개하며 똑같이 문자열이 비교되는 모습을 보여준다. 크게 다른 점은 위에서는 return value가 boolean이었지만 여기서는 int인 것을 알 수 있다.


'compare4.c에서는 포인터를 통해 값을 저장하고 주의해서 볼 점이 12~14줄에 return값이 1로 설정이 되었는데 이를 'Segmentation fault'라고 한다. 건들지 말아야할 메모리를 건드렸다는 것이나 무언가 잘못되어 가고 있고 탐지하지 못한 것으로 정의한다. 이는 return값이 0이어야 정상이기 때문인 듯 하다.


'compare6.c'에서는 's == NULL'을 '!s'와 같이 더 간결하게 표현하였다.


이번에는 'copy0.c'를 통해 string을 복사해서 첫글자를 대문자로 바꿀 때 어떻게 출력이 되는 지 확인하였다. t에 s의 주소를 복사하고 t의 첫글자만 대문자로 바꾸고 출력했더니 t와 s 모두 첫글자가 대문자로 변한 것을 확인 할 수 있었다.


이는 아까 얘기나왔던 것과 같이 string은 문자열이 아닌 주소값이 들어서는데 t에 s값을 복사해도 똑같은 주소값이 들어서고 이는 둘 다 같은 문자열을 가리키는 것이 된다. 때문에 t를 대문자로 바꿔도 s또한 대문자화 되는 것을 설명할 수 있다.


그래서 문자열의 글자 하나씩 개별적으로 복사를 하여 그 데이터의 주소를 저장하고 해당 문자열만 대문자화하면 해결할 수 있게 된다. 여기 'malloc'이라는 새로운 함수를 볼 수 있는데 'memory allocation'의 약어이며 어구를 위한 메모리(chunk of memory)를 요청하는 것이다. strlen에 하나의 공간이 더 필요한 것은 null(\0)값을 위한 자리가 필요하기 때문이다.


이제 확보한 메모리에 한글자씩 복사해오면 된다. 그후 복사한 문자열만 대문자화 하면 해결이 되는 것을 볼 수 있다. 이는 'strcpy'라는 함수와 역할이 같다. 또한 malloc을 통해 메모리를 확보하는데 이는 자칫 메모리 낭비로 이어질 수 있기에 사용자의 입력에 따라 할당할 수 있는 free 함수가 존재한다.


'scanf0.c'에서는 get_int나 get_float등과 같은 함수 대신에 사용할 수 있는 scanf함수를 소개한다. printf에 대비되는 개념이며 사용법이 비슷하나 위와 같이 int 데이터를 출력할 때 '&'가 변수앞에 붙어 있다. 이는 단순한 x의 값을 요청하지 않고 x의 주소값을 받아오는 형태이다.


 따라서 char 데이터를 받아올 때 char *s로 변수를 받아 내면 scanf에서 굳이 &를 변수앞에 붙이지 않아도 되는 개념이다. 하지만 실제로 실행하면 null만 표시되고 제대로 동작하지 않음을 확인할 수 있고 이는데 pointer는 8byte의 값을 통해 주소를 저장하지만 방금은 주소에 대한 내용만 있고 내용물은 없기 때문에 null이라고 출력되는 것이다.
 위와 같이 hardcode이긴 하지만 실제 영역을 잡아주면(s[6]) array가 pointer로 대체역할을 할 수 있게 된다고 한다. 물론 s는 아직 주소값으로 인식이 되었기때문에 scanf에서는 여전히 &가 필요없다. 이제는 제대로 동작한다.
 아주 긴 문자열을 넣었더니 프롬프트에서 segmentation fault를 냈는데 이는 메모리는 6자리를 잡아달라고 했지만 약간 더 여유를 두고 메모리를 할당된 상태에서 메모리량을 초과했기 때문에 나타나는 현상이다.
 추가적으로 실제 6자리만 할당한 상태에서 조금 더 긴 문자열을 넣은 상태에서 segmentation fault가 일어나지않을 때도 있는데 왜 6자리 이후 글자들은 나타나지 않느냐면 사용자가 원한 동작이 아니기 때문이다.


저번과 같은 개념으로 메모리의 각 칸을 char의 값으로 각각 채운다면 이는 주소값들로, 즉 pointer로 저장이 된다.


이를 'addresses.c'에서 확인을 해보는데 위와 같이 조금 복잡한 형태의 주소값을 반환하는 것들을 볼 수 있다. 이때 앞의 '0x'는 16진수임을 알려주는 것이며 뒤의 자리가 실제 주소이다. 이 주소값을 10진수로 변환해보면 0x2331010 = 36900880임을 확인할 수 있다. 꽤 큰값이며 여기서 16진수를 주소값으로 정한 이유는 다음과 같다.


10진수로 255 216 255는 2진수로는 1111 1111 1101 1000 1111 1111과 같이 변환이 되고 16진수로는 f f d 8 f f (0xff0xd80xff)로 환산이 가능하다. 따라서 훨씬 적은 공간으로 많은 수를 커버할 수 있기 때문에 그리고 2진수와 전환이 굉장히 쉽기 때문에 활용한다.


이번에도 두개의 컵만 있을 때 우유와 오렌지주스를 바꾸는 미션을 주면서 당황하는 이유에 대해 물어보고 한개의 컵이 더 필요하다는 것을 알려주며 데이터를 교환 할 때 임시저장공간이 필요함을 설명한다.


'noswap.c'에서 값을 바꾸는 코드를 작성하여 진행해보지만 실제로 교환이 되지 않음을 알 수 있고 이는 함수내에서 바뀌기만 하고 실제 x, y값들이 변경되지 않았기 때문임을 알 수 있다.


그래서 이번에도 pointer를 통해 주소로 접근을 하여 값을 바꾸는 해결책을 제시하고,


반환값이 없는 함수임에도 주소로 접근하여 교환을 진행하면 제대로 swap이 진행된 것을 확인할 수 있다. (pointer로 접근하여 주소값을 가져오지 않고 해당 주소에 있는 값을 실질적으로 가져온다) 또한 함수의 input을 pointer로 받기 때문에 함수를 사용할 때도 swap(&x, &y)와 같이 주소로 parameter를 전달하여야 한다.


위는 main함수와 swap함수가 메모리상에서 어떻게 공간을 차지하는지 conceptual하게 보여주는 그림이다. 이는 'stack'이라고 칭하며 정확한 메모리 상태에 대한 설명은 아래와 같다.


메모리의 모습이 위와 같다고 가정할 때 가장 위쪽은 compile된 파일의 'text'가 자리잡고 처음 실행 될 때 차지하게 되는 모습이다. 'heap'은 일정 단위의 메모리들을 malloc과 같은 함수들로 호출했을 때 잡히게 되는 것을 호칭한다. 함수들은 위쪽에서부터 차근차근 차지한다. 메모리의 하단부터 메모리들이 할당되는 것을 'stack'이라고 한다. 그리고 만약 전역변수를 선언하면  'text'와 'heap'사이에 'initialized data' or 'uninitialized data'로 존재한다. 'stack'밑에는 'environment variables'가 위치하고 web programming같은  경우에 자주 사용한다고 한다.



위의 메모리에 따라 움직이는 것을 보기 위한 예시이며 x에는 메모리를 할당했지만 y는 그렇지 않았기 때문에 쓰레기값으로 차서 제대로 작동하지 않을 것임을 알려준다.


위는 pointer에 대해 재밌게 애니메이션으로 만든 영상이다. 굉장히 쉽게 설명하고 있어 참고하기 좋을 듯하다.


이번에는 자체적으로 data structure을 만들어서 정보를 저장하는 모습이다. typedef struct { char *names; char *dorms; }의 형태로 정의한다.


'struct1.c'를 통해 struct에 대해 알아보고자 한다. #include "struct.h"를 통해 본인만의 data structure를 만들 수 있는 모습을 보여주고 정의한 구조에 따라 student라는 데이터 타입에 정보들을 순차적으로 넣을 수 있게 된다. 여기서는 custom data type으로 'student'를 정의하였다.


그래서 student의 순서에 따라 i가 증가하면서 데이터들을 저장하기 용이하게 설계할 수 있다.


다시 처음으로 돌아가서 보여줬던 영상처럼 실제로 이미지를 확대해도 그 이상의 resolution을 가지고 있지 않다면 반사된 이미지같은 것은 볼 수 없다는 걸 깨닫게 된다.


이제까지 열심히 공부한 pointer가 빛을 발하게 되는 순간이다. 위와 같이 웃는 이미지를 데이터로 저장할 때 검은점은 0 흰점을 1로써 bmp로 저장할 수 있으며 이를 저장한 주소를 연결(dereferencing)하는데 아래와 같은 포맷을 이용하게 된다.


물론 사람이 결정한 포맷이며 이미지를 저장할 때 첫 비트들은 이름, 다음 비트들은 색, 다음비트들은 크기 등등의 정보가 담겨있도록 값을 저장하게 되는 것이다. 값들을 수정하거나 추가/삭제할 때도 pointer를 통해 접근한다고 한다.

댓글