CS50_메모리_(2)[문자열비교,복사와 메모리 할당과 해제 (swap)]

김두미·2022년 6월 23일
0
post-thumbnail

1. 문자열 비교

#include<stdio.h>
int main(void)
{
    char *s = "EMA";
    printf("%c\n",*s);
    printf("%c\n",*(s+1));
    printf("%c\n",s[1]);
}

이와 같은 코드를 실행하면

위 사진과 같은 결과를 얻을 수 있습니다.

s[1]은 당연하게도 s의 1번째 원소에 접근하게 하는 것이고
*(s+1)은 s에 적혀있는 주소에 1을 더해 한바이트 이후의 문자 즉 첫번째 원소에 접근하게 합니다.

1) 문자열 비교 코드

#include<stdio.h>
#include<cs50.h>
int main(void)
{
    string s = get_string("s :");
    string t = get_string("t :");

    if (s==t) {
        printf("Same\n");
    }
    else{
        printf("Different!\n");
    }
}

이와 같은 코드를 실행하면 어떤 글자를 입력되는지와는 상관없이
s와 t가 다르다고 나옵니다.
그 이유는 s와 t에는 해당 문자가 저장되어 비교되는 것이 아니라
해당 문자의 첫번째 글자의 주소가 저장되어 주소끼리 비교되는 것이기 때문입니다.

[get_string은 입력받은 문자열을 저장할 메모리 공간의 첫바이트 주소를 반환합니다.]



2. 문자열 복사

문자를 입력받아 첫번째 문자가 대문자로 변경한 문자열을 출력하는 코드를 만들어봅시다.


#include<stdio.h>
#include<cs50.h>
#include<ctype.h> // toupper
int main(void)
{
    string s = get_string("s :");
    string t = s;

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

    printf("%s\n",s);
    printf("%s\n",t);

}

첫째 줄에는 입력받은 문자열이 출력되고
둘째 줄에는 첫문자를 대문자로 변경한 문자열이 출력될것입니다.

하지만 이상하게도 대문자로 둘다 변경되어있습니다.

그 이유는 string t = s;에서 주소를 t에 복사한 것이 되어 같은 곳을 가리키게 되기 때문입니다.

그렇다면 어떻게 서로 다른 메모리 공간에 jack를 복사할 수 있을까요?

메모리를 추가로 사용해 jack과 동일한 크기이 변수를 만들고 s안에 있는 글자를 하나씩 t로 복사하면 됩니다-! (정말 간단하네요)

#include<cs50.h>
#include<ctype.h> // toupper
#include<string.h> // strlen
#include<stdlib.h> //malloc
int main(void)
{
    char *s = get_string("s : ");
    char *t = malloc(strlen(s)+1); // 1은 널 종단 문자를 위해
    // 복사하는데 필요한 메모리 공간을 할당 
    // strlen(s)+1은 할당 받은 메모리의 크기를 의미

    for (int i=0,n =strlen(s); i<n+1 ; i++) { // 널 종단 문자까지 복사하기위해 n+1 까지
        t[i] = s[i];
    }

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

    printf("%s\n",s);
    printf("%s\n",t);

}

강의와 똑같이 해보았는데 malloc에서 문제가 생겼습니다.
help50을 이용해 문제의 원인을 확인해보니

강의할 때와 조금은 달라졌나봅니다.

malloc을 사용하기 위해서는 #include <stdlib.h>를 추가해주어야합니다.


결과 생각대로 잘 나옵니다.

#include<stdio.h>
#include<cs50.h>
#include<ctype.h> // toupper
#include<string.h> // strlen
#include<stdlib.h> //malloc
int main(void)
{
    char *s = get_string("s : ");
    char *t = malloc(strlen(s)+1); // 1은 널 종단 문자를 위해
    // 복사하는데 필요한 메모리 공간을 할당 
    // strlen(s)+1은 할당 받은 메모리의 크기를 의미
    strcpy(t,s); // s를 t로 
    t[0] = toupper(t[0]);
    printf("%s\n",s);
    printf("%s\n",t);

}

복잡한 for문 대신 누군가 쉽게 만들어놓은 함수가 있습니다.
strcpy

string 은 char *와 동일하다고 생각하면 됩니다!!



3. 메모리 할당과 해제

1) malloc : 할당한 메모리의 첫 바이트의 주소를 return
2) free : 할당 되었던 메모리를 다시 반환

