부스트코스 CS50 코칭스터디2기 5주차

승아·2021년 2월 16일
0

CS50 코칭스터디2기

목록 보기
5/6

📌 메모리

포인터

포인터

  • 컴퓨터 메모리의 주소로, 변수의 주소를 쉽게 저장하고 접근하기 위한 개념
  • 메모리상 변수의 주소는 16진법으로 표현됨
    & = address of
    * = go to
  • int *numberPtr; // 포인터 변수이기 때문에 이 안엔 주소가 있다.

💡포인터의 크기는 메모리의 크기와 어떤 관계가 있을까요?

  • 포인터의 크기와 메모리 크기는 비례한다. 32bit 운영체제 포인터 크기는 4byte, 64bit 운영체제 포인터 크기는 8byte.

문자열

string

  • typedef는 새로운 자료형을, char *은 문자에 대한 포인터를, string은 자료형의 이름을 의미합니다.
typedef char *string

문자열 비교

  • == 을 사용하면 주소값을 비교하기 때문에 <string.h>를 추가한 후 strcmp 함수를 이용하거나 char 배열 혹은 string을 인덱스로 접근하여 하나씩 비교한다.

문자열 복사

#include <cs50.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>

int main(void)
{
    char *s = get_string("s: ");
    char *t = malloc(strlen(s) + 1); // null 종단 문자 추가하기 위해 + 1 해준다.

    for (int i = 0, n = strlen(s); i < n + 1; i++)
    {
        t[i] = s[i];
    }

    t[0] = toupper(t[0]);

    printf("s: %s\n", s);
    printf("t: %s\n", t);
    
    free(t); // 
}
  • 만약 string t = s;라고 정의해버리면 둘이 같은 주소를 가리키기 때문에 둘 중 하나라도 값이 바뀌면 둘 다 값이 바뀐다.
  • malloc 함수 : 정해진 크기 만큼 메모리를 할당하는 함수.
  • free 함수 : 메모리 해제 함수. 메모리 해제를 하지 않을 경우 메모리에 저장한 값은 쓰레기 값으로 남게 되어 메모리 용량의 낭비가 발생하게 된다. 이러한 현상을 '메모리 누수'라고 함.
  • valgrind : debugging 도구. 버퍼 오버플로우, 메모리 누수 같은 에러 확인

메모리 교환, 스택, 힙

  • 머신 코드 영역 : 프로그램이 실행될 때 그 프로그램이 컴파일된 바이너리가 저장
  • 글로벌 영역 : 프로그램 안에서 저장된 전역 변수가 저장
  • 힙 영역 : malloc으로 할당된 메모리의 데이터가 저장.
  • 스택 영역 : 프로그램 내의 함수와 관련된 것들이 저장.

💡 메모리 영역을 다양하게 나누는 이유는 무엇일까요?

  • 한프로세스가 실행되며 사용하는 공간 중 여러 영역이 있는데 각 영역은 다른 특성이 있다. 어떤 영역은 쓰기 권한이 없고 또 어떤 영역은 프로그램 실행 후에는 사라지는 공간이다. 이처럼 다양한 특성을 갖는 메모리가 필요한데 모든 공간을 하나로 묶는 것보다 특성별로 나눠 관리하면 훨씬 더 편리하기 때문.

파일 쓰기

#include <cs50.h>
#include <stdio.h>
#include <string.h>

int main(void)
{
    FILE *file = fopen("phonebook.csv", "a");
    char *name = get_string("Name: ");
    char *number = get_string("Number: ");
    fprintf(file, "%s,%s\n", name, number);
    fclose(file);
}
  • fopen : 파일을 FILE이라는 자료형으로 불러올 수 있다. 첫번째 인자는 파일의 이름, 두번째 인자는 모드로 r은 읽기, w는 쓰기, a는 덧붙이기를 의미
  • fprint : printf에서 처럼 파일에 직접 내용을 출력할 수 있다.
  • fclose : 작업이 끝난 후에는 파일에 대한 작업을 종료해줘야 한다.

프레임워크

  • 환경, 뼈대
  • 커다란 환경안에 사람이 사용하는 도구

라이브러리

  • 코딩을 재생하기위한 도구

팀미션 👩🏻‍💻

  1. 포인터를 이중으로 활용해보자
#include <stdio.h>

int main(void){
    int arr[6][5] = {{1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}, {11, 12, 13, 14, 15}, {16, 17, 18, 19, 20}, {21, 22, 23, 24, 25}, {26, 27, 28, 29, 30}};
    
    int n = sizeof(*arr)/sizeof(int);
    int m = sizeof(arr)/sizeof(int)/n;
 
    for(int i=0; i< n; i++) {
        for(int j =0; j< m; j++){
           printf("%d\t", *(*(arr+i)+j));   
        }
        printf("\n");
    }
    return 0;
}
  1. 메모리와 overflow 개념 정리

1) 수업에서 언급되었던 Heap overflow와 stack overflow에 대해서 어떤 경우에 발생이 되는지 서술해주세요.

