#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을 더해 한바이트 이후의 문자 즉 첫번째 원소에 접근하게 합니다.
#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은 입력받은 문자열을 저장할 메모리 공간의 첫바이트 주소를 반환합니다.]
문자를 입력받아 첫번째 문자가 대문자로 변경한 문자열을 출력하는 코드를 만들어봅시다.
#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 *와 동일하다고 생각하면 됩니다!!
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]을 접근하려고 합니다. 이는 버퍼 오버플로우입니다.
여기서 버퍼는 배열을 의미합니다.
버퍼 오버플로우는 할당하지 않은 메모리의 영역을 접근하려고 하는 것을 의미합니다.
#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를 변화시키지않는 것입니다.
이럴 때는 어떻게 해야할까요?
(참조와 포인터는 동일한 의미)
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됩니다.