메모리 주소
- 16진수에서 10부터는 A, B, C...로 쓴다.
- 16진수는 모든 숫자앞에 0x를 붙인다
address
#include <stdio.h>
int main(void)
{
int n = 50;
printf("%p\n", &n);
}
- &을 사용하면 변수의 주소를 알려준다. 주소의 형식 지정자는 %p다
- *는 &과 반대로 메모리의 주소에 해당하는 변수를 알려준다.
- 출력하면 16진수의 메모리 주소가 나온다.
- 메모리의 주소를 포인터라고 한다.
포인터
#include <stdio.h>
int main(void)
{
int n = 50;
int *p = &n;
printf("%i\n", *p);
}
- 포인터는 *로 표시하고 앞에 int는 포인터가 가리키는 값이 int라는 뜻이다.
- 포인터 변수 역시 메모리에 저장된다. 포인터는 추상화에 사용된다.
문자열
- 문자열을 s라는 변수에 선언할때 s가 담고 있는 것은 문자열 첫 글자의 포인터다.
- 컴퓨터는 문자열 첫 글자의 주소만 알면 널 문자를 만날때까지 루프를 돌면서 끝을 알아낸다.
- 문자열의 자료형은 다음과 같다.
typedef char *string;
#include <stdio.h>
int main(void)
{
char *s = "EMMA";
printf("%p\n", s);
printf("%p\n", &s[0]);
}
문자열 비교
#include <stdio.h>
int main(void)
{
char *s = "EMMA";
printf("%c\n", *s);
printf("%c\n", *(s+1));
printf("%c\n", *(s+2));
printf("%c\n", *(s+3));
}
- 각 글자가 주소 1차이로 담겨 있다.
- prinf는 %s 형식 지정자를 사용하면 널 문자가 나올때까지 주소를 옮겨가며 char를 출력한다.
두 문자열 비교하기
#include <stdio.h>
#include <cs50.h>
int main(void)
{
string s = get_string("s: ");
string t = get_string("t: ");
printf("%p\n", s);
printf("%p\n", t);
}
- string s와 string t는 메모리에 담겨 있는 주소를 의미하므로 서로 담겨있는 메모리가 달라서 문자열이 같아도 ==만으로 비교하면 다르다고 표시한다.
- 문자열 s와 t는 *s == *t로 비교한다.
문자열 복사
#include <stdio.h>
#include <cs50.h>
#include <ctype.h>
int main(void)
{
string s = get_string("s: ");
string t = s;
t[0] = toupper(t[0]);
printf("%s\n", s);
printf("%s\n", t);
}
- s와 t의 문자열이 모두 첫글자가 대문자가 된다.
- t 변수가 s의 포인터를 복사해서 같은 메모리를 가리키기 때문이다.
#include <stdio.h>
#include <cs50.h>
#include <ctype.h>
#include <string.h>
int main(void)
{
char *s = get_string("s: ");
char *t = malloc(strlen(s) + 1);
for (int i = 0, n = strlen(s); i <= n; i++)
{
t[i] = s[i];
}
t[0] = toupper(t[0]);
printf("%s\n", s);
printf("%s\n", t);
}
- 문자열을 복사하려면 malloc을 이용해서 메모리를 할당하고 한 글자씩 복사한다.
- strcpy() 함수로 문자열을 복사할 수도 있다.
메모리 할당과 해제
- 메모리를 할당하고 free를 통해 메모리를 해제해줘야 메모리를 자유롭게 쓸 수 있게 된다.
#include <stdio.h>
#include <cs50.h>
#include <ctype.h>
#include <string.h>
int main(void)
{
char *s = get_string("s: ");
char *t = malloc(strlen(s) + 1);
for (int i = 0, n = strlen(s); i <= n; i++)
{
t[i] = s[i];
}
t[0] = toupper(t[0]);
printf("%s\n", s);
printf("%s\n", t);
free(t);
}
버퍼 오버플로우
#include <stdio.h>
void f(void)
{
int *x = malloc(10 * sizeof(int));
x[10] = 0;
}
int main(void)
{
f();
return 0;
}
- 할당된 메모리의 크기가 40바이트인데 x[10]이라고 쓰면 할당된 메모리를 넘어서 버퍼 오버플로우가 일어난다.
- x[9], free(x)로 코드를 고쳐야 한다.
메모리 교환, 스택, 힙
메모리 교환
- 메모리를 교환하기 위해선 임시공간이 있어야 한다.
- 그러나 아래와 같은 함수는 동작하지 않는다.
void swap(int a, int b)
{
int tmp = a;
a = b;
b = tmp;
}
- 함수로 인자를 전달할때는 변수 자체가 아니라 변수의 복사본이 전달되기 때문이다
- 프로그램이 실행될 때, 메모리 가장 위에는 0과 1로 컴파일된 머신코드가, 그 아래엔 전역변수를 저장하고 그 아래에는 힙이라는 메모리 영역이 있다.
- 힙은 메모리를 할당받을 수 있는 큰 영역이다. 예를 들어 malloc을 사용하면 힙에서 메모리를 할당한다.
- 힙 아래에는 함수의 지역변수들이 할당되는 스택이라는 공간이 있다.
- 변수를 함수에 그대로 넣지 않고 두 변수의 포인터를 이용해서 변수에 직접 접속하도록 한다.
void swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
swap(&x, &y);
파일 쓰기
- 스택 오버플로우: 무한히 재귀하는 함수를 실행할 경우 스택 부분의 메모리가 넘쳐 스택 오버플로우를 일으킨다.
- 힙 오버플로우: 계속 malloc을 사용해 너무 많은 메모리를 할당하면 힙 오버플로우를 일으켜서 메모리를 덮어쓰게 된다.
get int 구현
#include <stdio.h>
int main(void)
{
int x;
printf("x: ");
scanf("%i", &x);
printf("x: %i\n", x)
}
get string
#include <stdio.h>
int main(void)
{
char s[5];
printf("s: ");
scanf("%s", s);
printf("s: %s\n", s);
}
- 배열은 포인터와 같다. clang은 배열을 포인터와 같이 다뤄서 배열의 첫 바이트 주소를 넘겨준다.
파일에 저장하기
#include <stdio.h>
#include <cs50.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);
}
파일 읽기
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];
fread(bytes, 3, 1, file);
if (bytes[0] == 0xff && bytes[1] == 0xd8 && bytes[2] == 0xff)
{
prinf("Maybe\n");
}
else
{
printf("Not JPEG\n")
}
}
출처: 부스트코스 모두의 컴퓨터과학