42서울 7기 라피신을 대비하여 정리한 CS50 2019 과정을 간략히 복기하여 재업로드한 내용입니다.
#include <stdio.h>
const int N = 3;
int main(void)
{
// 점수 배열 선언 및 값 저장
int scores[N];
scores[0] = 72;
scores[1] = 73;
scores[2] = 33;
// 평균 점수 출력
printf("Average: %i\n", (scores[0] + scores[1] + scores[2]) / N);
}
N
으로 고정된 정수 배열 scores
를 선언 및 초기화했다.#include <stdio.h>
float average(int length, int array[]);
int main(void)
{
// 사용자로부터 점수의 갯수 입력
int n = get_int("Scores: ");
// 점수 배열 선언 및 사용자로부터 값 입력
int scores[n];
for (int i = 0; i < n; i++)
{
scores[i] = get_int("Score %i: ", i + 1);
}
// 평균 출력
printf("Average: %.1f\n", average(n, scores));
}
n
을 사전에 선언하지 않고, 사용자가 입력한 임의의 정수 값에 따라 결정되도록 했다.char *s = “HI!”;
s
가 정의되어 있다고 생각해 보자. \0
을 널 종단 문자 또는 NUL
문자라고 한다. (NULL
이 아니다)char
자료형의 0 즉 문자 0
(아스키 코드 0x30
) 과는 구별된다.char *names[4];
names[0] = "EMMA";
names[1] = "RODRIGO";
names[2] = "BRIAN";
names[3] = "DAVID";
printf("%s\n", names[0]);
printf("%c%c%c%c\n", names[0][0], names[0][1], names[0][2], names[0][3]);
NUL
문자가 들어가야 하며, 문자열을 읽어들일 때 NUL
문자를 만나기 전까지를 하나의 문자열로 간주한다.*
과 &
#include <stdio.h>
int main(void)
{
int n = 50;
printf("%p\n", &n);
printf("%i\n", *&n);
}
&
연산자를 사용한다.*
를 사용하면 그 메모리 주소에 있는 실제 값을 얻을 수 있다.*&
과 같이 두 연산자가 연속되면 상쇄되어 아무 연산자도 붙이지 않은 것과 같다.#include <stdio.h>
int main(void)
{
int n = 50;
int *p = &n;
printf("%p\n", p);
printf("%i\n", *p);
}
int *p
p
바로 앞의 *
는 이 변수가 포인터라는 의미int **p
처럼 생긴 포인터라도 마찬가지다. p
바로 앞의 *
만 이 변수가 포인터라는 의미를 갖고, 남겨진 int *
는 해당 타입의 변수를 가리킨다는 뜻이다.int
는 이 포인터가 int
타입의 변수를 가리킨다는 의미int *
, double *
, char *
의 크기는 모두 4바이트이다.int *
, double *
, char *
의 크기가 모두 8바이트다.char *s = “EMMA”;
s[0]
, s[1]
, s[2]
와 같이 하나의 문자가 배열의 한 부분을 나타낸다.s
는 결국 이러한 문자열을 가리키는 포인터가 된다.0x123
에 있는 s[0]
를 가리키게 된다.char *
으로 선언된다.char
형 변수 한 개를 가리키는 포인터인 것이다.#include <stdio.h>
int main(void)
{
char *s = "EMMA";
// 배열 각 요소의 주소값을 출력
printf("%p\n", &s[0]);
printf("%p\n", &s[1]);
printf("%p\n", &s[2]);
printf("%p\n", &s[3]);
// 배열 각 요소에 저장된 값을 출력
printf("%c\n", *s);
printf("%c\n", *(s+1));
printf("%c\n", *(s+2));
printf("%c\n", *(s+3));
}
&
연산자를 사용하면 해당 값이 저장되어 있는 주소를 알 수 있다.*
연산자를 사용하면 해당 주소에 저장되어 있는 값을 알 수 있다.p[n] == *(p + n)
이다.malloc()
과 free()
malloc()
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
를 선언해 s
로 초기화했다.t
의 첫 번째 문자를 toupper
함수를 이용하여 대문자로 바꿨다.t
뿐만 아니라 s
도 “Emma”라고 출력된다.s
라는 변수에는 emma
라는 문자열이 아닌 그 문자열이 있는 메모리의 주소가 저장되기 때문이다.e
의 주소다.s
에 저장된 값(= e
의 주소)으로 초기화된 t
도 s
와 동일한 주소를 가리키게 됐고, t
를 통한 수정은 s
에도 그대로 반영된다.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
를 초기화하면 된다.malloc()
: 정해진 크기 만큼 메모리를 할당하는 함수t
는 s
와 아예 다른 메모리 공간을 가리키고 있으므로, 루프를 돌면서 s
문자열에 있는 문자 하나 하나를 t
배열에 복사해주면 값은 동일하지만 각각 별도로 존재하는 두 문자열을 얻을 수 있다.free()
malloc()
함수를 이용하여 메모리를 할당한 후에는 free()
함수를 이용하여 메모리를 해제해줘야 한다.malloc()
함수는 힙(heap)
부분의 메모리를 사용하는데, 이 힙 영역은 프로그래머가 직접 공간을 할당, 해제하는 공간이므로 명시적으로 해제해주지 않으면 프로그램이 종료되어도 해당 메모리 공간을 다시 사용할 수가 없다.머신 코드
영역에는 프로그램이 실행될 때 그 프로그램이 컴파일된 바이너리가 저장된다.글로벌
영역에는 프로그램 안에서 저장된 전역 변수가 저장된다.힙
영역에는 malloc
으로 할당된 메모리의 데이터가 저장된다.힙
영역에서는 malloc
에 의해 메모리가 더 할당될수록, 점점 사용하는 메모리의 범위가 아래로 늘어난다.스택
에는 프로그램 내의 함수와 관련된 것들이 저장된다.스택
영역에서는 함수가 더 많이 호출 될수록 사용하는 메모리의 범위가 점점 위로 늘어난다.힙
또는 스택
영역이 이렇게 점점 늘어나다 보면, 제한된 메모리 용량 하에서는 기존의 값을 침범하는 상황도 발생할 것이다.#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;
}
main
함수의 변수 x
, y
는 swap
함수를 거치지만 결과적으로 각 변수의 값은 교환되지 않을 것이다.main
함수의 변수와 swap
함수의 변수는 각각 메모리상의 서로 다른 위치에 저장되어 있기 때문이다.swap
함수는 자신의 매개변수 a
, b
에 각각 x
, y
의 값 을 복사해 저장했을 뿐, 그 변수들을 직접적으로 조작하고 있는 것이 아니다.a
와 b
를 각각 x
와 y
를 가리키는 포인터로 지정해야 한다.스택
을 사용하는 예시로는, 사용자로부터 입력된 문자열이나 정수 값을 받아오는 함수를 들 수 있다.#include <stdio.h>
int main(void)
{
char s[5];
printf("s: ");
scanf("%s", s);
printf("s: %s\n", s);
}
scanf
는 사용자로부터 형식 지정자에 해당되는 값을 입력받아 저장하는 함수다.scanf
함수의 변수가 실제로 저장된 주소로 찾아가서, 사용자가 입력한 값을 저장하도록 하기 위함이다.#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
자료형으로 불러올 수 있다.
FILE
구조체는 스트림의 상태에 대한 정보를 저장하고, 파일 함수에서 사용한다. FILE
구조체는 stdio.h
헤더 파일에 정의된 typedef
이름이다.fopen
함수의 첫번째 인자는 파일의 이름, 두번째 인자는 모드로 r
은 읽기, w
는 쓰기, a
는 덧붙이기를 의미한다.
fprintf()
는 printf()
와 동일하게 stdio.h
헤더에 포함된 함수로, printf()
와는 값을 출력할 위치 가 다르다.
printf()
는 화면 출력, 즉 표준 출력 함수다.fprintf()
는 데이터를 형식에 맞추어 스트림(파일)에 쓴다. 따라서 첫 번째 인자로 파일구조체 포인터 (FILE *
) 이 온다.#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);
}
fread
함수를 이용해서 파일에서 첫 3바이트를 읽어왔다.fread(배열, 읽을 바이트 수, 읽을 횟수, 읽을 파일);