과제의 목표
//test.txt
Hello every one~~
My name is Hyson
Plain Text
복사
위와 같은 파일이 있을때, 우리는 test.txt를 open함수를 통해서 열게 되고 open함수를 통해 얻게된 fd값을 인자값으로 받아서 \n 을 만나기 전까지의 문장을 하나하나씩 출력하게 하는 get_next_line 함수를 만들면 된다.
따라서, test.txt를 열어 get_next_line함수를 사용하게 된다면 아래와 같은 결과가 나온다.
1번째 get_next_line 호출 : Hello every one~~
2번째 get_next_line 호출 : My name is Hyson
과제 더 이해해보기
main문을 통해 get_next_line 함수의 사용을 확인한다면 이해가 더욱 쉬울 것이다(Thanks to chan)
#include "get_next_line.h"
#include <fcntl.h>
#include <stdio.h>
void ft_putstr(char *s)
{
if (!s)
return ;
while (*s)
write(1, s++, 1);
}
int main(int argc, char **argv)
{
int fd;
int ret;
char *str;
str = NULL;
if (argc < 2)
ft_putstr("File name missing.\n");
else if (argc > 2)
ft_putstr("Too many arguments.\n");
else
{
fd = open(argv[1], O_RDONLY);
if (fd == -1)
{
ft_putstr("Error, cannot open file\n");
return (1);
}
ft_putstr("File:\n\n");
while ((ret = get_next_line(fd, &str)) > 0)
{
printf("[%s]\n", str);
free(str);
}
printf("[%s]\n", str);
free(str);
ft_putstr("\n\n:End of File\n");
if (ret == -1)
{
ft_putstr("Error, cannot close file\n");
return (1);
}
}
return (0);
}
C
복사
open이 정상적인 경우 파일 디스크립터(File Descriptor)의 값을 반환하게 되고, 실패하게 되면 -1 을 반환하게 된다.
get_next_line함수의 리턴값은 아래의 3가지로 나뉜다.
1.
1 정상적으로 읽은 경우
2.
0 EOF(파일의 끝)을 만난 경우
3.
-1 에러 발생
따라서 파일안의 내용이 EOF를 만날때까지 한줄씩 출력을 하고 EOF를 만나면 마지막 출력을 한 뒤에 파일의 끝을 알리게 된다.
함수의 흐름
1.
오류 체크
•
fd가 올바른지
•
버퍼사이즈가 올바른지
•
우리가 get_next_line을 호출할때마다 쓸 buf 변수가 동적할당이 잘 되었는지
2.
파일의 끝을 만날때까지 안의 내용 계속 저장
•
read를 통해서 buf에 저장
•
buf의 내용을 save에 계속 저장을 하고 save가 업데이트 될때마다 save안에 \n 이 있는지 체크
•
\n 이 있다면 그 전까지 출력하고 그 후는 다시 save에 저장
static이 필요한 이유
#include <stdio.h>
void increase_num(void)
{
static int num = 4;
printf("%d\n", num);
++num;
}
int main(void)
{
increase_num();
increase_num();
increase_num();
return (0);
}
C
복사
위의 경우 static int num이라는 내부 정적 변수의 초기화는 프로그램의 시작에 이뤄지며 초기 값은 4가 된다. 이 때 static int num은 increase_num이라는 함수의 지역 변수처럼 보여 Stack에 위치할 것 같지만, 실제로는 (이 경우에는 초기화 구문이 존재하므로 BSS 영역이 아닌) Data 영역에 위치하고 있다. 위에서 언급했던 초기화 구문이 동작하지 않는다는 얘기는 increase_num 함수 내의 초기화 구문인 static int num = 4가 매 함수 실행마다 이뤄지지 않는다는 말이다. 또한 내부 정적 변수는 특정 함수 혹은 클래스 간 공유되어 사용된다고 했기 때문에 위 main 함수의 실행 결과는 4, 5, 6가 된다.
프로세스는 운영체제에 의해서 관리 되며, 프로세스 별로 사용 가능한 메모리 영역이 존재한다.(기본적으로 서로 다른 프로세스는 각 프로세스가 사용하는 메모리 공간을 읽고 쓰는 것이 불가능하다) 프로그램이 프로세스로써 돌아가게 되면 프로그램의 Code와 Data는 프로세스 메모리로 불러들여 진다. 이 때 프로세스는 Code Segment, Data Segment, Heap, Stack (Stack은 함수의 호출 기록 및 지역 변수들의 기록이 담겨 있다.)의 구조로 메모리 레이아웃을 가진다. (프로그램은 HDD, SSD와 같은 곳에 존재하고, 프로세스는 RAM에 존재한다.)
일반적인 Stack의 크기는 84KB 정도이다. 이 크기는 시스템에 따라 그 다를 수 있는데, 이 때문에 아마 GNL을 진행하면서 지역 변수로 문자 배열을 선언 시 문제를 겪을 수 있다. (지역 변수로 선언하면 Stack에 할당되므로)
대다수의 카뎃들이 지역 변수로 100만짜리 char 배열을 선언했을 때는 문제가 없엇겠지만, 1000만부터는 할당 공간이 부족하여 잘못된 주소 접근으로 Segmentation Fault를 많이 겪게 된다.
따라서 이를 해결하기 위해선 시스템 설정으로 Stack의 크기를 강제로 늘려주거나, 메모리 이용을 Stack을 피하면 된다. 이는 곧 정적 변수 혹은 전역 변수로 선언하여 Data Segment에 위치시키면 된다는 것이다. 혹은 동적할당을 통해 Heap에 위치시키는 것도 하나의 방법이 될 수 있다.
1) static 변수란?
전역이든 지역이든 static 변수는 Data Segment에 위치한다.
static 변수는 Global 변수 (전역 변수), Local 변수 (지역 변수) 어느 것으로 이용이 가능하다.
2) 외부 정적 변수
전역으로 선언된 static 변수는 외부 정적 변수라고도 불리며, 별도의 초기화 구문이 없어도 0으로 초기화된다.
Data Segment의 BSS 영역에 위치하여 0으로 초기화된다. 초기화 구문 존재 시에는 Data Segment의 Data 영역에 위치한다.
3) 내부 정적 변수
특정 함수나 클래스 내부에 선언된 지역 변수는 내부 정적 변수라고도 불리며, 외부 정적 변수와 마찬가지로 별도의 초기화 구문이 없어도 0으로 초기화 된다. 또한 내부 정적 변수의 경우에도 프로세스의 메모리가 할당되는 프로그램의 시작 시점에 이뤄지기 때문에 함수 실행 등으로 인한 선언문에서의 실행으로는 초기화가 이뤄지지 않고 무시된다.
초기화 시점이 프로그램의 시작이라서 함수 실행 시 초기화 구문에서 초기화가 안 된다고 했는데, 이렇게 되어도 문제가 없는 이유는 static 변수는 함수 혹은 클래스에 대해서 내부 정적 변수로 이용되는 경우에 각 함수 별 혹은 클래스 별로 공유되는 일종의 공유 변수로 이용되기 때문이다. 아래 코드를 보자.