42Seoul Subject를 수행하면서 필요했던 지식을 정리해본다.
이전까지는 과제에서 알아야할 주제 별로 나누어서 포스팅 했는데, get_next_line 과제에서 조금 애매한 것들은 한번에 모아 기록하게 됐다.
get_next_line은 읽은 파일의 한 줄을 반환하는 프로그램이다.
시스템이 파일을 열고, 일정한 크기씩 읽어들여서 줄바꿈을 구분하여 파일내용을 출력하는 함수를 짜야한다.
유사한 질문이 있는 어느 게시판이 있었는데, kldp.org/node/156440을 읽어보면 대충 감이 올 것이다.
사용한 헤더
- <limits.h> : 파일 디스크립터의 최대값인 OPEN_MAX가 정의돼 있다. 사용하는 컴퓨터 환경의 OPEN_MAX를 직접 구해서 상수로 사용한다면 이 헤더는 include 하지 않아도 된다.
- <unistd.h> : 파일을 읽고 쓰고 닫는 read(), write(), close() 함수가 정의돼 있다. (⚠️fread() 등의 표준 입출력 함수는 <unistd.h>가 아닌 <stdio.h>에 정의돼있다. 사용방식이 조금 다르다.)
- <fcntl.h> : 파일을 여는 open() 함수와 O_RDONLY 등의 파일 접근모드가 정의돼있다.
- <stdlib.h> : 힙 메모리에 동적할당하는 malloc() 함수가 정의돼 있다.
main 함수 코드
get_next_line은 main.c와 Makefile을 제출하지 않는다.
과제로 제출하지 않지만 프로그램을 돌리기 위해 사용할 main 함수 견본코드는 다음과 같다.
슬랙에서 yohlee님이 공유해주셨다.
#include "get_next_line.h"
#include <stdio.h>
#include <fcntl.h>
int main(void)
{
int temp;
int fd;
char *line;
fd = open("test.txt", O_RDONLY);
while ((temp = (get_next_line(fd, &line)) > 0))
{
printf("%s\n", line);
free(line);
}
printf("%s\n", line);
free(line);
close(fd);
return (0);
}
get_next_line을 이용해서 main 함수는 파일내용을 한줄씩 출력한다.
파일의 한 줄(줄바꿈되기 전까지)을 담는 line 변수는 main 함수에서 선언하고, get_next_line 함수에 line 변수을 넘겨줌으로써
get_next_line 함수가 끝나면 main 함수는 값이 담긴 line을 출력한다.
get_next_line 함수는 line을 직접 리턴하지 않으며, 로직에 의해 동적할당해서 담을 뿐이다.
(get_next_line의 리턴값은 1, 0, -1이다.)
main 함수에서는 파일을 여는 open() 함수와 파일을 읽는 read() 함수가 사용된다.
유닉스 파일시스템을 미리 훑어보는 걸 추천한다.
<파일 입출력 참고글>
jihooyim1님의 깃북 jihooyim1.gitbooks.io/unixbasic/content/contents/02.html
숙명여대 창병모 교수님 강의자료 cs.sookmyung.ac.kr/~chang/lecture/sp/chap2.pdf
티스토리 bubble-dev bubble-dev.tistory.com/entry/CC-read-%ED%95%A8%EC%88%98-%ED%8C%8C%EC%9D%BC%EC%9D%84-%EC%9D%BD%EB%8A%94-%ED%95%A8%EC%88%98
티스토리 jeongchul jeongchul.tistory.com/96
파일 관련 함수
int open(const char *FILENAME, int FLAGS[, mode_t MODE])
헤더 | <fcntl.h> |
|
형태 | int open (const char *FILENAME, int FLAGS[, mode_t MODE]) |
|
인자 | char *FILENAME | 대상파일이름 |
int FLAGS | 파일 열기 옵션 | |
[, mode_t MODE] | O_CREAT 옵션 사용에 의해 파일이 생성될 때 지정되는 파일 접근 권한 | |
리턴값 | - 정상적으로 파일을 열었다면 : 파일 디스크립터의 양의 정수 값 - 파일 열기에 실패하면 : -1 |
MODE의 여러 옵션을 잘 정리한 블로그를 찾아볼 수 있었다.
⚠️open()과 fopen()의 차이
open() 함수는 사용하는 파일을 구별하기 위해 파일 디스크립터라는 정수를 반환하고 FD 번호로 지정하여 사용한다.
반면 fopen()함수는 FILE 구조체 정보, 즉 파일 스트림 정보를 구한다.
즉, 정수값만 구하는 open()함수 보다도 fopen()은 FILE 의 다양한 정보를 구하여 편리함을 제공한다.
subject에서는 표준 입출력함수인 fopen, fread 등을 사용하지 않고 open, read를 사용한다.
ssize_t read (int fd, void *buf, size_t nbytes)
헤더 | <unistd.h> |
|
형태 | ssize_t read (int fd, void *buf, size_t nbytes) |
|
인자 | int fd | 파일 디스크립터 |
void *buf | 파일을 읽어들일 버퍼 (read 함수가 끝나면 버퍼에 파일 내용이 담긴다.) |
|
size_t nbytes | 버퍼의 크기 | |
리턴값 | - 정상적으로 파일을 읽었다면 : 읽어들인 바이트 수 - 파일 읽기에 실패했다면 : -1 - 파일을 끝까지 읽었다면 : 0 (더 이상 읽을 바이트가 없다면 0을 반환한다. EOF(End of file)을 의미한다.) |
버퍼(buffer) 개념
파일을 읽을 때 사용할 임시 공간으로 버퍼 변수를 사용한다.
버퍼(buffer)란 컴퓨터의 주기억장치와 주변 장치 사이에서 데이터를 주고받을 때 둘 사이의 전송속도 차이를 해결하기 위해 전송할 정보를 임시로 저장하는 고속 기억장치다.
버퍼에는 용량이 한정되어 있기 때문에, 오버 플로우 현상이 발생할 수 있다.
표준 입출력함수(scanf, printf 등)는 버퍼를 사용한다.
offset 개념
read 함수와 관련해서 offset을 이해할 필요가 있다.
read(fd, buf, buffsize)를 수행하면 데이터를 읽은 만큼 offset를 증가시킨다.
다시 파일을 읽으려하면 마지막 읽은 파일 위치부터 읽어들인다.
read 함수는 자동으로 끊어진 부분부터 쭉쭉 읽는데, 이유는 파일 디스크립터 테이블(FD Table)에서 읽은 위치(offset)를 저장하기 때문이다.
offset은 각 프로세스가 같은 FD를 참조하여 같은 i-node를 참조하더라도, 각자 다른 부분을 읽을 수 있도록 할당한다.
참고로 ab만 담긴 파일을 버퍼크기 4 로 읽으면
read 함수의 리턴값은 2 이고, 버퍼에는 a, b, 쓰레기값, 쓰레기값 이 담긴다. (By jko님)
만약 버퍼크기가 4일 때 abcdefghij가 담긴 파일을 읽는다면
첫 번째 read() 호출 후 리턴값은 4 이고, 버퍼에는 abcd 이 담긴다.
두 번째 read() 호출 후 리턴값은 4 이고, 버퍼에는 efgh 이 담긴다.
세 번째 read() 호출 후 리턴값은 2 이고, 버퍼에는 ijgh 이 담긴다. (By jolim님)
파일 디스크립터(File Descriptor)
파일 디스크립터 개념은 La Piscine과 Libft 과제에서 접했던 개념이다.
파일 디스크립터는 시스템으로 부터 할당받은 파일이나 소켓을 대표하는 정수다.
정수값은 사용하는 컴퓨터에 따라 각각 다른 상한값인 OPEN_MAX까지 할당된다.
OPEN_MAX 값은 <limits.h>에서 include 해서 쓸 수도 있고,
개인 컴퓨터에서 한번에 열 수 있는 파일 개수인 OPEN_MAX 값을 확인해서 헤더파일에 직접 상수로 define할 수도 있다.
파일 디스크립터에 대해서는 이 포스팅에 정리한 바 있었다.
+) Norm에는 '표준 라이브러리의 매크로는 프로젝트에서 사용이 허가되었을 경우에만 사용 가능합니다.'라는 문구가 있어서 <limits.h의 OPEN_MAX로 통과할 수 있지만 상수를 추천한다는 글이 있었다. (By hyeonski님)
+) Linux 시스템에서는 OPEN_MAX가 더 이상 사용되지 않는다는 stackoverflow의 답변도 있었다.
만약 wsl 리눅스 환경에서 문제를 푼다면 아예 헤더파일에서 상수로 define하는 것도 괜찮을 것 같다.
stackoverflow.com/questions/14042824/where-is-open-max-defined-for-linux-systems
컴파일 옵션 -D
조건부 컴파일을 할때 유용하게 사용되는 것이 -D 옵션이다.
gcc -D <매크로이름>
GNL 과제에서 gcc -D 플래그로 컴파일 시 외부에서 #define 하여 버퍼사이즈를 정의한다.
subject PDF에서 안내하는 컴파일 방식은 다음과 같다.
버퍼사이즈를 외부에서 정의하기 때문에 코드에서는 BUFFER_SIZE라는 변수를 별도의 선언 없이 바로 사용할 수 있다.
gcc -Wall -Wextra -Werror -D BUFFER_SIZE=32 get_next_line.c get_next_line_utils.c
정적변수(Static Variable)
함수 내에서 사용하는 변수로서 지역변수, 전역변수, 정적변수가 있다. 본 과제에서는 정적변수를 사용할 필요가 있다.
get_next_line은 버퍼사이즈 단위로 파일을 읽고, line변수에 한 줄에 해당하는 문자열을 동적할당해야 한다. (한 줄이 얼마나 길지 모르기 때문에 동적할당)
한 줄이 완성되기까지 문자열을 보관할 필요가 있으며, 개행문자 ‘\n’을 기준으로 이후의 문자열도 보관해야 한다.
따라서 함수 안에서 값을 유지시키는 static 변수를 사용한다.
위의 과정을 파일 디스크립터별로 한다면 OPEN_MAX만큼 관리하기 위해서 포인터 배열 변수를 선언해야할 것이다.
한편 static 변수는 스택이 아닌 데이터 영역에 저장되는데, 경우에 따라 스택오버플로우를 피하는 용도로도 사용될 수 있다.
정적변수에 대한 자세한 내용은 이 포스팅에 별도로 정리했다.
정적변수는 메모리의 데이터(Data) 영역에 저장되는데(비초기화시 BSS 영역에 저장), 그 이유를 이 포스팅에서 더 자세히 다뤘다.
GNL Tester
내가 사용했던 테스터들은 아래와 같다.
- GNL_lover https://github.com/charMstr/GNL_lover
+) STDIN_FILENO 버전은 표준입력 테스트 뜨면 글자 아무거나 써보고(최대한 길게 테스트해볼 것) 엔터치다가 Ctrl+D로 끝내면 된다. - gnlkiller https://github.com/DontBreakAlex/gnlkiller
+) gnlkiller에서 KO나면 메모리 누수가 발생한 것이니 free로 잡아줘야 한다. - 42TESTERS-GNL https://github.com/Mazoise/42TESTERS-GNL
- 42curcus_gnl_tests https://github.com/mrjvs/42cursus_gnl_tests
-
Get_Next_Line_Tester https://github.com/Hellio404/Get_Next_Line_Tester
+) 메모리 누수 확인을 하는 memory_leak.sh를 실행하기 위해선 valgrind를 설치할 필요가 있다. stdin_check.sh는 안 됐던 것 같다.
GNL Convention
- subject_ko github.com/42seoul-translation/subject_ko/blob/get_next_line/get_next_line/get_next_line.ko.md
추가 Tips
- 과제에서 읽는 파일에 NUL 문자('\0')는 없다고 간주한다. subject PDF에 binary file일 때 undefined behavior 라고 써있는데 NUL 문자가 들어있는 파일은 binary file이므로 신경 안 써도 된다.
- bonus part의 점수를 받으려면 정적 변수를 한 개만 선언해야 한다. 테스터 중 Get_Next_Line_Tester은 두 개 이상의 static 변수를 선언 했을 시에 CHECK ONE STATIC VARIABLE : KO를 출력한다.
-> 나의 경우 여러 정적 변수를 사용했다가 뒤늦게 발견해서, 구조체 자료형으로 변경한 뒤 하나의 static 변수만 선언했다. - mandatory part의 get_next_line.c가 bonus part까지 충족하게 코드를 작성하고 get_next_line_bonus.c로 복사해도 된다. bonus part는 get_next_line_bonus.h를 include 해야함을 주의하자. 처음부터 bonus part를 충족하게끔 여러 FD를 사용하도록, static 변수를 한 개만 사용도록 짜는게 편할 수도 있다.
출처
42Seoul의 get_next_line
hi, daehyunlee velog.io/@hidaehyunlee/GetNextLine-%EC%82%BD%EC%A7%88%EC%9D%98-%EA%B8%B0%EB%A1%9D
SEOHLOG ohseyong.github.io/study/Get-Next-Line/
open()
FF 포럼 http://forum.falinux.com/zbxe/index.php?document_srl=408448&mid=C_LIB
open()과 fopen()의 차이
티스토리 랄라라 https://unabated.tistory.com/entry/fopen-open-차이
read()
FF 포럼 http://forum.falinux.com/zbxe/index.php?mid=C_LIB&document_srl=466628
snowdeer.github.io/c++/2017/09/10/linux-control-file-as-low-level/
fgets()
KLDP 프로그래밍 QnA kldp.org/node/156440
버퍼 개념
네이버블로그 else if goodjob blog.naver.com/PostView.nhn?blogId=paydma&logNo=50179473286&proxyReferer=https:%2F%2Fwww.google.com%2F
gcc -D 플래그
네이버블로그 NeoWizard blog.naver.com/beyondlegend/110029042755
'Activities > 공부' 카테고리의 다른 글
[42Seoul] ft_server 실행 기록 및 제출파일 작성 (0) | 2021.04.07 |
---|---|
[42Seoul] ft_server 개념 - Docker, Devian, Nginx, MySQL, phpMyAdmin, Wordpress, HTTPS (0) | 2021.03.25 |
[42Seoul] Libft 과제 - 함수 ft_lstnew와 ft_lstadd_front의 차이 이해하기 (0) | 2020.10.15 |
국토연구원 X 데이콘 주최 <국토도시 빅데이터 윈터스쿨>에 참여하다 (0) | 2020.01.01 |
SW 테스트 전문가 (CSTS) 자격시험 합격 수기 (2) | 2019.11.30 |
댓글