✅ Heap overflow

  • Heap은 사용자가 동적으로 할당하는 메모리 영역이다.
  • Heap 영역에 할당된 공간보다 큰 사이즈를 입력받아 발생할 수 있음.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // read함수 사용 위해

int main(){

    char* buf = malloc(40);
    char* secret = malloc(40);

    read(0, secret, 40);
    read(0, buf, 100); //취약점

    printf("%s\n", buf); //취약점
    return 0;
}

✅ Stack overflow

  • Stack은 함수의 처리를 위해 지역변수와 매개변수가 위치하는 메모리이다.
  • 스택에 할당된 버퍼들이 문자열 계산 등에 의해 미리 정의된 버퍼의 크기를 넘는 경우에 버퍼 오버플로우가 발생한다.
  • 한 함수에서 너무 큰 지역 변수를 선언하거나 함수를 재귀적으로 무한정 호출하게 되면 발생할 수 있음

// 너무 큰 지역 변수를 선언하는 경우

int main(void) {
     char buf[100000000000000];
     return 0;
}

// 미리 정의된 버퍼의 크기를 넘는 경우

int main(void) {
     char buf[3];
     scanf(%s”,buf); 
    // 입력된 값이 3byte를 넘을 경우 다른 영역 메모리까지 침범한다. 
    // 다른 영역까지도 뛰어넘는 문자열을 넣을 경우 스택 오버플로
     return 0;
}

// 재귀 무한 호출

int foo() {
     return foo();
}

2) strcpy와 strncpy의 차이점을 서술해보세요. (어떤 것을 추천하는지와 그 이유에 대해서 서술해주세요.)

✅ 공통점

  • 둘 다 문자열 복사 함수

✅ 차이점

  • 문자열 길이 설정 여부

✅ strcpy(dest, src)

  • 2번째 인자 값(src)에 있는 문자열을 첫번째 인자 값(dest)에 복사하는 함수
  • 2번째 문자열의 길이가 1번째 문자열 공간보다 많다면 overflow 발생
#include <stdio.h>
#include <string.h>

int main(void){
    char destStr[128] = {0,};
    char * srcStr = "hello world";

    strcpy( destStr, srcStr );

    printf("%s\n",destStr);
    printf("%s\n",srcStr);

    return 0; 
}
  • 주의 해야 할 점
  1. strcpy는 문자열 끝(= '\0') 까지 복사를 한다.
char buf1[] = "Hello";    //"Hello\0" 이므로 size = 6;
char buf2[] = "BoostCourse";    // size = 12;
 

strcpy(buf2, buf1);
 
printf("%s\n", buf2);    // "HelloCourse"가 아닌 "Hello\0ourse"가 된다.​

✅ strncpy(dest, src strlen(src))

  • strcpy와 다른 점 3번째 인자에 복사할 길이를 넣음
  • overflow를 예방해줄 수 있음
#include <stdio.h>
#include <string.h>

int main(void){
    char destStr[128] = {0,};
    char * srcStr = "hello world";

    strncpy( destStr, srcStr, sizeof(destStr)-1 ); // 널 종단문자 때문에 -1을 해준다.

    printf("%s\n",destStr);
    printf("%s\n",srcStr);

    return 0; 
}
  • 주의 해야 할 점
  1. strncpy로 복사했을경우 적절한 위치에 '\0'을 넣어줘야 한다.
char buf1[] = "Hello";    //"Hello\0" 이므로 size = 6;
char buf2_1[] = "BoostCourse";    // size = 12;
char buf2_2[] = "BoostCourse";    // size = 12;

strncpy(buf2_1, buf1, sizeof(buf1));
strncpy(buf2_2, buf1, sizeof(buf1) - 1);
 
printf("%s\n", buf2_1);    // "Hello\0ourse"가 된다.​
printf("%s\n", buf2_1);    // "HelloCourse"가 된다.
  1. n의 길이를 주의해야 한다.
strncpy(dest, origin, n)
n <= sizeof(origin)
n <= sizeof(dest)

✅ strcpy vs strncpy

strncpy는 복사할 대상의 크기를 정해주므로 한 번더 점검 할 수 있기 때문에 오버플로우 예방에 있어 strncpy가 strcpy보다 더 안전하다고 생각한다. 그러나 stncpy도 2번째 문자열의 길이가 1번째 문자열 공간보다 많다던가 첫번째 문자열보다 더 큰 사이즈를 입력한다면 에러가 날 수 있기 때문에 완전히 안전하다고 할 수는 없다.

3) 메모리 초기화, 복사, 이동, 비교와 같은 함수가 라이브러리에 있습니다. 사용방법을 숙지하고, 간단하게 코드로 구현 후 정상적으로 동작이 되는지 확인해보세요.

✅ memset

  • memset(넣을 주소, 넣고자 하는 값, 몇 바이트 넣을 건지)
  • ptr에 ‘’을 character 사이즈 2 만큼 넣을 거다.
#include <stdio.h>
#include <string.h>
#include <memory.h>

int main(void){
    char ptr[2];

    memset(ptr, '*', sizeof(char) * 2);
    puts(ptr);

    return 0; 
}

✅ memcpy

  • memcpy(복사할 주소, 복사할 값, 복사할 길이)
