0x
를 붙여 뒤에 오는 문자가 16진수임을 알려준다.정수형 변수 n에 50이라는 값을 저장하고 출력하는 경우
→ n이라는 값은 int 타입이므로 4바이트 만큼의 자리를 차지함.
SourceCode
#include <stdio.h> int main(void) { int n = 50; printf("%p\n", &n); }
→ 메모리상 주소를 받기 위해 '&'
이라는 연산자 사용
SourceCode
#include <stdio.h> int main(void) { int n = 50; printf("%i\n", *&n); }
→ 반대로 '*'
를 사용하면 메모리 주소에 있는 실제 값을 얻을 수 있다 !
'CS50'을 16진수로 표현해볼까요?
① 2진수
SourceCode
#include <stdio.h> int main(void) { int n = 50; int *p = &n; printf("%p\n", p); // n의 주소값 printf("%i\n", *p); // n의 값(p가 가리키는 변수 값) }
- p라는 포인터 변수에 &n이라는 값(=n의 주소)을 저장
- int p에서 p앞의 *은 이 변수가 포인터
라는 의미
- int는 int 타입의 변수
를 가리킨다는 의미
- 변수 p가 메모리가 위와 같이 저장된다.
(이때 아래 그림처럼 p가 n을 가리키고 있다고 생각해도 됨)
포인터의 크기는 메모리의 크기와 어떤 관계가 있을까요?
→ 포인터 변수는 자료형과 상관없이 메모리 크기가 항상 같다.
'\0'
을 붙인다.SourceCode
1. string 자료형으로 "EMMA" 출력#include <cs50.h> #include <stdio.h> int main(void) { string s = "EMMA"; printf("%s\n", s); }
- char 포인터로 "EMMA" 출력
#include <stdio.h> int main(void) { char *s = "EMMA"; printf("%s\n", s); }
- 2번 코드의 char *s에서 s라는 변수는 문자에 대한 포인터가 되고, "EMMA"라는 문자열의 가장 첫 번째 값을 저장하기 때문
string 자료형을 정의해서 사용하면 어떤 장점이 있을까요?
→ 문자열을 쉽게 출력할 수 있다.
SourceCode
#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[0] : “E”의 주소값, &s[1]은 “M”의 주소값, &s[2]은 “M”의 주소값, &s[3]은 “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"); } }
→ 문자열이 저장된 변수로 비교하면 저장된 주소가 다르기 때문에 다르다.
∴ 문자열 하나씩 비교해야한다 !
문자열을 비교하는 코드는 어떻게 작성해야 할까요?
① 문자열의 길이가 다르면 다르다고 판단한다.
② 문자열의 길이가 같다면 문자열의 길이만큼 돌면서 해당 위치의 문자가 같은지 판단한다.
SourceCode
#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); }
→ s와 t 모두 "Emma"라고 출력됨
(s라는 변수에는 문자열이 있는 메모리의 주소가 저장되기 때문)
→ string s는 char *s와 동일한 의미
SourceCode
메모리 할당 함수로 복사하기#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); for (int i = 0, n = strlen(s); i < n + 1; i++) { t[i] = s[i]; } t[0] = toupper(t[0]); printf("s: %s\n", s); printf("t: %s\n", t); }
→ 위의 코드와 다른 점은 malloc이라는 함수로 t를 정의한다.
→ s 문자열의 길이에 1을 더한 만큼 메모리 할당 !
배운 바와 같이 메모리 할당을 통해 문자열을 복사하지 않고, 단순히 문자열의 주소만 복사했을 때는 어떤 문제가 생길까요?
→ 값을 바꾸게 되면 전체가 바뀌게 된다. (의도하지 않은 값 변경 발생)
free
라는 함수로 메모리를 해제해줘야한다 !SourceCode
#include <stdlib.h> void f(void) { int *x = malloc(10 * sizeof(int)); x[10] = 0; } int main(void) { f(); return 0; }
→ x에 int형 크기(4바이트) * 10만큼의 크기를 할당(40바이트)
→ x의 10번째 값으로 0을 할당
→ main에서 함수 f 호출, valgrind로 검사할 시 버퍼 오버플로우와 메모리 누수 확인 가능
→ x[10] = 0; 으로 인해 버퍼 오버플로우 발생
→ 메모리 누수는 x라는 포인터로 할당한 메모리를 해제하기 위해 free(x)라는 코드 추가하여 해결 !
제한된 메모리를 가지고 프로그래밍 할 때 메모리를 해제하지 않으면 어떤 문제가 발생할 수 있을까요?
→ 메모리에 저장된 값은 쓰레기 값으로 남게되고 결국 메모리를 낭비하게 됩니다.
SourceCode
#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의 값을 복제한 것이기 때문)
(= 서로 다른 메모리 주소에 저장됨)
SourceCode
#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; }
→ 위의 코드에서 a와 b를 x와 y를 가리키는 포인터로 지정하면 된다 !
메모리 영역을 다양하게 나누는 이유는 무엇일까요?
→ 메모리를 효율적으로 접근, 사용, 관리하기 위해서
SourceCode
[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); }
→ scanf : 형식 지정자에 해당되는 값을 입력받아 저장하는 함수
(scanf에 s가 아닌&s
로 입력해줘야한다 !)[파일 쓰기]
#include <cs50.h> #include <stdio.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); }
→ fopen으로 파일을 FILE이라는 자료형으로 불러올 수 있음.
→ fopen(파일의 이름, 모드)
(모드 - r은 읽기, w는 쓰기, a는 덧붙이기)
→ fprintf로 파일에 직접 내용을 출력할 수 있음.
→ 작업이 끝난 후fclose
로 종료해줘야 함.
get_long, get_float, get_char도 비슷한 방식으로 직접 구현할 수 있을까요?
→ get_int에서 %i를 %ld, %f, %c로 바꾸면 된다 !
SourceCode
파일 내용을 읽고 파일의 형식이 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) { printf("Maybe\n"); } else { printf("No\n"); } fclose(file); }
→ 파일의 이름을 입력으로 받음.
→ if(argc != 2)인 경우 1을 return하고 종료
→ if(argc == 2)인 경우 그대로 진행
→ 입력받은 파일명(argv[1])을읽기(r)
모드로 불러옴
→ 파일이 제대로 열리지 않으면 NULL을 return, 제대로 쓸 수 없다면 1 return
→ 파일이 잘 열렸다면 계속 진행
→ 크기가 3인 문자 배열을 만들어 fread 함수로 파일에서 첫 3바이트를 읽어옴
*fread(배열, 읽을 바이트 수, 읽을 횟수, 읽을 파일)
→ 마지막으로 읽은 바이트가 각각 0xFF, 0xD8, 0xFF인지 확인
JPEG 외에 다른 파일 형식도 그 형식임을 알려주는 약속이 있을까요?
→ 있을 것 같다.