물리 디스크 상의 파일, 장치들을 통일된 방식으로 다루기 위한 가상적인 개념
따라서 스트림은 어디서 나왔는지 어디로 가는지 신경 쓸 필요없이 자유롭게 어떤 장치 및 프로세스, 파일들과 연결될 수 있어 많은 편리성 제공
FILE *fopen(const char *path, const char *mode);
path로 지정한 파일에대한 스트림을 만들고 그것을 관리하는 FILE포인터 반환
return 성공시 FILE 포인터, 실패시 NULL/errno 설정
r: O_RDONLY
w: O_WRONLY, O_CREAT, O_TRUNC
a: O_WRONLY, O_CREAT, O_APPEND
r+: O_RDWR
w+: O_RDWR, O_CREAT, O_TRUNC
a+: O_RDWR, O_CREAT, O_APPEND
int fclose(FILE *stream);
return 성공시 0, 실패시 EOF/errno 설정
하나의 char 타입 문자를 받는 함수, 세함수 모드 문자를 아스키 코드값으로 return
int fgetc(FILE *stream); //함수
//**return 1바이트 읽어서 읽은 값 반환, 스트림 종료정보(EOF) 반환
int fputc(int c, FILE *stream); //함수
//int c인 이유는 get에서 가져온 값을 그대로 사용하기 위해서
int getc(FILE *stream); //매크로 함수
int putc(int c, FILE *stream); //매크로 함수
int getchar(void); //getc(stdin); //자동으로 **표준 입력**스트림으로 입력
int putchar(int c); //putc(c, stdout); //자동으로 **표준 출력**스트림으로 출력
c: 출력할 문자
stream: 출력 대상 파일 포인터
get: return 성공시 1바이트 읽어서 읽은값 반환, error EOF(보통 -1)
put: return 성공시 쓴 1바이트의 값 그대로 반환, error EOF(보통 -1)
getc, putc는 매크로로 구현되어 있어 부작용이 있을 수 있기 때문에 인자가 수식으로 오면 안된다.(ex: getc(*p++);)
매크로
특정한 코드를 간단하게 표현한것
getc, putc는 스트림을 인자로 받지만 getchar, putchar은 스트림 인자를 받지 않는다.
getchar(), getc(stdin) 특징
- Enter값도 \n 글자로 키보드 버퍼에 저장
- space 또한 공백으로 입력받을 수 있다.
- 하나의 문자만을 입력받기 때문에 두자리를 못받는다.
- 버퍼로부터 입력받기 때문에 버퍼비움(fflush(stdin))이 필요하다.
- Enter가 입력될 때까지 키보드 입력값들을 계속해서 키보드 버퍼에 저장한다.
- 키보드 버퍼에 존재하는 data 중에서 버퍼 포인터가 위치한 곳으로부터 1바이트 분량을 꺼내오고 버퍼 포인터를 1바이트만큼 이동시키는 함수이다.
바이트c(읽었던 문자)를 다시 stream의 버퍼로 되돌려주는(넣어주는) 함수
바이트 단위로 읽으며 토큰을 구분하기 위해 사용
int ungetc(int c, FILE *stream);
세번째 인자인 stream으로 지정한 스트림에서 한줄을 읽어들여
첫번째 인자 buff에 저장
두번째 인자로 지정한 size에서 1을 뺀 바이트 수만큼 최대로 읽어들임
그래서 buff의 크기를 size로 지정하면 스트림에서 size-1바이트를 읽고 마지막에 \0을 넣어서 반환한다.
char *fgets(char *buf, int size, FILE *stream);
return 성공 buf 반환, 에러/문자 읽지 않은 채 EOF 도달한 경우 NULL
fgets()와 비슷한 gets()는 절대로 사용하면 안된다.
gets()는 버퍼 오버플로를 일으킬 수 있다.
버퍼 오버플로(버퍼 오버런): 지정한 버퍼를 초과해서 사용하는 것
ex) char buf[1024] 정의된 buf에 buff[1025]또는 buf[9999]에 값을 할당하는 경우
chat *gets(char *buf);
fgets와 달리 버퍼 크기를 지정하는 인자가 없다.
int fputs(const char *buf, FILE *stream);
buf: 지정한 문자열 출력
stream: 지정한 스트림 출력
return 성공 0이상의 숫자, error EOF&errno(스트림이 종료된 경우와 구별하기 위해서는 호출전 errno를 0으로 설정하는 것이 좋다.)
-줄단위로 읽어들이는 fgets와 비슷한 이름이지만 줄단위로 출력하는 함수가 아니다.
int puts(const char *buf);
주의
fgets로 읽어들인 문자열에는 \n이 붙어있기 때문에 내용을 그대로 puts에 넘기면 \n이 하나가 여분으로 붙어버린다. 그래서 fgets는 fputs와 조합해서 사용하는 것이 좋다.
Int printf(const char *fmt, ...);
Int fprintf(FILE *stream, const char *fmt, ...);
printf는 fmt에서 지정한 형식에 따라 이어지는 인자의 내용을 포함한 문자열을 출력
printf는 표준출력에 출력
fprintf는 지정한 stream에 출력
형식지정자
%c: unsigned char형의 값을 문자로 출력
%s: unsigned char*형이 가리키는 값을 문자열로서 출력
%d, %i: 정수형 값을 10진수 표기로 출력
%u: 부호 없는 정수형 값을 10진수 표기로 출력
%o: 부호 없는 정수형 값을 8진수 표기로 출력
%x, %X: 부호 없는 정수형 값을 16진수 표기로 출력
%f, %F: 부동 소수점수형 값을 소수점 표현 (XX.XXXX)으로 출력
%e, %E: 부동 소수점수형 값을 'e표기(X.XXe+XX)로 출력
%p: 포인터를 16진수 표기로 출력
- X, F, E는 소문자를 사용한 것과 거의 동일하지만, 알파벳 부분을 대문자로 출력한다는 점이 다르다.
- long 타입의 값을 출력할 때 앞에 l을 붙인다.
- %와 형식지정자 사이에 숫자를 넣으면, 지정한 숫자가 최소 출력 자릿수가 된다.
이때 -기호를 앞에두면 왼쪽 맞춤, 0을 앞에 써넣으면 빈 부분이 0으로 채워진다.- %를 문자로서 출력하고 싶을때는 %%
printf 사용시 주의점
char buf[1024]; fgets(buf, sizeof buf, stdin); printf(buf);
표준 입력에서 한줄을 읽어 그대로 printf로 출력하고 있다.
- buf에 문자 %가 들어있을 때 문제가 된다.
따라서 포맷팅을 하지 않고 문자열을 출력하는 경우에도 printf("%s", str);와 같이 출력하는 것이 좋다.
int n;
scanf("%d", &n);
gets와 같은 이유로 잠재적으로 버퍼오버플로를 일으킬 위험이 있다.
필요한 이유
fread와 fwrite는 read와 write와 큰차이가 없는데 왜 필요한가?
- 다른 stdio API와 함께 사용할 수 있음
- C언어 표준함수이기 때문에 이식성이 좋다
size_t fread(void *buf, size_t size, size_t nmemb, FILE *stream);
buf: 저장할 버퍼
size : 읽어올 데이터 크기
nmemb : 읽어올 데이터 갯수
stream : 읽어올 파일 스트림
stream이 가리키는 스트림에서 최대 size x nmemb 바이트의 데이터를 읽어들여 buf가 가리키는 메모리 영역에 쓴다.
return 성공시 nmemb, 실패시 nmemb보다 작은값/errno 설정
while((n=fread(buf, sizeof(char), 1, rfp)) > 0) {
fwrite(buf, sizeof(char), n, wfp);
}
size_t fwrite(const void *buf, size_t size, size_t nmemb, FILE *stream);
buf에 있는 size x nmemb 바이트의 데이터를 네번째 인자 stream이 가리키는 스트림에 쓴다.
return 성공시 nmemb, 실패시 nmemb보다 작은 값/errno 설정
int fseek(FILE *stream, long offset, int whence);
int fseeko(FILE *stream, off_t offset, int whence);
시스템 콜 lseek에 해당
fseek, fseeko 차이
두번째 인자 offset의 데이터 타입
- 2GB이상의 대용량 파일을 지원하기 위해서 존재(fseeko)
- off_t는 #define _FILE_OFFSET_BITS 64라고 선언하고 프로그램을 빌드하면 32비트 컴퓨터에서도 64비트 부호가 있는 정수형(long long)으로 정의되어 64비트 오프셋을 사용할 수 있게된다.
- 요즘은 64비트 머신이 일반적이어서 처음부터 off_t타입이 64비트라 fseeko만을 사용하면 크게 문제될 일은 없다.
long ftell(FILE *stream);
off_t ftello(FILE *stream);
return stream의 파일 offset값
void rewind(FILE *stream);
rewind는 파일 offset을 파일의 처음으로 되돌린다.
FILE은 파일 디스크립터를 감싸고, 버퍼링 기능을 추가한 구조체이다.
이처럼 기존의 기능을 그대로 감싸고 추가적인 기능을 제공하는 레이어를 wrapper라고 한다.
FILE은 file descriptor의 wrapper라고 할 수 있다.
int fileno(FILE *stream);
인자로 지정한 stream이 감싸고 있는 파일 디스크립터를 반환
FILE *fdopen(int fd, const char *mode);
파일 디스크립터 fd를 감싸고 있는 FILE을 만들어서 그 포인터를 반환
return 성공시 FILE 포인터 반환, 실패시 NULL
주의할점
파일 디스크립터와 FILE 타입 함께 사용하지 않기
- 버퍼를 사용한 작업과 그렇지 않은 작업이 섞이면 입출력 순서가 의도치 않게 바뀐다.
그렇지만 사용하는 이유는- stdio에서 할 수 없는 작업들이 있기 때문
ex) fopen()사용해서 새 파일을 만들 때 해당 파일에 대한 권한을 지정할 수 없기 때문에
open()으로 파일 열고, fdopen()으로 FILE을 만들게 된다.
또는 ioctl(), fcntl() 사용하고 싶을 때
stream이 버퍼링하고 있는 내용을 즉시 write()하도록 하는 API
int fflush(FILE *stream);
return 성공시 0, 실패시 EOF/errno 설정
파일의 버퍼를 바꿔주는 함수
void setbuf(FILE *stream, char *buf);
void setbuffer(FILE *stream, char *buf, size_t size);
void setlinebuf(FILE *stream);
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
setbuf()
setvbuf()
Full buffer(_IOFBF): 버퍼가 가득 차야 파일에 읽고 쓰는 방식으로 주로 일반 파일에 적용
Line buffer(_IOLBF): 버퍼에 개행 문자가 들어오면 파일에 읽고 쓰는 방식으로 표준 입출력 파일에 쓰임
None buffer(_IONBF): 버퍼를 사용하지 않고 바로바로 파일에 읽고 쓰는 방식으로 표준 에러 파일에 쓰임
인자로 지정한 스트림의 EOF 플래그를 가져온다.
📌EOF 플래그는 스트림이 처음 만들어질때는 0
stream 읽는 작업이 EOF에 도달하면 0이 아닌 값이 설정된다.
int feof(FILE *stream);
잘못된 예시
char buf[1024]; while(!foef(stdin)){ fgets(buf, 1023, stdin); fputs(buf, stdout); }
fgets가 EOF를 이미 만나고 나서도 fputs가 실행된 후에야 루프를 빠져나온다.
결과적으로 마지막 줄이 두번 출력되고 만다.
- feof()는 fread()처럼 반환값이 EOF인지 에러인지 구별할 수 없는 API와 함께 사용하는 것이 바람직하다.
- 그러나 ferror()을 사용하는 것이 더 좋다
인자로 지정한 스트림의 에러 플래그를 가져온다.
int ferror(FILE *stream);
fread()처럼 반환값이 EOF인지 에러인지 구별할 수 없는 API와 함께 사용
void clearerr(FILE *stream);
지정한 stream의 에러 플래그와 EOF 플래그를 지운다.
ex) tail -f(파일이 커짐에 따라 추가된 데이터 출력) 처럼 계속 생성되는 파일을 출력하는 경우
EOF에 도달한 이후에도 다른 프로세스가 내용을 추가하면 다시 읽어야한다.
시스템 콜 수준에서는 하나의 스트림에 대해서 몇번이고 EOF가 발생할 수 있는 것이다.
그러나 stdio는 read()가 한번이라도 EOF를 반환하면 FILE에 EOF플래그를 설정하기 때문에 이후 read()를 호출하지 않는다.
바로 이때 clearerr() 사용하여 stdio가 다시 read를 사용할 수 있게 된다.
strace 명령을 사용하여