#include <stdio.h>
#include <string.h>

int main(void){
    char str1[50] = "Hello BoostCourse";
    char str2[50];
    char str3[50];

    memcpy(str2, str1, strlen(str1) + 1); // 널 종단문자 추가 
    memcpy(str3, "Hello",5);

    // puts(const char * str) : ‘\0’까지의 문자열을 출력하는 함수
    puts(str1);  // Hello BoostCourse
    puts(str2);  // Hello BoostCourse
    puts(str3);  // Hello

    return 0; 
}

✅ memmove

  • memmove(옮길 위치, 복사할 위치, 복사할 문자 개수)
  • str + 17번째에 str부터 총 5개의 문자를 복사해 붙이겠다.
#include <stdio.h>
#include <string.h>

int main(void){
    char str[50] = "Hello BoostCourse";

    puts(str); // Hello BoostCourse

    printf("--> memmove\n");

    memmove(str + 17, str, 5);

    puts(str); // Hello BoostCourseHello

    return 0; 
}

✅ memcmp

  • memcmp(비교할 값, 비교할 값, 비교할 데이터 개수)
  • 반환 값이 0일 경우 같음 1일 경우 다름
#include <stdio.h>
#include <string.h>

int main(void){
    char str1[50] = "Hello BoostCourse";
    char str2[50] = "Hello BoostCourse";
    char str3[50] = "Hello";

    if (memcmp(str1, str2, strlen(str1)) == 0){
        printf("str1과 str2는 일치합니다.\n");
    }else {
        printf("str1과 str2는 일치하지 않습니다.\n");
    }

    if(memcmp(str1,str3, strlen(str1)) == 0){
        printf("str1과 str3는 일치합니다.\n");
    }else {
        printf("str1과 str3는 일치하지 않습니다.\n");
    }

    return 0; 
}

참고 사이트

추가 질문

  • 메모리 내에서 code와 data는 영역이 각자 따로 존재하지만 heap과 stack 영역은 공유해서 사용하는 건가요?
    - 컴파일때 영역이 fix. 집이라 생각하면 heap은 거실 거실이 커지면 남은 방 사이즈가 줄어들듯 heap이 커지면 스택이 줄어들고 이런식 유동적 ..
  • stack영역은 프로그래머가 할당한 변수나 함수의 크기만큼만 컴파일 시에 할당되는 것인지? 그렇다면 만약 stack영역(지역변수나 함수)을 덜 사용했다면 heap영역(동적할당)부분을 더 많이 사용할 수 있나요?
    - stack영역은 Os정책에 따라 정해진 크기가 있음. 비주얼 스튜디오엔 스택영역을 지정할 수 있는 옵션이 있음. 여기까진 다 내꺼 이런느낌 내가 쓰던 안쓰던. 스택을 줄이면 힙이 커진다고 하지만 스택을 아예 0으로 할 순 없음
  1. 버블정렬을 포인터로 바꾸는 문제
#include <stdio.h>

void sort(int n, int arr[]);

int main(void){
    int n = 7; 
    int arr[7] = { 0, 25, 10, 17, 6, 12, 9 }; 
    sort(n, arr); 
    return 0; 
}

void sort(int n, int arr[]){
    int temp;
    int cnt;

    for(int i = 0; i < n; i++){
        cnt = 0;
        for(int j = 0; j < n - 1; j++){
            if (*(arr + j) > *(arr + j + 1)){
                temp = *(arr + j);
                *(arr + j) = *(arr + j + 1);
                *(arr + j + 1) = temp;
                cnt++;           
            }
        }
        if(cnt == 0)
            break;
    }

    for(int i = 0; i < n; i++){
        printf("%d\t",*(arr+i));
    }
    printf("\n");
}

회고 ✍🏻

학교다닐때 가장 이해가 안갔던 포인터를 드디어 배웠다. 여러 언어를 배우고 다시 배워서 그런지 확실히 예전보단 이해가 더 잘되었다. 이 강의를 1학년때 접했더라면 더 이해하기 수월했을텐데.. ! 모든 컴공학생들이 1학년때 필수로 보았으면 한다.
1번 문제를 통해 2차원배열을 이중포인터로 활용하는 방법에 대해 알게되었다. *(*(arr+i)+j) 이 부분이 핵심인데 일단 1차원 배열과 달리 arr은 2차원 배열이기 때문에 *(arr+i) 는 값이아닌 주소값이 나온다. 그러므로 *(arr+i)+j는 주솟값을 가지며 값을 얻으려면 *(*(arr+i)+j) 로 표현해주면 된다.
2번 문제를 풀며 스택 오버플로우와 힙 오버플로우에 대해 공부하면서 각각이 무슨역할을 하는지 정확히 알 수 있었다. 마지막 3번 문제는 1번 문제를 풀었다면 수월하게 풀 수 있었던 문제라 생각한다.
갈수록 점점 더 재밌어지는 코칭스터디..! 벌써 다음주가 마지막이란게 놀랍다. 언제 또 한 달이 지났는지.. 요즘들어 시간이 참 빠르게 가는것 같다 😭 그럼 다음주차도 화이팅 !!!

0개의 댓글