free(t) : t가 malloc이 할당해준 메모리 주소라고 할 때 이를 통해 할당된 메모리를 해제 할 수 있습니다.

free를 해주지 않으면 메모리 용량 낭비가 되므로 사용하지 않는 메모리는 해제하는 것이 좋습니다.

이렇게 메모리 용량이 낭비되는 것을 메모리 누수라고 합니다.

메모리 관련 에러를 찾는 프로그램 : valgrind
프로그램을 실행할 때 앞에 적어주면 됩니다.

[ valgrind ./copy ]


#include<stdlib.h>

void f(void) {
	int *x = malloc(10*sizeof(int));
    x[10] = 0;
    free(x);
}

int main(void) {
	f();
    return 0;
}

sizeof는 괄호 안에 있는 자료형의 크기를 알려줍니다.
int는 4byte이므로 4*10 = 40byte의 메모리를 요청하는 것입니다.
malloc을 이용해 할당 받은 메모리의 시작주소를 x에 넣습니다.

10 size를 요청했으므로 0~9까지 접근할 수 있습니다.
그러나 여기서 x[10]을 접근하려고 합니다. 이는 버퍼 오버플로우입니다.
여기서 버퍼는 배열을 의미합니다.

버퍼 오버플로우는 할당하지 않은 메모리의 영역을 접근하려고 하는 것을 의미합니다.



4. 메모리 교환, 스택, 힙

1) nonswap.c

#include<stdio.h>
void swap(int a, int b);

int main(void)
{
	int x = 1;
    int y = 2;
    
    printf("x is %i,  y is %i\n",x,y);
    swap(x,y);
    printf("x is %i,  y is %i\n",x,y);
}

void swap(int a, int b )
{
	int tmp = a;
    a = b;
    b = tmp;
}

위 코드에서 swap함수는 swap에 실패합니다.
당연히 이 함수 내부에서는 swap이 잘 되지만 x,y의 값이 swap되는 것이 아니라
인수로 전달받은 x와 y의 복사본이 swap되는것이기 때문입니다.

이를 더 잘 이해하기 위해 메모리의 구조를 살펴봅시다.

<메모리>


가장 위에는 clang이 컴파일한 0,1로 이루어진 머신코드가 있습니다.
그 다음 전역변수가 저장됩니다.
그 다음 heap이라는 공간이 아래로 점점 내려가며 공간을 늘립니다.
heap은 메모리를 할당받을 수 있는 커다란 영역으로,
malloc을 호출하면 메모리를 여기서 갖고옵니다.

그 다음 메모리에 끝에 stack이라는 영역이 있습니다.
함수가 호출될 때 지역변수가 쌓이는 공간입니다.

만약 위와 같은 코드로 swap을 한다면
먼저 stack에 main함수의 스택 프레임이 쌓입니다.

main에서는 swap함수를 호출하니 stack에 swap함수의 스택 프레임이 쌓입니다.
이 swap 함수의 스택 프레임안에서 main의 x,y와는 개별로 a,b,tmp가 생기는 겁니다. 그리고 swap함수가 끝나면 없어집니다.

즉 복사본만 swap이 되고 원본 x,y를 변화시키지않는 것입니다.

이럴 때는 어떻게 해야할까요?

2) swap.c

(참조와 포인터는 동일한 의미)

main에서 x와 y의 값을 swap에게 전달하는 것이 아니라
x, y의 주소를 알려줘서 swap함수가 그 주소에 가서 값을 변경하게 만들면 됩니다!

<swap 잘 되는 코드 >

#include<stdio.h>
void swap(int *a, int *b);

int main(void)
{
	int x = 1;
    int y = 2;
    
    printf("x is %i,  y is %i\n",x,y);
    swap(&x,&y); // x와 y의 주소를 전달 
    printf("x is %i,  y is %i\n",x,y);
}

void swap(int *a, int *b ) //정수의 주소를 받아 a라고 부른다. * -> 포인터
{
	int tmp = *a; // * -> a가 가리키는 주소로 가라! 
    *a = *b;
    *b = tmp;
}

함수 swap의 인수에 복사본이 아닌 원본의 주소를 넘깁니다.
함수에서는 인수로 받은 주소를 저장할 수 있는 포인터로 받습니다.

그 포인터에는 주소가 저장되어있습니다.
*을 통해 해당 주소에 저장되어있는 값에 접근할 수 있습니다.

이렇게 코드를 만들면 잘 swap됩니다.

profile
개발자를 꿈꾸는 대학생

0개의 댓글