[CS50 2019] 4. Memory
모두를 위한 컴퓨터 과학
1. 포인터
- 메모리 주소를 저장하는 변수로, 프로그램이 메모리 상의 위치를 추적하고 조작하는 데 사용
- 다른 변수, 배열, 구조체 또는 함수 등의 주소를 저장하고 해당 메모리 위치에 직접 접근할 수 있게 해줌
- 포인터를 선언할 때는 변수의 자료형과 포인터 연산자
*
를 사용 - 또한, 변수의 메모리 주소를 받아올 때에는 연산자
&
을 사용
#include <stdio.h>
int main(void)
{
int n = 50;
int *p = &n;
printf("%p\n", p);
printf("%i\n", *p);
}
>>
0x7ff7bfeff1ec
50
- 정수형 변수
n
에는 50이라는 값이 저장됨*p
라는 포인터 변수에는&n
이라는 값, 즉 변수 n의 주소를 저장함
2. 문자열
char 포인터로 문자열 출력 가능
#include <stdio.h>
int main(void)
{
char *s = "EMMA";
printf("%s\n", s);
}
EMMA
char *s
에서s
라는 변수는 문자에 대한 포인터를 저장- 더 상세히는 문자열의 가장 첫번째 문자, 즉 s[0]의 주소를 저장
printf
함수는 포인터 s가 가리키는 메모리 주소부터 널 종료 문자를 만날 때까지 문자를 출력
printf("%c\n", *s); // "E"
printf("%c\n", *(s + 1)); // "M"
printf("%c\n", *(s + 2)); // "M"
printf("%c\n", *(s + 3)); // "A"
- 문자열은 첫번째 문자를 시작으로 메모리상에서 바로 옆에 저장되어 있다.
- 즉, 가장 첫번째 문자에 해당하는 주소값을 하나씩 증가시키면 바로 옆에 있는 문자의 값을 출력할 수 있다.
문자열 비교
if (s == t)
{
printf("Same\n");
}
else
{
printf("Different\n");
}
- C 언어에서 단순히
==
연산자로 문자열을 비교하면 제대로 동작하지 않는다. - 왜냐하면 문자열이 저장된 변수를 바로 비교하게 되면 두 문자열의 주소를 비교하기 때문이다.
문자열 복사
printf("Enter a string: ");
gets(s);
char *t = s;
- 문자열 s를 문자열 t에 복사할 때, 단순히
=
연산자로 복사하면 메모리의 주소가 저장되기 때문에 t와 s는 동일한 주소를 가리키게 되고 t를 통한 수정은 s에도 그대로 반영된다. - 따라서
malloc
함수를 사용하여 메모리를 할당해준 다음 루프를 돌면서 문자 하나 하나를 복사해줘야 한다.
3. 메모리
메모리 할당
✔ malloc
함수
void* malloc(size_t size);
- 메모리 동적 할당을 위해 사용되는 함수
- 특정 크기의 메모리 블록을 할당하고, 해당 메모리 블록의 시작 주소를 반환
메모리 해제
✔ free
함수
void free(void *ptr)
malloc
함수를 이용해 메모리를 할당한 후에는free
함수로 메모리를 해제해줘야 함- 그렇지 않은 경우 메모리에 저장한 값은 쓰레기 값으로 남게 되어 메모리 용량의 낭비가 발생 (메모리 누수)
메모리 교환
#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
함수가 제대로 동작하지 않는다. - a와 b는 함수에 전달된 변수들(x, y)의 값을 복제하여 저장 (서로 다른 메모리 주소에 저장)
- 즉, a와 b를 바꾸는 것은 x와 y를 바꾸는 것에 아무런 영향도 미치지 않음
- 따라서 제대로 동작하게 하려면, a와 b를 각각 x와 y를 가리키는 포인터로 지정해야 함
✔ 수정된 코드
#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;
}
메모리 영역
머신 코드 (Machine Code)
- 프로그램의 기계 코드가 저장되는 공간
- CPU가 실행할 명령어들이 저장되어 있으며, 프로그램이 실행될 때 변경되지 않는 읽기 전용 메모리
전역 영역 (Globals)
- 전역 변수와 정적 변수가 할당되는 영역
- 프로그램 시작 시에 할당되고 프로그램 종료 시까지 유지
- 초기화된 전역 변수는 초기값으로 초기화되며, 초기화되지 않은 전역 변수는 0 또는 null 값으로 초기화
힙 (Heap)
- 동적으로 할당된 메모리가 저장되는 영역
- 프로그램이 실행 중에 필요에 따라 메모리를 동적으로 할당하고 해제할 수 있음 (
malloc
,free
또는new
,delete
를 통해 수행)- 힙은 사용자가 메모리를 직접 관리할 수 있는 영역으로, 크기가 동적으로 변할 수 있음
스택 (Stack)
- 지역 변수 및 함수 호출과 관련된 정보를 저장하는 영역
- 함수가 호출될 때 지역 변수, 함수의 매개변수, 함수의 반환 주소 등이 스택에 저장되며, 함수가 종료되면 정보가 스택에서 제거
- 스택은 LIFO(후입선출) 구조를 가지며, 메모리 할당 및 해제가 자동으로 이루어짐
메모리 오버플로우
버퍼 오버플로우 (Buffer Overflow)
- 가장 일반적이고 잘 알려진 메모리 오버플로우 형태
- 버퍼의 크기를 넘어서 데이터가 복사되거나 쓰여지는 상황
- 주로 배열과 같은 자료 구조에서 발생
힙 오버플로우 (Heap Overflow)
- 동적으로 할당된 메모리인 힙(Heap)에서 발생하는 오버플로우
malloc
이나free
와 같은 함수를 사용할 때, 할당된 메모리 영역을 벗어나 데이터를 쓰려고 할 때 발생
스택 오버플로우 (Stack Overflow)
- 스택 메모리에서 발생하는 오버플로우
- 주로 재귀 호출이 깊게 이어지거나 지역 변수가 많아지면서 스택의 한계를 초과할 때 발생
4. 파일 입출력 함수
✔ fopen
: 파일 열기
FILE* fopen(const char* filename, const char* mode);
- 지정된 파일을 열고 파일에 대한 포인터를 반환
- 첫번째 인자는 파일의 이름, 두번째 인자는 모드로 r은 읽기, w는 쓰기, a는 덧붙이기를 의미
✔ fclose
: 파일 닫기
int fclose(FILE* stream);
- 열려 있는 파일을 닫음
- 함수의 인자는 닫을 파일에 대한 포인터
✔ fprintf
: 파일에 데이터 출력
int fprintf(FILE* stream, const char* format, ...);
- 지정된 파일에서 서식화된 출력 수행
- 첫번째 인자는 출력을 할 파일에 대한 포인터, 두번째 인자는 출력 형식을 지정하는 서식 문자열
✔ fread
: 파일에서 읽어오기
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
- 지정된 파일로부터 특정 크기의 데이터를 지정된 개수만큼 읽어와서 메모리에 저장
- 각 인자는 읽어온 데이터를 저장할 배열, 읽을 바이트 수, 읽을 횟수, 데이터를 읽어올 파일을 의미
예제 코드
파일을 읽고 JPEG 파일인지를 검사하는 프로그램
#include <stdio.h>
int main(int argc, char *argv[]) // 파일의 이름을 입력받음
{
if (argc != 2) // 인자가 제대로 입력되지 않았다면 프로그램 종료
{
return 1;
}
FILE *file = fopen(argv[1], "r"); // 읽기 모드로 파일명 받아서 파일 열기
if (file == NULL) // 파일이 제대로 열리지 않으면 프로그램 종료
{
return 1;
}
unsigned char bytes[3]; // 크기가 3인 char 배열 선언
fread(bytes, 3, 1, file); // 파일에서 첫 3byte 읽어오기
// JPEG 파일의 파일 시그니처 검사
if (bytes[0] == 0xff && bytes[1] == 0xd8 && bytes[2] == 0xff)
{
printf("Maybe\n");
}
else
{
printf("No\n");
}
fclose(file); // 파일 닫기
}