[System Programming] 7-2장. C 임의 접근
7.4 임의 접근
sequential access vs. random access
전 시간에 배운 것이 sequential access
파일 내 위치
- 현재 파일 위치(current file position)
- 열린 파일에서 다음 읽거나 기록한 파일 내 위치
- 파일 위치 포인터(file position pointer)
- 시스템 내에 그 파일의 현재 파일 위치를 저장하고 있다.
파일 위치 관련 함수
- REVIEW)
off_t lseek (int fd, off_t offset, int whence);
// off_t: file size in bytes- 이동에 성공하면 현재 위치(measured in bytes from the beginning of the file)를 리턴하고 실패하면 -1을 리턴한다.
int fseek(FILE *fp, long offset, int mode)
- 파일 위치 포인터를 임의로 설정할 수 있는 함수
void rewind(FILE *fp)
- 현재 파일 위치를 파일 시작에 위치시킴.
long ftell(FILE *fp)
- 파일의 현재 파일 위치를 나타내는 파일 위치 지정자 값 리턴
fseek() 함수
fseek(FILE *fp, long offset, int mode)
- 파일이 binary mode로 open 된 경우
- FILE 포인터 fp가 가리키는 파일의 현재 파일 위치(currnet file position)를 모드(mode) 기준으로 오프셋(offset)만큼 옮긴다.
- 파일이 binary mode로 open 된 경우
fseek() 함수
- 파일 위치 이동
fseek(fp, 0L, SEEK_SET);
파일 시작으로 이동(rewind)fseek(fp, 100L, SEEK_SET);
파일 시작에서 100바이트 위치로fseek(fp, 0L, SEEK_END);
파일 끝으로 이동(append)
- 레코드 단위로 이동
fseek(fp, n * sizeof(record), SEEK_SET);
n+1번째 레코드 시작위치로fseek(fp, sizeof(record), SEEK_CUR);
다음 레코드 시작위치로fseek(fp, -sizeof(record), SEEK_CUR);
전 레코드 시작위치로
- 파일끝 이후로 이동
fseek(fp, sizeof(record), SEEK_END);
파일끝에서 한 레코드 다음 위치로 이동
파일 끝 이후로 이동: 예
fwrite(&record1, sizeof(record), 1, fp);
fwrite(&record2, sizeof(record), 1, fp);
fseek(fd, sizeof(record), SEEK_END);
fwrite(&record3, sizeof(record), 1, fp);
- 텍스트 파일이나 이진 파일은 쓰는 순서대로 순차적으로 저장됨
- 읽기를 할 때 레코드 단위로 검색/읽기를 위해서 저장 시에 순차적이 아닌 저장 장소 지정이 필요
- 앞에서 공부한 다음 두 프로그램 참조
- fprint.c (텍스트 파일에 쓰기) 및 stcreate1. c (이진 파일에 쓰기)
stcreate2.c - 레코드 형식으로 저장
#include <stdio.h>
#include "student.h"
#define START_ID 1201001
/* 구조체를 이용하여 학생 정보를 파일에 저장핚다. */
int main(int argc, char* argv[])
{
struct student rec;
FILE *fp;
if (argc != 2) {
fprintf(stderr, "사용법: %s 파일이름\n", argv[0]);
exit(1);
}
fp = fopen(argv[1], "wb");
printf("%7s %6s %4s\n", "학번", "이름", "점수");
while (scanf("%d %s %d", &rec.id, rec.name, &rec.score) == 3) {
fseek(fp, (rec.id – START_ID)* sizeof(rec), SEEK_SET);
fwrite(&rec, sizeof(rec), 1, fp);
}
fclose(fp);
exit(0);
}
$ stcreate2 stdb2
학번 이름 점수
14010001 박연아 96
14010002 김연아 86
----
stquery.c
#include <stdio.h>
#include "student.h"
int main(int argc, char *argv[])
{
struct student rec;
char c;
int id;
FILE *fp;
if (argc != 2) {
fprintf(stderr, "사용법: %s 파일이름\n", argv[0]);
exit(1);
}
if ((fp = fopen(argv[1], "rb")) == NULL ) {
fprintf(stderr, "파일 열기 오류\n");
exit(2);
}
do {
printf("검색할 학생의 학번 입력: ");
if (scanf("%d", &id) == 1) {
fseek(fp, (id - START_ID) *sizeof(rec), SEEK_SET);
if ((fread(&rec, sizeof(rec), 1, fp) > 0) && (rec.id != 0))
printf("학번: %8d 이름: %4s 점수: %4d\n", rec.id, rec.name, rec.score);
else printf("레코드 %d 없음\n", id);
}
else printf("입력 오류");
printf("계속하겠습니까?(Y/N)");
scanf(" %c", &c);
} while (c == 'Y');
fclose(fp);
exit(0);
}
$ stquery stdb2
검색할 학생의 학번 입력: 1401003
학번: 1401003 이름: 김연아 점수: 90
계속하겠습니까?(Y/N) Y
검색할 학생의 학번 입력: 1401006
학번: 1401006 이름: 박연아 점수: 80
계속하겠습니까?(Y/N) N
$
레코드 수정 과정
(1) 파일로부터 해당 레코드를 읽어서
(2) 이 레코드를 수정한 후에
(3) 수정된 레코드를 다시 파일 내의 원래 위치에 써야 한다
즉, 읽기와 쓰기가 동시에
stupdate.c
#include <stdio.h>
#include "student.h"
/* 파일에 저장된 학생 정보를 수정한다. */
int main(int argc, char *argv[])
{
struct student rec;
int id;
char c;
FILE *fp;
if (argc != 2) {
fprintf(stderr, "사용법: %s 파일이름\n", argv[0]);
exit(1);
}
if ((fp = fopen(argv[1], "rb+")) == NULL) {
fprintf(stderr, "파일 열기 오류\n");
exit(2);
}
do {
printf("수정핛 학생의 학번 입력: ");
if (scanf("%d", &id) == 1) {
fseek(fp, (id - START_ID) * sizeof(rec), SEEK_SET);
if ((fread(&rec, sizeof(rec), 1, fp) > 0)&&(rec.id != 0)) {
printf("학번: %8d 이름: %4s 점수: %4d\n", rec.id, rec.name, rec.score);
printf("새로운 점수 입력: ");
scanf("%d", &rec.score);
fseek(fp, -sizeof(rec), SEEK_CUR);
fwrite(&rec, sizeof(rec), 1, fp);
} else printf("레코드 %d 없음\n", id);
} else printf("입력오류\n");
printf("계속하겠습니까?(Y/N)");
scanf(" %c",&c);
} while (c == 'Y');
fclose(fp);
exit(0);
}
$ stupdate stdb2
수정할 학생의 학번 입력: 1401003
학번: 1401003 이름: 김연아 점수: 90
새로운 점수 입력: 95
계속하겠습니까?(Y/N) N
7.5 버퍼링
C 라이브러리 버퍼
- C 라이브러리 버퍼 사용 목적
- 디스크 I/O 수행의 최소화
- fget(), fputc() 호출마다 디스크 I/O 수행은 부적절(시간이 굉장히 많이 걸리기 때문에)
- read(), write() 함수 호출의 최소화
- 최적의 크기 단위로 I/O 수행
- 시스템 성능 향상
- 그래서 putc로 모으고 모아서 한 번에 write 하자는 아이디어
- 디스크 I/O 수행의 최소화
- C 라이브러리 버퍼 방식
- 완전 버퍼(fully buffered) 방식
- 줄 버퍼(line buffered) 방식
- 버퍼 미사용(unbuffered) 방식
C 라이브러리 버퍼 방식
- 완전 버퍼 방식
- 버퍼가 꽉 찼을 때 실제 I/O 수행
- 디스크 파일 입출력 -–블록 단위
- 줄 버퍼 방식
- 줄 바꿈 문자(newline)에서 실제 I/O 수행
- 터미널 입출력 (stdin, stdout)
- 버퍼 미사용 방식
- 버퍼를 사용하지 않는다.
- 표준 에러 (stderr)
buffer.c
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
FILE *fp;
if (!strcmp(argv[1], "stdin")) { // 표준 입력
fp = stdin;
printf("한 글자 입력:");
if (getchar() == EOF) perror("getchar"); //파일 끝 도달
}
else if (!strcmp(argv[1], "stdout")) // 표준 출력
fp = stdout;
else if (!strcmp(argv[1], "stderr")) // 표준 에러
fp = stderr;
else if ((fp = fopen(argv[1], "r")) == NULL) { //일반 파일
perror("fopen");
exit(1);
}
else if (getc(fp) == EOF) perror("getc");
printf("스트림 = %s, ", argv[1]);
if (fp->_flags & _IO_UNBUFFERED)
printf("버퍼 미사용");
else if (fp->_flags & _IO_LINE_BUF)
printf("줄 버퍼 사용");
else
printf("완전 버퍼 사용");
printf(", 버퍼 크기 = %d\n", fp->_IO_buf_end - fp->_IO_buf_base);
// FILE 구조체 참조
exit(0);
}
$ buffer stdin
한 글자 입력:x
스트림 = stdin, 줄 버퍼 사용, 버퍼 크기 = 1024
$ buffer stdout
스트림 = stdout, 줄 버퍼 사용, 버퍼 크기 = 1024
$ buffer stderr
스트림 = stderr, 버퍼 미사용, 버퍼 크기 = 1
$ buffer /etc/passwd
스트림 = /etc/passwd, 완전 버퍼 사용, 버퍼 크기 = 4096
setbuf()/setbuf()
#include <stdio.h>
void setbuf (FILE *fp, char *buf );
int setvbuf (FILE *fp, char *buf, int mode, size_t size );
- 버퍼의 관리 방법을 변경한다 .
- 호출 시기
- 스트림이 오픈된 후,
- 입출력 연산 수행 전에 호출되어야 함
setbuf()
void setbuf(FILE *fp, char *buf)
- 버퍼 사용을 on/off 할 수 있다.
- fp는 열린 파일(스트림을 지칭)
- buf 가 NULL 이면 버퍼 미사용 방식
- char *buf: buf의 시작주소
- buf 가 BUFSIZ크기의 공간을 가리키면 완전/줄 버퍼 방식
- BUFSIZ는 <stdio.h> 에 정의된 버퍼 사이즈
- 터미널 장치면 줄 버퍼 방식
- 그렇지 않으면 완전 버퍼 방식
예제: setbuf
#include <stdio.h>
main()
{
printf("안녕하세요, "); sleep(1); // printf(" \n");수행후 출력
printf("리눅스입니다!"); sleep(1); // printf(" \n");수행후 출력
printf(" \n"); sleep(1);
setbuf(stdout, NULL); // 버퍼 미사용 설정
printf("여러분, "); sleep(1); // 바로 출력 (버퍼 미사용)
printf("반갑습니다"); sleep(1);
printf(" ^^"); sleep(1);
printf(" \n"); sleep(1);
}
$ setbuf
안녕하세요, 리눅스입니다!
여러분, 반갑습니다^^
\n이 입력 되면 그 전에 있던 거와 한 꺼번에 출력이 됨(줄버퍼 사용 때문에)
- 줄버퍼: 버퍼가 꽉 차지 않더라도 \n에 출력
버퍼 미사용 설정 이후에는 출력이 바로바로 되게 된다.
setvbuf()
int setvbuf(FILE *fp, char *buf, int mode, size_t size()
- 버퍼 사용 방법을 변경
- 리턴 값: 성공하면 0, 실패하면 nonzero
- mode
- _IOFBF : 완전 버퍼 방식
- _IOLBF : 줄 버퍼 방식
- _IONBF : 버퍼 미사용 방식
explicitly 명시하기 때문에 훨씬 더 좋다.
- mode == _IONBF
- buf 와 size 는 무시됨 (버퍼 미사용이기 때문)
- mode == _IOLBF or _IOFBF
- buf 가 NULL이 아니면
- buf 에서 size 만큼의 공간 사용
- buf 가 NULL이 아니면
- NULL이면 라이브러리가 알아서 적당한 크기 할당 사용
- stat 구조체의 st_blksize 크기 할당 (disk files)
- st_blksize 값을 알 수 없으면 BUFSIZ 크기 할당 (pipes)
예제:setvbuf
#include <stdio.h>
int main( void )
{
char buf[1024];
FILE *fp1, *fp2;
fp1 = fopen("data1", "a");
fp2 = fopen("data2", "w");
if( setvbuf(fp1, buf, _IOFBF, sizeof( buf ) ) != 0 )
printf("첫 번째 스트림: 잘못된 버퍼\n" );
else
printf("첫 번째 스트림: 1024 바이트 크기 버퍼 사용\n" );
if( setvbuf(fp2, NULL, _IONBF, 0 ) != 0 )
printf("두 번째 스트림: 잘못된 버퍼\n" );
else
printf("두 번째 스트림: 버퍼 미사용\n" );
}
$ setbuf
첫 번째 스트림: 1024 바이트 크기 버퍼 사용
두 번째 스트림: 버퍼 미사용
fflush()
#include <stdio.h>
int fflush (FILE *fp);
- fp 스트림의 출력 버퍼에 남아있는 모든 내용을 write() 시스템 호출을 통하여 커널에 전달한다.
- fp가 가리키는 함수 내용에 버퍼에 남아있는 모든 내용을 하드 디스크에 write을 요구
- 리턴 값 : 성공하면 0, 실패하면 EOF (-1)
- fp 가 NULL이면, 모든 출력 스트림의 출력 버퍼에 남아있는 내용을 커널에 전달한다
7.5 기타 함수
문자열 처리 함수1
- # include
- char *strcpy(char *s, const char *t);
- Null 문자를 포함해서 문자열 t를 문자열 s에 복사하고 s를 반환
- char *strncpy(char *s, const char *t, size_t n);
- 최대 n개 문자의 문자열 t를 문자열 s에 복사하고 s를 반환.
- t의 길이가 n보다 작으면 NULL 문자로 채운다.
- 만일 t의 처음 n 바이트 중 NULL이 없다면, s는 NULL 종료가 안될 수 있다.
- char *strcat(char *s, const char *t);
- 문자열 t를 문자열 s에 접합하고 s를 반환
- char *strncat(char *s, const char *t, size_t n);
- 최대 n개 문자의 문자열 t를 문자열 s에 접합한다. S를 NULL로 끝내고 반환한다.
- int strcmp(const char *s, const char *t);
- 문자열 s를 문자열 t와 비교한다.
- S < t 이면 음수값, s=t면 0, s>t이면 양수값을 반환한다.
문자열 처리 함수2
- int strncmp(const char *s, const char *t, size_t n);
- 문자열 s의 최대 n개의 문자들을 문자열 t와 비교한다.
- S t 이면 양수값을 반환한다.
- char *strchr(char *s, int c);
- 문자열 s 내에서 처음 나타난 문자 c에 대핚 포인터를 반환한다. 없으면 NULL 반환
- char *strrchr(char *s, int c);
- 문자열 s 내에서 마지막으로 나타난 문자 c에 대한 포인터를 반환한다.
- 없으면 NULL 반환
- char *strpbrk(const char *s, const char *t);
- 문자열 s 내에서 문자열 t 내의 문자가 처음 나타난 곳에 대한 포인터를 반환한다.
- 없으면 NULL 반환
- char *strstr(const char *s, const char *t);
- 문자열 s 내에서 부분 문자열 t가 처음 나타난 곳에 대한 포인터를 반환한다.
- 없으면 NULL 반환
문자열 처리 함수3
- size_t strlen(const char *s);
- 문자열 s의 길이를 반환
- char *strerror(int n);
- 오류 n에 대응되는 메시지에 대핚 포인터를 반환
- char *strtok(char *s, const char *t);
- 문자열 t 내 문자로 구분된 토큰으로 s를 나눈다.
- If, s is not NULL, 첫번째 호출을 나타내고, 두번째 호출부터는 s를 NULL로 설정해야 함.
문자 처리 함수1
- # include
- int isalnum(int c); // isalpha(c) or isdigit(c)
- int isalpha(int c); // isupper(c) or islower(c)
- int iscntrl(int c); // 제어문자인가? Ascii 0x00-0x1F, 0x7F
- int isdigit(c); // 십진 숫자인가?
- int isgraph(c); // 공백 문자가 아닊 인쇄 가능 문자인가?
- int islower(c); // 소문자인가?
- int isprint(c); // 공백 문자를 포함하여 인쇄 가능 문자인가?
- int punct(c); // 공백 문자, 문자, 숫자 이외의 인쇄 가능 문자인가?
문자 처리 함수2
- int isspace(c) // 공백 문자, 폼피드, 새줄문자 캐리지 리턴, 탭, 수직 탭인가?
- int isupper(c) // 대문자인가?
- int isxdigit(c) // 16짂수 숫자인가?
- int tolower(c) // 상응하는 소문자를 반환
- int toupper(c) // 상응하는 소문자를 반환
핵심 개념
- 파일은 모든 데이터를 연속된 바이트 형태로 저장한다.
- 파일을 사용하기 위해서는 반드시 파일 열기 fopen()를 먼저 해야 하 며 파일 열기를 하면 FILE 구조체에 대한 포인터가 리턴된다.
- FILE 포인터는 열린 파일을 나타낸다.
- fgetc() 함수와 fputc() 함수를 사용하여 파일에 문자 단위 입출력을 할 수 있다.
- fgets() 함수와 fputs() 함수를 이용하여 텍스트 파일에서 한 줄씩 읽 거나 쓸 수 있다.
- fread()와 fwrite() 함수는 한 번에 일정한 크기의 데이터를 파일에 읽거나 쓴다.
- 열린 파일에서 다음 읽거나 쓸 파일 내 위치를 현재 파일 위치라고 하며 파일 위치 포인터가 그 파일의 현재 파일 위치를 가리키고 있다.
- fseek() 함수는 현재 파일 위치를 지정한 위치로 이동시킨다.
댓글남기기