ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • CS50 2019 (14)메모리3, swap, call by value(reference), 힙스택, buffer overflow, 변수초기화, csv, argc, argv
    CS50 2023. 3. 13. 04:19

    ---

    www.boostcourse.org/cs112

     

    모두를 위한 컴퓨터 과학 (CS50 2019)

    부스트코스 무료 강의

    www.boostcourse.org

    1.컴퓨팅사고

    2.C언어

    3.배열

    4.알고리즘

    5.메모리

    6.자료구조

     

    ---

    7)메모리 교환, 스택, 힙

    각각 사이다와 콜라가 들어있는 컵 두 개를 떠올려봅시다. 만약 사이다와 콜라를 각각 다른 컵으로 바꿔 담고 싶으면 어떻게 해야 할까요? 교환을 도와줄 수 있는 새로운 컵이 잠시 필요하겠죠. 그렇다면 메모리에 저장된 값들을 교환할 때도 이와 비슷하게 할 수 있을까요?

     

    메모리에 저장된 두 값을 교환하는 코드를 작성할 수 있습니다.

     

    ---

    bublle sort, selection sort에서 swap이 엄청 유용했다

    임시 공간이 없으면 맞교환 하는게 불가능해

     

    ---

    2개의 변수를 교환하기 위해 swap이라는 함수를 작성할텐데, 

    정수 두개 a, b를 인자로 받아서 바꾸는게 목적

    그럼 코드에도 임시공간이 필요하겠지

    tmp에 a를 복사해둬서

    a = b;가 가능

     

    근데? 안타깝게도 이렇게 쉽게는 동작하지 않아

     

    ---

    프로토타입 위에

    main 에서 x, y를 1, 2로 초기화 = 보라색 물과 초록색 물을 부은 것

    출력

    swap

    출력 - 값이 바뀌어 있어야 하지

    swap은 이렇게 생김

     

    make noswap(컴파일)

    ./noswap

    코드도 맞고

    컴파일 에러도 없고

     

    근데 값이 안바뀌었어

     

    ---

    왜 그럴까?

    함수에 인자를 전달할 때, 값을 복사해서 전달한다

    그래서 x, y를 초기화해서 함수에 전달하는 데

    x, y자체가 아니라

    x, y의 복사본을 전달받는다

     

    그래서 swap function안에서 a, b를 교환한다

    x, y를 교환하는게 아니야. 복사본을 바꿔

     

    ---

    어떻게 해야할까?

    기본적인 개념으로 넘어가서 컴퓨터 메모리 속을 살펴보자

    그리고 메모리를 격자로 놓인 바이트로 생각해보자

    알고보니 C를 사용할 때 컴퓨터는 

    메모리 속에서 아무 공간이나 사용하는게 아니었어

    사실 아주 조직적인 방법을 사용

    특정 자료형은 여기로 가고, 다른 자료형은 저기로 가고

     

    ---

    이게 메모리라면 맨 위에는 clang이 컴파일한 0과 1의 값이 들어간다. 즉 machine code가 메모리에 올라가

    ./이름 or 아이콘을 더블클릭하면 0과 1로 컴파일된 코드가 메모리 위쪽에 저장됨

     

    그 아래에는 프로그램이 전역 변수나 정보를 쓴다면 컴퓨터 메모리 속 머신 코드 바로 아래 공간에 놓이게 된다

    사람들이 컴파일러를 만들 때 메모리 어디에 둘지 이렇게 정한 것

     

    그 아래는 heap이라는 특별한 메모리 영역이 있다

    valgrind에서 조금 전에 봤다

    heap은 여러분이 메모리를 할당 받을 수 있는 커다란 영역

    malloc을 호출하면 메모리를 이 영역에서 가져온다

    머신코드, 전역변수 아래다

    clang과 다른 컴파일러 개발자가 그렇게 메모리를 구성하기로 한 것

    heap은 아래로 자라기 때문에 메모리를 더 사용할수록 점점 더 아래로 내려간다

     

    하지만 여기 아래에 다른 용도로 할당된 메모리 영역이 있다

    프로그램에서 어떤 함수를 호출할 때마다

    함수의 지역 변수들은 스택이라는 메모리 제일 아래 영역에 놓인다

    기본 함수 main에 한 개 이상의 인자와 지역 변수가 있다면, 이 변수들은 메모리의 아래쪽에 놓인다

    swap같은 다른 함수를 호출하면 그 위에 있는 메모리를 사용

     

    heap은 malloc이 메모리를 할당하는 곳

    stack은 함수가 호출될 때 지역 변수가 쌓이는 공간

     

    ---

    스택만 생각해보면 변수를 교환할 때

    이 코드는 

    C프로그램의 기본 작동 방식으로, 스택 프레임이라는 공간이 주어짐

    argv, argc

    그리고 x, y같은 지역 변수를 저장하는 공간

    main함수 안에 있는 변수는 모두 이 메모리 영역에 저장됨

     

    ---

    main함수가 swap같은 함수를 호출하면

    해당 함수를 위한 메모리 영역이 main위에 쌓인다

    swap함수는 변수가

    인자 a와 b

    또다른 변수 tmp

    이 세개가 swap메모리프레임에 존재

     

    그니까 x, y가 바닥에 놓이고, 그 위에 a, b, tmp가 쌓임

     

    ---

    프로그램이 시작되고 main이 호출되면

    x, y두 변수가 있고, 이를 각각 1과 2로 초기화해

     

    그리고 swap을 호출하면 컴퓨터가 스택에 또 다른 프레임을 위한 영역을 할당해 준다 

    swap은 변수가 a, b, tmp 세개 있었어. 처음 두개는 인자고, 세번째는 임시 변수

    a, b를 1, 2로 초기화 했었고, 그 값은 x, y의 복사본이야

    ▽그림상 메모리에 따로 잡히는 것

     

    ---

    이제 코드에서 

    int tmp = a;하니까 tmp에 1이 들어가고, 

    a = b에서 a에 2가 들어가고

    b에 tmp를 넣는데, tmp엔 1이 있었어

    성공적으로 교환이 됐다.

     

    ---

    하지만 스택은 식당과 같다

    식당에서 식판이 쌓여있는데, 새로운 식판을 계속 위에 쌓고 

    맨 위부터 꺼내는 것

    =>교환이 세번째 줄에서 완료되면 누가 식판을 꺼내듯 프레임은 사라져

    이렇게 된다고,  ▽함수가 끝나면 사라진다 이거같네

    메모리가 사라지는게 아니지만, 이 프로그램을 위해 더 이상 사용되지 않는 것

     

    ---

    교환 이후에도 main함수가 아직 있다. 하지만 x와 y는 전혀 영향을 받지 않았다

    근본적인 해결책은 뭘까?

    복사된 값을 사용하기 때문에 교환되지 않았다.

    value를 전달했어

    main에서 swap을 호출하면 x와 y가 아니라 a와 b라는 복사본을 전달한다

     

    학생이 뭔가 말함

    쌤 : reference를 전달한다고요? reference가 뭔가요?

    참조는 포인터와 동일한 의미입니다.

    우편함의 주소로 찾아가 값을 살펴보듯, main에서 x와 y의 값을 swap에게 전달하지 않고,

    x, y의 주소를 알려줘서 swap함수가 그 주소로 가서 값을 바꾸게 하는 것

     

    그럼 함수가 끝나고 제대로 바뀌어 있다.

     

    ---

    그림으로 보면

    a가 x를 가리키고, b가 y를 가리키게 할 것

    그런 다음 코드를

    이렇게 하면

    x, y가 바뀐다

     

    ---

    swap.c

    다 똑같은데, 13번째 줄의 저 swap(&x, &y); 만 달라

    주소를 의미.

    인자쪽 해석은

    정수의 주소를 받아 a라고 부를거야, 하는 것. b도 마찬가지

     

    *a는 그 주소로 가라  ▽그 주소값 자체가 아니라. 주소로 가서의 값. 

    그럼 x가 있고, 1이라는 값이 있다. 그 값을 tmp에 저장해

     

    b*는 b의 주소를 따라가면 y가 있고 2라는 값이 있어

    그 값을 a*, a가 가리키는 주소에 에 저장해

     

    마지막으로 tmp의 값인 1을 b가 가리키는 주소에 저장

    되고 있는 중간 과정

    이제 swap함수를 반환해서 a, b, tmp가 사라져도 괜찮다

    x, y의 값 자체를 교환했으니까

     

    ---

    질문타임)

    malloc없이도 주소를 사용할 수 있다. 이 경우 &연산자를 이용해 x, y의 주소를 알아냈다

     

    ▽malloc은 왜 썼더라? malloc은 문자열 복사할 때 썼었는데, 지금처럼 문자열 복사를 하려면 아직 복사할 문자열의 길이가 정해지지 않았을 땐 방법이 없어. 어떻게 알고 공간을 만들거임. 거기선 strlen()+1이라는게 있었으니까 가능했잖아(라는 생각인데 맞을까?)

     

    함수 속에서 malloc을 사용해서 메모리 영역을 할당해줄텐데 그걸 어떻게 다룰까?어떻게든 그 메모리 영역을 기억해서 해제해야 한다. get_string은 malloc으로 메모리를 할당하는데, 여러분이 문자열 메모리를 해제한 적은 없지만, cs50라이브러리의 쓰레기 수집이라는 기능이(Garbage Collection) 프로그램이 종료될 때 해제되지 않은 메모리를 해제해 준다

     

    ---

    8)파일 쓰기

    get_int나 get_string처럼 사용자에게 입력을 받는 함수는 어떻게 구현되어 있었을까요? 앞서 배운 메모리 교환, 스택의 정의를 잘 복습한 후에 두 함수를 직접 구현해보고, 더 나아가서 파일에 출력해보는 방법도 알아 보겠습니다.

     

    ---

    이것이 가장 좋은 설계는 아니다.

    malloc을 계속 호출하면 heap이 내려오고

    함수를 계속해서 호출하면 stack이 올라가

    서로 부딪힐 것 같잖아

    재귀함수를 계속 호출하면 stack overflow를 겪을 것 -> sof이 여기서 유래 stack overflow

    자기 자신을 계속 호출하는 버그가 있는 프로그램을 실행하면 스택이 넘칠 수 있어. 다른 이유도 있지만 사이트 이름의 근원은 이것

     

    heap overflow는 반대로 malloc을 계속 호출해서 너무 많은 메모리를 할당해 메모리 속 다른 내용을 덮어쓴다

     

    너무 많은 메모리를 쓰다 보면 파일, 사진이 안열리거나, 화면정지, 동작안하는 상황 = buffer overflow라고 한다

     

    ---

    마지막 보조바퀴를 떼기

    이 CS50 라이브러리에 있는 함수들은 포인터를 사용한다

    메모리 주소를 잘 관리해서 여러분이 신경쓸 필요가 없었다

     

    ---

    이제 get_int를 구현해보자 CS50라이브러리 없이 똑같은 역할을 하게 할 것

    scanf라는 프로그램을 만들 것. 형식이 있는 scan을 의미

    보통은 이렇게 get_int()를 썼겠지만 CS50라이브러리를 제거했어서 대안이 필요해

     

    사실 scanf라는 함수가 있어, 이 함수는 printf와 비슷해. 형식지정자를 쓰면 그 형식대로 입력을 받아

    사용자의 입력을 저장하고 싶은 변수의 주소를 적는다

    입력한 값 출력

     

    scanf는 깜빡이는 입력창을 띄워 사용자가 숫자를 입력하고 엔터를 누르면 그 숫자를 저 주소에 저장해

    x의 주소를 scanf에 주는 이유는 swap함수와 같아.

     

    도우미 함수 또는 남이 작성한 코드로

    (▽내) 변수의 값을 바꾸고 싶다면

    값으로 전달하면 안된다. 그럼 복사본을 전달하고 결국 사라져  ▽다른 영역에서 일을 하기 때문이군

    대신에 &x를 사용해서 주소를 전달해야해

    그래야 swap이나 scanf같은 함수가 그 주소로 찾아가 값을 저장할 수 있다

     

    ---

    근데 사용자가 숫자대신 emma를 적으면 프로그램이 죽거나, 예상하지 못한 방식으로 동작할 것. scanf에는 에러 확인 기능이 없어

     

    ---

    문자열을 입력받아보자

    char *는 주소이기 때문에 &가 필요없다. 포인터 변수는 그 자체가 주소

    근데? 컴파일 할때 오류

    변수 s를 초기화 하지 않고 사용했어. 이걸 주소로 초기화해야해

    char *s = 어디에 저장할까?

    char *s = 0x123 이런데다가 해? 주소를 미리 알 수 없으니 NULL이라고 쓴다. 빈 공간을 의미해. NULL은 특별한 포인터로 가리키는 곳이 없다는 뜻. 모두 0이다  ▽주소를 0000..이라고 쓰면 가리키는 곳이 없다는 뜻으로 먹힌단 얘기지?

    컴파일은 잘 됐는데, null이래  ▽EMMA가 HEAD CA였구나

    프로그램이 멈추게 할 수도 있어

    어쨌든 프로그램이 망가짐

     

    왜 동작하지 않을까?

     

    char *s의 의미가, 메모리 영역의 주소로 저장할 수 있는 변수를 말해 -> 엥?? 그냥 char *s에 문자열 저장하면 안됐어? 그냥  = "어쩌구"; 하면 됐었잖아 -> 그게 초기화할 때 공간을 그만큼 할당한거네??

    null은 메모리 공간이 할당되지 않았다는 뜻

    =>EMMA의 이름이 저장될 공간을 할당하지 않았어

     

    따라서 내가 해야하는건, 사용자가 EMMA를 입력한다고 가정하고, 크기 5의 문자 배열을 선언한다

    ▽크기 5짜리 문자열을 = "12345"; 해도 똑같겠지 그럼?

    그리고 7번 줄에 주소를 건네줘

     

    배열과 포인터는 사실 연관돼 있다.

    배열은 메모리가 연속적으로 할당된 공간이고

    문자열은 문자가 연속적으로 있는 것

     

    문자열은 사실 그 메모리 공간 첫 번째 주소를 의미하고

    따라서 추이적 관계에 의해

    최소한 이 문맥에서 포인터는 배열과 같다

    *추이적 관계 (a,b) / (b,c) / (a,c)

     

    char포인터에 string을 넣으면 배열로 하나하나 다룰 수 있어 

     

    ---

    clang컴파일러는 문자 배열의 이름을 포인터처럼 다룬다

    scanf의 상황에서는 배열 첫 바이트 주소를 넘겨줘

    이제 된다

    근데

    응?

    왜그럴까, 충분한 공간을 할당하지 않았다  ▽다다음 시간에 뭐 부족하면 바꾸는거 배우는 것 같아

    다행히 프로그램이 멈추지 않았어

    하지만 커다란 문단을 입력하면 프로그램이 멈추거나, segfault가 발생할 것. 메모리를 다루면 이 오류메시지를 자주 본다

     

    ▽입력받는대로 공간 잡히게 하기는 저렇게는 안되는구나!!

     

    ---

    메모리 주소를 다룰 수 있게 됐으니

    파일에 정보를 저장해보자

    잠시 cs50.h갖고와서 사용자 입력 쉽게 받기

    string.h, stdio.h도

    File이라는 file을 만들고, fopen이라는 함수를 이용해서 phonebook.csv를 연다

     

    앞으로 우리는 포인터와 점점 더 익숙해질 것인데,

    여기 모두 대문자로 쓰인 FILE이라는 새로운 자료형을

    가리키는 포인터 변수 file을

    만들 수 있다

     

    file은 변수의 이름이고, 오늘 우리의 파일 내용을 저장해줄 것. 엄밀히 말하면 아니지만 임시로 그렇다고 하자

     

    fopen은 첫번째 인자로 열고 싶은 파일 이름을, 두번째 인자로는 r, w, a를 받는다

    r읽기

    w쓰기

    a덧붙이기 - 파일에 계속 추가하는 것

     

    fopen은 해당 파일을 가리키는 포인터를 반환해

     

    목표는 전화번호부를 만들어서 사용자로부터 이름과 번호를 입력받아 텍스트 파일에 덧붙이는 것

    데이터베이스처럼 사람들의 전화번호를 추적할 수 있도록

     

    ---

    이름을 위한 변수를 만드는데, string대신 char *를 사용

    편의를 위해 get_string을 사용, 이름 요청

    전화번호도 get_string, 원하면 scanf를 써도 되지만 에러 확인을 더 많이 해야 할 것

     

    printf와 별개로

    fprintf라는 함수가 있다 - 파일용 printf로 파일에 출력할 수 있다

    file, %s, %s\n을 사용해서 이름과 번호를 출력할 것

    그리고 파일을 닫아

     

    새롭지만 개념적으로는 직관적이야. 문법이 낯설어도 동작은 이해할 것

     

    의도적으로 csv라는 이름을 선택했어

    csv가 뭔지 알아? 쉼표로 분리된 값이야(밑에 설명 더)

    간단한 엑셀이나 Numbers같은 프로그램으로 열 수 있어

     

    ---

    컴파일 : make phonebook

    실행 : ./phonebook

    ls를 쳐보면 오늘 적은 프로그램과 별개로 phonebook.csv파일이 생겼다

    이 파일을 열어보면

    한번 더 한번 더

     

    ---

    Numbers나 MS Excel같은 프로그램으로 보면

    이렇게 이름과 번호가 저장된 스프레드시트를 볼 수 있다

     

    정보과학에서처럼 데이터 분석을 할 때 우리가 익숙한 행렬 형태의 CSV파일로 데이터를 생성해주는 코드를 작성할 수 있다

     

    ---

    9)파일 읽기

    우리가 일상적으로 사용하는 파일은 텍스트, 이미지, 영상 등 여러 형식이 있습니다. JPEG 형식의 파일인 경우 그 값 속에는 JPEG파일 형식인지를 알려주는 실마리가 있습니다. 이번 강의에서는 JPEG 파일을 읽고 그 실마리를 찾아보도록 하겠습니다.

     

    ---

    JPEG형식으로 된 Brian의 사진

    gif포맷 고양이 그림

    이 있고

     

    jpeg.c  ▽제이펙이라고 발음하게

    명령줄으로 주어진 파일이 JPEC인지 아닌지 확인해주는 것

     

    ---

    오타 : file()이 아니고 fopen()

    이제 int main(int argc, char *argv[])를 쓴다

    사용자가 프로그램의 이름 말고 파일명도 입력하길 바라기 때문에 if (argc != 2)

     

    사용자가 입력해준 파일명으로 파일을 여는 것 = 사용자가 입력한 두 번째 문자열이니까 argv[1]

    읽을거라서 "r"

    file() -> fopen()

    그리고 간단한 에러 체크, fopen이나 mallc이나 get_string같은 함수는 에러가 생기면 NULL이란 값을 돌려줌

    일단 여기선 문제가 생기면 1을 반환

     

    크기가 3인 문자 배열을 만들고

    fread라는 함수를 사용 - 과제에서 나올테니 빠르게 지나간다나..

    배열, 읽을 바이트 수, 읽을 횟수, 읽을 파일을 넘겨줌

     

    결국 저 두 줄은 파일에서 3바이트를 읽어오는 것.

    파일의 첫 24비트, 3바이트를 읽어온다.

     

    이걸 왜함?

    JPEG형식에 관한 설명 문서를 보면, 여러분이 카메라로 사진을 찍으면 생기는 모든 JPEG파일의 첫 세 바이트는 무조건 FF, D8, FF로 시작합니다. JPEG 개발자들이 정한 일종의 매직 넘버. 파일의 시작점에서 파일이 JPEG사진이라는 것을 알려주기 위한 것(더 조건이 있지만 과제에서 살펴볼거래. 그래서 Maybe)

     

    그래서 그걸 체크한다, C에서 16진수를 그대로 적을 수 있다

    그리고 맨 위에 저건 사실 unsigned가 돼야 한다. -128~127이 아닌 0~255범위의 값

     

    ---

    위에도 썼던 오타수정

    이제 컴파일 잘 됨.

    jpeg.c 프로그램을 실행해서, Brian 사진의 경로를 인자로 준다

    ▽오 경로로 줬는데 그걸 char *로 받고 그걸 또 fopen에서 사용할 수가 있네

     

    포인터를 배우게 되면, 파일에 적을 뿐 아니라 읽을 수도 있다

     

    ---

    이 정보를 어디에 쓸까?

    파일 입출력과 관련된 많은 기능을 배울건데, 사진 파일이 어떻게 생겼냐면

    첫주에 이렇게 생겼다고 했었어

    1은 흰색, 0이 검은색이라고 하면 이렇게 됨

    멀리서 보면 웃는 얼굴

     

    이렇게 사진 또는 비트맵이 이미지의 픽셀을 나타냄

     

    과제로 이미지가 어떻게 표현되고 어떻게 바꿀 수 있는지 배울 것. 흑백필터.. 좌우전환 필터, 모든 모서리를 찾아내는 모서리 타밎도 구현할 수 있다

     

    나중엔 JPEG파일을 복구하는 코드도 적을 것

    원본이 지워졌는데 그걸로 수사용 사본을 만들어뒀었음. 그걸 C로 JPEG 사진을 복구하는 코드를 만들 것

Designed by Tistory.