ko ko ko en en en ko ko cs zh ko ko ko ko ko ko ko ko ko ko ko ko fr ko ko ru en ko en ko ko ko en ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko en ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko ko fr ko ko ko ko fr ko ko ko en en ko en zh en en
C 언어 코딩 도장: 82.4 아카이브 파일 생성 및 첫 파일 추가하기
◀ 82.3 프로젝트를 만들고 소스 파일 추가하기82.5 파일 목록 출력하기 ▶︎
아무것도 없는 상태에서 프로그램을 만들려면 좀 막막하죠. 그래서 먼저 테스트용 아카이브 파일을 만들어보겠습니다.
그림 82‑10은 아카이브 파일을 생성하고 첫 파일을 추가하는 과정입니다.
▼ 그림 82‑10 아카이브 파일에 첫 파일을 추가하는 과정
getFileSize: 파일의 크기를 구하는 함수
append: 아카이브 파일에 파일을 추가하는 함수
이제 main.c 파일이 있는 폴더에 다음 내용을 hello.txt로 저장합니다. 이 파일이 아카이브 파일에 추가될 파일입니다.
Visual Studio에서 Ctrl+F5 키를 눌러 프로그램을 실행하면 main.c 파일이 있는 폴더에 archive.bin 파일이 생성됩니다. 이 파일을 Visual Studio에서 열어보면 다음과 같은 모양으로 보일 것입니다.
참고 | 명령 프롬프트에서 실행하기
먼저 탐색기에서 프로젝트 디렉터리의 Debug 디렉터리로 이동한 뒤 hello.txt 파일을 생성하고 "Hello, world!" 문자열을 저장합니다(c:\project\filearchive에 만들었습니다).
이제 윈도우 키 + R을 누른 뒤 cmd.exe를 입력하여 명령 프롬프트를 실행합니다. 그리고 프로젝트 디렉터리의 Debug 디렉터리로 이동한 뒤 다음과 같이 실행합니다.
드디어 최초의 아카이브 파일이 만들어졌습니다. hello.txt 파일의 내용이 몇 글자 안 되어서 파일 이름 부분(256바이트)이 훨씬 크게 나오지만 정상입니다. 만약 크기가 큰 파일을 추가했다면 파일 이름 부분보다 파일 데이터 부분이 더 크게 보일 것입니다.
이제 소스 코드를 자세히 알아보겠습니다. 앞에서 정의한 ARCHIVE_HEADER, FILE_DESC 구조체 이외에도 다음과 같이 ARCHIVE 구조체를 추가했습니다. 이번 예제에서는 구조체가 두 가지 종류로 나뉘는데 ARCHIVE_HEADER, FILE_DESC 구조체와 같이 아카이브 파일에 그대로 저장되는 구조체와 아카이브 파일에는 저장되지 않아도 프로그램에서 각종 데이터를 다루기 위한 구조체가 필요합니다.
ARCHIVE 구조체는 파일 아카이브 프로그램에서 메인 구조체로 사용되며 아카이브 헤더와 아카이브 파일의 파일 포인터를 가지고 있습니다. 이후 각종 함수를 실행할 때마다 ARCHIVE 구조체에서 필요한 정보를 꺼내 쓰겠습니다.
만약 파일 저장용 구조체와 프로그램용 구조체를 분리하지 않고, 하나로 뭉쳐서 구현하게 되면 구조체를 파일에 저장했을 때 필요 없는 데이터까지 함께 파일에 저장됩니다. 그래서 예제에서는 파일 저장용 구조체 ARCHIVE_HEADER, FILE_DESC와 프로그램용 구조체 ARCHIVE를 나누었습니다. 여러분들도 앞으로 파일에 데이터를 저장한다면 파일에 저장할 부분과 저장하지 않을 부분을 구분해서 설계하세요.
아카이브 파일 이름은 매크로로 정의했습니다. 자주 사용되는 문자열인 만큼 매크로를 사용하면 편리합니다.
아카이브 파일에 새 파일을 저장할 때 파일의 크기를 알아야 하므로 파일의 크기를 구하는 함수가 필요합니다.
getFileSize 함수는 매개변수로 파일 이름이 아닌 파일 포인터를 받습니다. 따라서 함수 바깥에서 fopen으로 파일을 연 뒤 사용해야 합니다.
파일 크기를 구하는 방법은 간단합니다. fseek 함수를 이용하여 파일의 끝으로 이동한 뒤 ftell 함수로 현재 파일 포인터의 위치를 구하면 되겠죠? 단, 아카이브 파일과 같은 바이너리 파일에서는 파일 포인터의 위치가 중요하므로 이전 위치를 저장해두었다가 파일 크기를 구한 뒤 다시 되돌려놓습니다.
아카이브 파일의 파일 포인터는 반드시 ARCHIVE 구조체 변수의 멤버에 저장해서 이후 파일 포인터가 필요할 때마다 가져다 쓸 수 있도록 만듭니다. 또한, append(archive, "hello.txt");와 같이 append 함수에 archive와 추가할 파일 이름을 넣습니다.
int main() { PARCHIVE archive = malloc(sizeof(ARCHIVE)); memset(archive, 0, sizeof(ARCHIVE)); FILE *fp = fopen(ARCHIVE_NAME, "r+b"); // 아카이브 파일을 읽기/쓰기 모드로 열기 if (fp == NULL) // 아카이브 파일이 없으면 { fp = fopen(ARCHIVE_NAME, "w+b"); // 아카이브 파일을 생성 if (fp == NULL) // 파일 생성(열기)에 실패하면 return -1; // 프로그램 종료 // 새 아카이브 헤더 생성 archive->header.magic = 'AF'; // 매직 넘버 AF 저장(리틀 엔디언에서는 FA로 저장됨) archive->header.version = 1; // 파일 버전 1 저장 // 아카이브 파일에 아카이브 헤더 저장 if (fwrite(&archive->header, sizeof(ARCHIVE_HEADER), 1, fp)  fp = fp; // 아카이브 파일 포인터 저장 append(archive, "hello.txt"); // hello.txt 파일 추가 } fclose(fp); // 아카이브 파일 포인터 닫기 free(archive); // 동적 메모리 해제 return 0; }
main 함수에서 아카이브 파일에 아카이브 헤더를 저장하면 다음과 같은 모양이 됩니다.
▼ 그림 82‑11 아카이브 파일에 아카이브 헤더 저장
이제 파일 추가 함수입니다. 코드가 길어서 좀 복잡해 보이지만 어렵지 않습니다. 먼저 추가할 파일을 열고 파일 크기를 구합니다. 그리고 파일 크기만큼 버퍼를 할당한 뒤 추가할 파일의 내용을 읽어서 버퍼에 저장합니다.
int append(PARCHIVE archive, char *filename) // 파일 추가 함수 정의 { int ret = 0; // 함수가 성공했는지 실패했는지 반환값으로 표현 // 추가할 파일 열기 FILE *fp = fopen(filename, "rb"); if (fp == NULL) { printf("%s 파일이 없습니다.\n", filename); return -1; // 함수 종료. -1은 실패 } uint8_t *buffer; uint32_t size; size = getFileSize(fp); // 추가할 파일의 크기를 구함 buffer = malloc(size); // 추가할 파일의 내용을 읽음 if (fread(buffer, size, 1, fp) < 1) { printf("%s 파일 읽기 실패\n", filename); ret = -1; // -1은 실패 goto Error1; // fp를 닫고 buffer를 해제하는 에러 처리로 이동 }
앞으로 각 과정을 실패할 때마다 goto를 사용하여 에러 처리 부분으로 이동하고, -1을 반환하도록 하겠습니다(모든 과정을 성공하면 0을 반환). 에러 처리는 별다른 게 없으며 파일을 열었거나 메모리를 할당했다면 파일 포인터를 닫고, 메모리를 해제하는 코드입니다. 이 상황에서 goto를 사용하지 않으면 실패할 때마다 매번 fclose나 free 함수를 계속 사용해야 하므로 코드가 길어집니다(참고로 파일 포인터나 포인터가 NULL이라면 닫거나 해제할 필요가 없습니다).
int ret = 0; // 함수가 성공했는지 실패했는지 반환값으로 표현 // 생략... { printf("%s 파일 읽기 실패\n", filename); ret = -1; // -1은 실패 goto Error1; // fp를 닫고 buffer를 해제하는 에러 처리로 이동 }
추가할 파일의 내용을 읽었다면 새 파일 정보를 생성합니다. 여기서는 쓸데없는 값이 들어가지 않도록 memset 함수로 할당한 메모리 공간을 0으로 초기화해 줍니다. 또한, 추가할 파일의 이름과 크기를 저장합니다.
// 새 파일 정보 생성 PFILE_DESC desc = malloc(sizeof(FILE_DESC)); memset(desc, 0, sizeof(FILE_DESC)); strcpy(desc->name, filename); // 파일 정보 구조체에 추가할 파일의 이름 저장 desc->size = size; // 파일 정보 구조체에 추가할 파일의 크기 저장
파일 정보를 아카이브 파일에 써야 하는데 먼저 위치를 정해야 합니다. 따라서 fseek 함수를 사용하여 ARCHIVE_HEADER 구조체 크기만큼 파일 포인터를 순방향으로 이동시켜서 아카이브 헤더 바로 뒤에 파일 정보를 쓸 수 있도록 만듭니다.
파일 데이터의 시작 위치는 아카이브 파일에서 현재 파일 포인터의 위치에 파일 정보(FILE_DESC) 구조체 크기만큼 순방향으로 이동시킨 값입니다. 이 값도 파일 정보 구조체의 dataOffset에 저장해줍니다.
// 아카이브 헤더 바로 다음으로 파일 포인터를 이동시킴 fseek(archive->fp, sizeof(ARCHIVE_HEADER), SEEK_SET); // 파일 데이터의 시작 위치는 현재 파일 포인터의 위치에 // 파일 정보 크기만큼 앞으로 설정 desc->dataOffset = ftell(archive->fp) + sizeof(FILE_DESC);
파일 포인터의 이동과 파일 데이터의 시작 위치를 그림으로 표현하면 다음과 같은 모양이 됩니다.
▼ 그림 82‑12 아카이브 헤더 바로 다음으로 파일 포인터를 이동시킴, 파일 데이터의 위치 구하기
모든 준비가 끝났으면 아카이브 파일에 새 파일 정보를 쓴 다음에 새 파일 데이터를 쓰면 됩니다. fwrite 함수로 파일을 쓰면 쓴 크기만큼 파일 포인터가 순방향으로 이동하므로 따로 fseek 함수를 사용하지 않아도 됩니다.
// 아카이브 파일에 새 파일 정보 쓰기 if (fwrite(desc, sizeof(FILE_DESC), 1, archive->fp)  fp) < 1) { printf("파일 데이터 쓰기 실패\n"); ret = -1; goto Error2; // fp를 닫고, desc와 buffer를 해제하는 에러 처리로 이동 } printf("%s 파일 추가 성공\n크기: %d\n", filename, size);
마지막으로 에러 처리 부분입니다. 여기서는 파일 열기나 메모리 할당한 순서의 역순으로 해제하도록 만듭니다. 그리고 free(desc)에는 Error2, free(buffer)와 fclose(fp)에는 Error1 레이블을 붙여줍니다. 이렇게 하면 goto를 사용하여 원하는 함수를 실행할 수 있습니다. 특히 Error2로 가면 그 아래에 있는 Error1의 코드도 실행되므로 fp가 열려 있고 buffer, desc가 모두 할당된 상황에서 에러 처리를 할 때 유용합니다. 모든 과정이 끝났으면 성공이냐 실패냐에 따라 0 또는 -1을 반환합니다.
Error2: free(desc); // 파일 정보 저장용 동적 메모리 해제 Error1: free(buffer); // 파일 내용 저장용 동적 메모리 해제 fclose(fp); // 파일 포인터 닫기 return ret; // 성공이냐 실패냐에 따라 0 또는 -1을 반환 }
다음은 아카이브 파일에 새 파일 정보(desc)를 쓴 다음에 새 파일 데이터(buffer)를 쓰는 과정입니다.
▼ 그림 82‑13 아카이브 파일에 파일 정보와 파일 데이터 쓰기
◀ 82.3 프로젝트를 만들고 소스 파일 추가하기82.5 파일 목록 출력하기 ▶︎
C 언어 코딩 도장 생략
C 언어 코딩 도장
도서 구매 바로 가기
C 언어 코딩 도장
Unit 1. 소프트웨어 교육과 C 언어
Unit 2. Visual Studio 설치하기
Unit 3. Hello, world!로 시작하기
Unit 4. 기본 문법 알아보기
Unit 5. 변수 만들기
Unit 6. 디버거 사용하기
Unit 7. 정수 자료형 사용하기
Unit 8. 실수 자료형 사용하기
Unit 9. 문자 자료형 사용하기
Unit 10. 상수 사용하기
Unit 11. 입력 값을 변수에 저장하기
Unit 12. 덧셈, 뺄셈하기
Unit 13. 증가, 감소 연산자 사용하기
Unit 14. 곱셈, 나눗셈하기
Unit 15. 나머지 연산하기
Unit 16. 자료형의 확장과 축소 알아보기
Unit 17. if 조건문으로 특정 조건일 때 코드 실행하기
Unit 18. else를 사용하여 두 방향으로 분기하기
Unit 19. else if를 사용하여 여러 방향으로 분기하기
Unit 20. 비교 연산자와 삼항 연산자 사용하기
Unit 21. 논리 연산자 사용하기
Unit 22. 불 자료형 사용하기
Unit 23. 비트 연산자 사용하기
Unit 24. 비트 연산자 응용하기
Unit 25. 연산자 우선순위 알아보기
Unit 26. switch 분기문으로 다양한 조건 처리하기
Unit 27. for 반복문으로 Hello, world! 100번 출력하기
Unit 28. while 반복문으로 Hello, world! 100번 출력하기
Unit 29. do while 반복문으로 최소 한 번은 실행하기
Unit 30. break, continue로 반복문 제어하기
Unit 31. 계단식으로 별 출력하기
Unit 32. goto로 프로그램의 흐름을 원하는 대로 바꾸기
Unit 34. 포인터 사용하기
Unit 35. 메모리 사용하기
Unit 36. 배열 사용하기
Unit 37. 2차원 배열 사용하기
Unit 38. 포인터와 배열 응용하기
Unit 39. 문자열 사용하기
Unit 40. 입력 값을 문자열에 저장하기
Unit 41. 문자열의 길이를 구하고 비교하기
Unit 42. 문자열을 복사하고 붙이기
Unit 43. 문자열 만들기
Unit 44. 문자열 검색하기
Unit 45. 문자열 자르기
Unit 46. 문자열과 숫자를 서로 변환하기
Unit 47. 회문 판별과 N-gram 만들기
Unit 48. 구조체 사용하기
Unit 49. 구조체 포인터 사용하기
Unit 50. 두 점 사이의 거리 구하기
Unit 51. 구조체 멤버 정렬 사용하기
Unit 52. 구조체와 메모리 활용하기
Unit 53. 구조체 배열 사용하기
Unit 54. 공용체 사용하기
Unit 55. 구조체와 공용체 활용하기
Unit 56. 구조체 비트 필드 사용하기
Unit 57. 열거형 사용하기
Unit 58. 자료형 변환하기
Unit 59. 포인터 연산 사용하기
Unit 60. 함수 사용하기
Unit 61. 함수에서 반환값 사용하기
Unit 62. 함수에서 매개변수 사용하기
Unit 63. 함수에서 포인터 매개변수 사용하기
Unit 64. 함수에서 배열 매개변수 사용하기
Unit 65. 함수에서 구조체 매개변수 사용하기
Unit 66. 함수에서 가변 인자 사용하기
Unit 67. 함수에서 재귀호출 사용하기
Unit 68. 함수 포인터 사용하기
Unit 69. 함수 포인터 활용하기
Unit 70. 파일에서 문자열을 읽고 쓰기
Unit 71. 파일 포인터 활용하기
Unit 72. 파일에서 구조체를 읽고 쓰기
Unit 73. 배열 정렬하기
Unit 74. 연결 리스트 구현하기
Unit 75. 매크로 사용하기
Unit 76. 조건부 컴파일과 파일 포함 사용하기
Unit 77. 여러 소스 파일 사용하기
Unit 78. 전역 변수 사용하기
Unit 79. 기억 부류 지정자 사용하기
Unit 80. 실행 파일 옵션 사용하기
Unit 81. 실전예제: 비트맵 파일을 아스키 아트로 변환하기
Unit 82. 실전 예제: 파일 아카이브 구현하기
82.0 실전예제: 파일 아카이브 구현하기
82.1 파일 포맷 설계하기
82.2 아카이브 파일 구조체 작성하기
82.3 프로젝트를 만들고 소스 파일 추가하기
82.4 아카이브 파일 생성 및 첫 파일 추가하기
82.5 파일 목록 출력하기
82.7 기능별로 파일 나누기
82.8 실행 파일에서 명령 처리하기
82.10 심사문제: 아카이브 파일 읽기
Unit 83. 실전 예제: JSON 파일 읽고 쓰기
Unit 84. 실전 예제: GitHub의 소스 코드 사용하기
- 이 사이트에 실린 모든 내용, 디자인, 이미지, 편집 구성의 저작권은 (주)도서출판 길벗과 지은이에게 있습니다.
- 저작권법에 의해 보호를 받는 저작물이므로 무단 전재와 복제를 금합니다.
- 책의 내용을 복제하여 블로그, 웹사이트 등에 게시할 수 없습니다.
- 링크, SNS 공유는 허용합니다.
서울시 마포구 서교동 월드컵로 10길 56 (서울시 마포구 서교동 467-9)
Copyright © 2016-2021 Coding Dojang