C언어 프로그래밍에서 외부 파일을 읽고 쓸 때, 대부분은 파일의 처음부터 끝까지 순차적으로 데이터를 처리하는 선형적인 방식을 사용합니다. 하지만 대용량 로그 파일에서 특정 날짜의 데이터만 골라 읽어야 하거나, 대규모 데이터베이스 파일의 특정 인덱스로 즉시 건너뛰어야 하는 상황에서는 처음부터 순독하는 방식이 매우 비효율적입니다. 파일 내부의 원하는 위치로 스크롤바를 움직이듯 커서를 자유롭게 이동시켜 필요한 데이터에 곧바로 접근하는 기술을 임의 접근(Random Access)이라고 부릅니다.
이러한 임의 접근을 구현하려면 파일 내부에서 현재 읽기나 쓰기 작업이 진행 중인 물리적 위치를 개발자가 직접 제어할 수 있어야 합니다. C 표준 라이브러리는 이를 위해 파일 위치 제어 핵심 함수들을 제공하고 있습니다. 이번 포스팅에서는 파일 포인터의 움직임을 결정하는 fseek, 현재 커서의 위치를 정확하게 짚어내는 ftell, 그리고 처음으로 데이터를 되감아주는 rewind 함수의 동작 원리와 매개변수 구조를 살펴보고, 실무에서 대용량 파일의 크기를 계산할 때 사용하는 핵심 기법까지 상세히 정리해 보겠습니다.

