문자열 비교
#include <stdio.h>
int main(void)
{
char *s = "EMMA";
printf("%p\n", s);
}
이 코드를 실행하면 s
라는 포인터의 값인 "EMMA" 문자열의 첫 문자 'E'에 해당하는 메모리 주소가 출력된다.
그렇다면 아래 코드들은 무엇을 출력할까?
printf("%p\n", &s[0]);
printf("%p\n", &s[1]);
printf("%p\n", &s[2]);
printf("%p\n", &s[3]);
s
가 가리키는 곳을 시작으로 "EMMA"라는 문자열로 이루어진 문자의 배열이 있으니 각각 s
문자열의 첫 번째, 두 번째, 세 번째, 네 번째 문자의 주소값을 출력하게 된다.
자세히 들여다보면 &s[0]
는 'E'의 주소값을, &s[1]
은 'M'의 주소값을, &s[2]
는 또 다른 'M'의 주소값을, &s[3]
는 'A'의 주소값을 의미한다. 문자열은 첫 번째 문자를 시작으로 메모리상에서 연속적으로 저장되어 있다.
가장 첫 번째 문자에 해당하는 주소값을 하나씩 증가시키면 바로 옆에 있는 문자의 값을 출력할 수 있다.
따라서 아래 코드는 'E', 'M', 'M', 'A'를 순서대로 출력한다.
printf("%c\n", *s);
printf("%c\n", *(s + 1));
printf("%c\n", *(s + 2));
printf("%c\n", *(s + 3));
문자열을 비교할 때도 문자열이 저장된 변수를 바로 비교하면 그 변수가 저장되어 있는 주소가 다르기 때문에 다르다는 결과가 나온다.
정확한 비교를 위해서는 실제 문자열이 저장된 곳으로 이동해서 각 문자를 하나씩 비교해야 한다.
아래 코드는 사용자가 입력한 두 문자열을 비교하지만 주소를 비교하기 때문에 항상 다르다고 출력된다.
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// 사용자로부터 s와 t 두 개의 문자열 입력받아 저장
string s = get_string("s: ");
string t = get_string("t: ");
// 두 문자열을 비교 (각 문자들을 비교)
if (s == t)
{
printf("Same\n");
}
else
{
printf("Different\n");
}
}
문자열 비교를 정확히 하려면 strcmp
함수를 사용해야 한다.
strcmp
함수는 두 문자열의 각 문자를 하나씩 비교하여 같으면 0을 반환하고 다르면 0이 아닌 값을 반환한다.
#include <cs50.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
// 사용자로부터 s와 t 두 개의 문자열 입력받아 저장
string s = get_string("s: ");
string t = get_string("t: ");
// 두 문자열을 비교 (strcmp 함수 사용)
if (strcmp(s, t) == 0)
{
printf("Same\n");
}
else
{
printf("Different\n");
}
}
이 코드는 사용자가 입력한 두 문자열을 정확히 비교해 같으면 "Same"을, 다르면 "Different"를 출력한다.
문자열 복사
문자열을 복사하기 위해 아래 코드를 실행하면 어떻게 될까?
#include <cs50.h>
#include <ctype.h>
#include <stdio.h>
int main(void)
{
string s = get_string("s: ");
string t = s;
t[0] = toupper(t[0]);
printf("s: %s\\n", s);
printf("t: %s\\n", t);
}
사용자에게 입력값을 받아 string s
에 저장하고 string t
를 s
로 정의한다.
그리고 t
의 첫 번째 문자를 toupper
함수를 이용하여 대문자로 바꾼다면 s
와 t
는 각각 어떻게 출력될까?
입력값으로 "emma"를 주게 되면 예상과는 다르게 s
와 t
모두 "Emma"라고 출력된다.
그 이유는 s
라는 변수에는 "emma"라는 문자열이 아닌 그 문자열이 있는 메모리의 주소가 저장되기 때문이다.
string s
는 char *s
와 동일한 의미라는 점을 떠올려보면 된다.
따라서 t
도 s
와 동일한 주소를 가리키고 있고 t
를 통한 수정은 s
에도 그대로 반영된다.
그렇다면 두 문자열을 실제로 메모리상에서 복사하려면 어떻게 해야 할까?
아래 코드와 같이 메모리 할당 함수를 사용하면 된다.
#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);
if (t == NULL)
{
return 1;
}
for (int i = 0, n = strlen(s); i <= n; i++)
{
t[i] = s[i];
}
t[0] = toupper(t[0]);
printf("s: %s\n", s);
printf("t: %s\n", t);
free(t);
return 0;
}
위 코드와 다른 점은 malloc
이라는 함수를 이용해 t
를 정의한다는 것이다. malloc
함수는 정해진 크기만큼 메모리를 할당하는 함수이다.
s
문자열의 길이에 널 종단 문자(\0
)에 해당하는 1을 더한 만큼 메모리를 할당한다. 그리고 루프를 돌면서 s
문자열 배열에 있는 문자를 하나하나 t
배열에 복사해준다.
이 코드를 컴파일 후 실행시키고 입력값으로 "emma"를 주면, 예상대로 s
는 "emma"가, t
는 "Emma"가 출력된다. 성공적으로 복사가 된 것을 확인할 수 있다.
문자열을 복사할 때 strcpy
함수를 사용해 좀 더 간단하게 문자열을 복사할 수 있다.
#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);
if (t == NULL)
{
return 1;
}
strcpy(t, s);
t[0] = toupper(t[0]);
printf("s: %s\n", s);
printf("t: %s\n", t);
free(t);
return 0;
}
이 코드에서는 strcpy
함수를 이용해 s
문자열을 t
에 복사하고 있다.
strcpy
함수는 문자열을 복사할 때 편리하게 사용할 수 있는 표준 라이브러리 함수이다.
바로 다음에 이야기 할 메모리 해제도 잠시 짚고 넘어가보자.
메모리를 할당한 후에는 누수를 방지하기 위해 반드시 free
함수를 호출해 할당된 메모리를 해제해야 한다. 밑에서 더 자세히 알아보자.
메모리 해제
malloc
함수를 이용하여 메모리를 할당한 후에는 반드시 free
함수를 이용하여 메모리를 해제해야 한다.
그렇지 않은 경우 할당된 메모리는 쓰레기 값으로 남아 메모리 용량의 낭비가 발생하게 된다.
이러한 현상을 '메모리 누수'라고 일컫는다.
valgrind
라는 프로그램을 사용하면 우리가 작성한 코드에서 메모리와 관련된 문제가 있는지를 쉽게 확인할 수 있다.
예를 들어 help50 valgrind ./filename
와 같은 명령어를 사용하면 filename
파일에 대한 valgrind
의 검사 내용을 확인할 수 있다.
아래와 같은 코드가 있다고 가정해보자.
#include <stdlib.h>
void f(void)
{
int *x = malloc(10 * sizeof(int));
x[10] = 0;
}
int main(void)
{
f();
return 0;
}
f
함수를 살펴보면 먼저 포인터 x
에는 int
형의 크기(4바이트)에 10배에 해당하는 크기의 40바이트를 할당한다. 그리고 x
의 10번째 값으로 0을 할당한다.
이후 main
함수에서 f
를 실행하는데 이 코드를 valgrind
로 검사해보면 버퍼 오버플로우와 메모리 누수 두 가지 에러를 확인할 수 있다.
먼저 버퍼 오버플로우는 x[10] = 0;
코드로 인해 발생한다. 10개의 int
형 배열을 만들었는데 배열의 인덱스가 0부터 시작한다는 점을 감안하면 인덱스 10은 11번째 요소에 접근하겠다는 의미다.
이는 정의되지 않은 것이기 때문에 버퍼 오버플로우가 발생하는 것이다.
따라서 이 오류는 0에서 9 사이의 인덱스를 사용하면 해결할 수 있다.
또한 메모리 누수는 x
라는 포인터를 통해 할당한 메모리를 해제하지 않았기 때문에 발생한다.
이를 해결하기 위해 free(x);
라는 코드를 추가해주면 된다.
#include <stdlib.h>
void f(void)
{
int *x = malloc(10 * sizeof(int));
if (x == NULL)
{
return;
}
x[9] = 0;
free(x);
}
int main(void)
{
f();
return 0;
}
수정된 코드에서 x[9] = 0;
으로 인덱스를 0에서 9 사이로 조정했고 free(x);
를 추가해 메모리를 해제했다.
이렇게 하면 버퍼 오버플로우와 메모리 누수를 모두 해결할 수 있다.
C 언어에서는 메모리를 직접 관리해야 하고 malloc
으로 할당한 메모리는 반드시 free
로 해제해야 한다는 점을 명심해야 한다.
또한 valgrind
같은 도구를 활용해 메모리 관련 문제를 사전에 점검하는 것이 중요하다.
References
'CSE > CS 기초' 카테고리의 다른 글
[CS50] 메모리 - 파일 쓰기 (0) | 2024.06.17 |
---|---|
[CS50] 메모리 - 메모리 교환, 스택, 힙 (0) | 2024.06.17 |
[CS50] 메모리 - 문자열 (0) | 2024.06.17 |
[CS50] 메모리 - 포인터 (0) | 2024.06.14 |
[CS50] 메모리 - 메모리 주소 (0) | 2024.06.14 |
컴퓨터 전공 관련, 프론트엔드 개발 지식들을 공유합니다. React, Javascript를 다룰 줄 알며 요즘에는 Typescript에도 관심이 생겨 공부하고 있습니다. 서로 소통하면서 프로젝트 하는 것을 즐기며 많은 대외활동으로 개발 능력과 소프트 스킬을 다듬어나가고 있습니다.
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!