핵심 요약 3줄
- 파일 임의 접근은 FILE * 구조체 내부의 위치 표시자(Cursor)를 원하는 바이트 위치로 강제 이동시켜 데이터를 필요한 곳부터 읽고 쓰는 기술입니다.
- fseek 함수는 세 가지 기준점(시작, 현재, 끝)과 오프셋을 조합하여 위치를 이동시키고, ftell 함수는 시작점으로부터의 현재 바이트 거리를 측정합니다.
- 운영체제별 개행 문자 변환 메커니즘 차이로 인해 텍스트 모드에서는 오차가 생길 수 있으므로, 정밀한 위치 제어에는 바이너리 모드(b) 활용이 필수적입니다.
1. 파일 포인터와 위치 표시자의 개념
C언어에서 파일은 일련의 바이트가 연속적으로 이어져 있는 스트림(Stream) 형태로 간주됩니다. fopen 함수를 통해 파일 스트림을 열면 시스템은 내부적으로 FILE 구조체를 생성하고 이 자원의 주소를 반환합니다. 이 구조체 안에는 파일의 제어 상태뿐만 아니라, 다음에 읽거나 쓸 바이트의 위치를 가리키는 '위치 표시자(Position Indicator)'가 포함되어 있습니다. 우리가 fread나 fgets 같은 함수를 호출할 때마다 이 커서는 읽어들인 바이트 수만큼 자동으로 뒤로 이동합니다. 임의 접근 함수들은 이 자동 이동 메커니즘을 깨고 개발자가 원하는 좌표로 커서를 강제 도약시키는 도구입니다.
2. fseek 함수: 파일 포인터 위치 도약하기
fseek 함수는 기준점으로부터 지정한 바이트 수만큼 파일 위치 표시자를 앞으로 전진시키거나 뒤로 후진시키는 임의 접근의 핵심 명령입니다.
2.1 함수 명세 및 매개변수 구조
int fseek(FILE *stream, long offset, int whence);
- stream: 위치를 변경하고자 하는 타깃 파일 포인터입니다.
- offset: 기준점으로부터 이동할 상대적인 거리(바이트 단위)입니다. 양수를 입력하면 파일의 뒤쪽(끝 방향)으로 전진하고, 음수를 입력하면 파일의 앞쪽(시작 방향)으로 후진합니다.
- whence: 이동 연산을 수행할 때 출발지가 되는 고정 기준점입니다. 미리 정의된 세 가지 매크로 상수를 사용합니다.
2.2 fseek 이동 기준점(whence) 매크로 상수 일람표
| 매크로 상수 | 실제 정수값 | 이동의 출발 기준점 | 실무 주요 활용 예시 |
| SEEK_SET | 0 | 파일의 가장 첫 번째 바이트 지점 | 파일 서두의 헤더 정보나 특정 고정 오프셋 영역으로 이동할 때 |
| SEEK_CUR | 1 | 현재 파일 위치 표시자가 머물고 있는 지점 | 방금 읽은 데이터 블록을 건너뛰거나 바로 앞 영역을 재조회할 때 |
| SEEK_END | 2 | 파일의 가장 마지막 데이터 지점(EOF) | 파일의 전체 크기를 측정하거나 끝에 새 로그를 이어 붙일 때 |
3. ftell 함수와 파일 크기 측정 응용
ftell 함수는 파일의 맨 처음 시작점(0번지)을 기준으로 현재 파일 위치 표시자가 몇 바이트만큼 떨어져 있는지 롱 정수형(long) 데이터로 알려주는 측정 함수입니다.
3.1 함수 명세 및 리턴값
long ftell(FILE *stream);
- 반환값: 성공 시 0 이상의 바이트 위치를 반환하며, 하드웨어 오류나 지원하지 않는 스트림일 경우 -1L을 반환합니다.
3.2 실무 필수 테크닉: 파일 전체 크기(Byte) 동적 계산법
이 기법은 실무에서 파일 데이터를 메모리에 한 번에 malloc하여 로드할 때 배열의 크기를 사전에 결정하기 위해 고정적으로 사용하는 알고리즘 패턴입니다.
// 1단계: 파일의 커서를 맨 끝(SEEK_END)으로 도약시킵니다.
fseek(fp, 0, SEEK_END);
// 2단계: 맨 끝에 위치한 커서의 번지수를 읽어옵니다. 시작점부터의 누적 거리가 곧 전체 파일 크기입니다.
long fileSize = ftell(fp);
printf("타깃 파일의 총 물리 크기: %ld 바이트\n", fileSize);
4. rewind 함수: 처음으로 되감기
rewind 함수는 파일 내부에서 어떤 위치 제어 작업을 수행했든 상관없이 위치 표시자를 파일의 맨 첫 바이트 공간(0번지)으로 즉시 원점 회귀시키는 역할을 수행합니다.
4.1 함수 명세 및 특징
void rewind(FILE *stream);
- fseek(fp, 0, SEEK_SET); 명령과 완벽하게 동일한 내부 결과물을 만들어내지만, 코드가 훨씬 직관적이고 가독성이 좋습니다.
- 추가적으로 파일 스트림 내부의 오류 플래그(ferror)와 파일 끝 도달 상태 플래그(feof)까지 깨끗하게 초기화해 주는 특성이 있어, 파일 독출 작업을 마친 뒤 다시 처음부터 2차 스캔을 시작해야 할 때 매우 안전하게 사용할 수 있습니다.
5. 실전 종합 시나리오: 파일 크기 동적 추적 및 중간 데이터 타깃 독출
아래 소스 코드는 외부 파일을 생성하여 문자열을 기록한 뒤, 임의 접근 함수 3인방을 조합하여 파일 크기를 계산하고, 파일의 한가운데 지점으로 건너뛰어 데이터를 부분 독출한 뒤, rewind를 통해 원점으로 회귀하는 일련의 과정을 담고 있습니다.
#include <stdio.h>
int main() {
// 실습을 위한 텍스트 파일 생성 및 샘플 데이터 저장
FILE *fp = fopen("random_access.txt", "w+");
if (fp == NULL) {
perror("파일 개방 에러");
return 1;
}
fputs("ABCDEFGHIJ", fp); // 10바이트 데이터 기록
fflush(fp);
// 1단계: ftell과 fseek 조합으로 파일 크기 구하기
fseek(fp, 0, SEEK_END);
long totalSize = ftell(fp);
printf("[정밀 측정] 파일의 총 크기: %ld 바이트\n", totalSize);
// 2단계: 파일의 정확한 중간 지점(5번째 바이트)으로 다이렉트 점프
long middleOffset = totalSize / 2;
fseek(fp, middleOffset, SEEK_SET);
printf("현재 포인터 위치 점크 완료: %ld 바이트 지점\n", ftell(fp));
// 3단계: 점프한 위치에서 데이터 1바이트 읽어보기
int ch = fgetc(fp);
printf("중간 위치에서 읽어온 문자 데이터: %c (현재 커서 번지: %ld)\n", ch, ftell(fp));
// 4단계: rewind 함수를 사용해 처음으로 파일 포인터 되감기
rewind(fp);
printf("rewind 호출 성공 후 최종 커서 위치: %ld 바이트 번지\n", ftell(fp));
fclose(fp);
return 0;
}
6. 실무 개발자를 위한 흔히 하는 실수
- 텍스트 모드와 바이너리 모드의 변환 편차 간과
- 윈도우 환경의 텍스트 모드("r", "w")에서는 줄 바꿈 문자(\n)를 파일에 저장할 때 커널 레벨에서 자동으로 두 바이트짜리 캐리지 리턴 문자가 포함된 \r\n으로 변환하여 기록합니다. 이 상태에서 ftell을 호출하면 컴파일러 내부의 버퍼링 계산 규칙이 꼬이면서 화면상으로 1글자처럼 보여도 실제 리턴되는 바이트 번지수가 다를 수 있습니다. 따라서 정밀한 바이트 연산이 필요한 프로그램에서는 반드시 바이너리 모드("rb", "wb")로 스트림을 생성해야 합니다.
- 비지원 스트림(표준 입력 장치 등)에 대한 임의 접근 시도
- fseek나 ftell은 하드디스크처럼 앞뒤로 이동이 자유로운 '블록 디바이스 파일'에서만 정상 가동됩니다. 실시간으로 데이터가 유입되는 네트워크 소켓이나 키보드 표준 입력 스트림(stdin)에 대고 fseek를 호출하면 내부 위치 제어가 불가능하므로 예외 처리 없이 런타임 논리 에러로 이어집니다.
- fseek 호출 후 파일 위치 포인터의 범위 이탈
- fseek(fp, 100, SEEK_END);와 같이 실제 파일의 물리적인 크기 경계선보다 훨씬 뒤쪽의 보이지 않는 영역으로 오프셋을 설정해도 함수 자체는 에러를 뿜지 않고 0(성공)을 반환하는 경우가 많습니다. 이 상태에서 파일 쓰기를 시도하면 중간 공간이 공백 데이터로 채워지며 파일 구조가 깨지는 스파스 파일(Sparse File) 변형 문제가 발생할 수 있습니다.
7. 생산성을 높이는 개발 팁
- fseek 성공 여부 검증 반환값 체크 자동화
- fseek 함수는 작업이 정상적으로 완수되면 0을 반환하고, 범위를 완전히 벗어났거나 장치 접근 오류가 발생하면 0이 아닌 값(일반적으로 -1)을 반환합니다. 실무형 방어 코드를 짤 때는 중요한 도약 포인트마다 if (fseek(fp, offset, SEEK_SET) != 0) 문법을 결합하여 도약 실패 시 로그를 남기고 스캔 작업을 중단하는 가드 코드를 배치하는 것이 견고합니다.
- 구조체 파일 저장 시 특정 인덱스로 고속 탐색 활용
- 크기가 고정된 회원 정보 구조체 배열(sizeof(User)) 데이터를 이진 파일로 순차 저장해 두었다면, 50번째 회원의 정보를 읽어오기 위해 반복문을 50번 돌릴 필요가 없습니다. 아래의 복합 패턴을 적용하면 단 1회의 디스크 탐색 연산만으로 타깃 데이터 레이아웃을 즉시 인메모리로 스냅샷 복사해 올 수 있습니다.
Cfseek(fp, sizeof(User) * 49, SEEK_SET); fread(¤tUser, sizeof(User), 1, fp); - 파일 크기 계산 후 원점 복귀 습관화
- 앞서 언급한 SEEK_END 기반의 파일 물리 크기 측정 알고리즘을 사용하고 나면, 내부 커서는 무조건 파일의 가장 끝단(EOF)에 걸려 정체되어 있습니다. 이 상태를 방치하고 바로 아래 라인에서 데이터를 읽으려고 하면 아무것도 읽히지 않는 버그가 발생하므로, 파일 크기를 추출한 직후에는 무조건 rewind(fp);를 세트로 호출해 주는 코드 흐름을 프로그래밍 규칙으로 내재화하는 것이 안전합니다.
맺음말
C언어 프로그래밍에서 fseek, ftell, rewind 함수를 조합하여 임의 접근을 매끄럽게 제어할 수 있게 되면 데이터 스트림을 입체적인 메모리 공간처럼 다룰 수 있는 시야가 열립니다. 대용량 바이너리 파일을 직접 파싱하거나 임베디드 환경에서 제한된 하드웨어 리소스를 사용해 인덱스 기반 파일 시스템을 커스텀 빌드해야 할 때 이 삼총사 함수는 최고의 효율성을 보장합니다. 다루고 있는 파일 입출력 컴포넌트 중 순차 탐색으로 인해 실행 지연이 발생하는 영역이 있다면 임의 접근 로직을 도입하여 시스템의 파일 입출력 처리율을 극대화해 보시기 바랍니다.
'Core Programming > C Standard Library: Resource & Performan' 카테고리의 다른 글
| C언어 문자열 안전하게 다루기: strlen 원리부터 strcpy, strncpy 버퍼 오버플로우 방지까지 (0) | 2025.02.09 |
|---|---|
| C언어 파일 에러 처리 가이드: feof 오동작 방지부터 ferror 상태 초기화까지 (0) | 2025.02.08 |
| C언어 구조화된 파일 제어: fprintf와 fscanf 활용법 및 데이터 파싱 예외 처리 (0) | 2025.02.06 |
| C언어 구조체 파일 저장의 핵심: fopen 모드 설정부터 fread, fwrite 실무 가이드 (0) | 2025.02.05 |
| C언어 문자열 제어의 핵심: fputs와 fgets 함수 동작 원리 및 실무 활용 가이드 (0) | 2025.02.04 |