IT공부/윤성우 열혈 C프로그래밍
C언어의 깊은 이해_3
doublehyun
2022. 2. 10. 15:37
Chapter.4 파일 입출력
- 파일과 스트림(Stream), 그리고 기본적인 파일의 입출력
- 파일에 저장되어 있는 데이터 읽기
- 파일에 저장되어 있는 데이터를 참조하기 위해서는 파일과 프로그램 사이의 통로를 만들어야 한다
- 이때 프로그램과 파일을 잇는 통로 역활을 하는것이 Stream 스트림 이다
- 스트림의 진행방향은 꼭 한 방향이다
- 이 스트림의 형성은 운영체제의 몫이고 우리는 스트림 생성을 요구하면 된다
- fopen함수 호출을 통한 파일과의 스트림 형성과 FILE 구조체
- fopen 함수는 스트림을 형성(요구) 할 때 호출하는 함수이다
- FILE * fopen(const char* filename, const char * mode); // 성공시 FILE 구조체의 변수 주소 값, 실패시 NULL
- 첫 인자는 스트림을 형성할 파일의 이름, 두번째 인자로는 스트림 종류에 대한 정보
- fopen 함수 호출 -> FILE 구조체 변수 형성 -> 구조체 변수에 파일에 대한 정보가 담긴다 즉 파일을 가리키는 지시자 역활
- fopen 함수는 스트림을 형성(요구) 할 때 호출하는 함수이다
- 입력 스트림과 출력 스트림의 생성
- 출력 스트림의 형성을 요구하는 fopen / FILE * fp = fopen("data.txt", "wt"); // 파일 data.txt 과 wt모드로 스트림 형성
- 입력 스트림의 형성을 요구하는 fopen / FILE * fp = fopen("data.txt", "rt"); // 파일 data.txt 과 rt모드로 스트림 형성
- cf) 파일과 스트림이 형성된 경우를 파일이 열렸다(개방, 오픈) 이라고도 표현한다
- 파일에 데이터 입력하기
- 출력 모드로 파일 열기 / FILE * fp = fopen("data.txt", "wt"); // 이때 파일에 경로 지정 가능!
- 파일에 데이터 입력하기 / fput('A', fp); // fput의 두번째 인자인 stream 에 파일 구조체 포인터를 입력해 준다
- 스트림의 연결을 종료해 주기 / fclose(fp);
- 스트림의 소멸을 요청하는 fclose 함수
- int fclose(FILE * stream); // 성공시 0, 실패시 EOF 반환
- 파일을 닫아주는 이유
- 운영체제가 할당한 자원의 반환
- 버퍼링 되었던 데이터의 출력
- 이때 출력버퍼의 데이터를 비우기 위해선 fflush 함수를 사용해도 된다
- 입력 버퍼의 경우 데이터를 비우면 소멸되기 때문애 fflush 함수를 사용할 수 없다!!!
- 파일로부터 데이터 입력받기
- 입력 모드로 파일 열기 / FILE * fp = fopen("data.txt", "rt");
- 파일에서 데이터 입력받기 / int ch = fgetc(fp);
- 스트림의 연결을 종료해 주기 / fclose(fp);
- 파일에 저장되어 있는 데이터 읽기
- 파일의 개방 모드(Mode)
- 스트림을 구분하는 기준1: 읽기위해? or 쓰기위해?
- 스트림을 구분하는 기준2: 텍스트 모드와 바이너리 모드
- 텍스트 파일과 바이너리 파일의 구분
- 텍스트 파일 : 문자 데이터 ex) 도서목록, 물품내역
- 바이너리 파일 : 컴퓨터가 인식하는 데이터 ex) 영상파일, 음원파일
- 개행문자 \n 에 관한 부가설명
- C언어에서는 \n을 개행문자로 인식하지만 모든 컴퓨터 환경이 그렇진 않다
- ex) Winodw = \r\n, MAC = \r, Unix 계열 = \n
- 이때 텍스트 모드로 파일을 열면 자동으로 변환해준다
- C언어에서는 \n을 개행문자로 인식하지만 모든 컴퓨터 환경이 그렇진 않다
- 텍스트 모드, 바이너리 모드의 구분
모드(mode) 스트림의 성격 b 바이너리 모드 t 텍스트 모드 ' '(선언X) 텍스트 모드
- 텍스트 파일과 바이너리 파일의 구분
- 파일 입출력 함수의 기본
- 전에 학습한 파일 입출력 함수를 이용한 파일 입출력
- int fputc(int c, FILE * stream) // 문자 출력
- int fgetc(FILE * stream) // 문자 입력
- int fputs(const char * s, FILE * stream) // 문자열 출력
- char * fgets(char * s, int n, FILE * stream) // 문자열 입력
- cf) 문자열을 파일에 저장할 때는 널문자로 구분 X, 개행문자(\n)으로 문자열을 구분하게 된다
- 따라서 문자열을 읽어들이는 함수를 호출하면 개행문자(\n)을 만날때 까지 게속 읽어들인다
- feof함수 기반의 파일복사 프로그램
- 파일의 마지막을 확인하는 feof함수
- int feof(FILE * stream); // 파일의 끝에 도달하면 0, 아닐경우 다른 값 반환
- 파일의 마지막을 확인하는 feof함수
- 바이너리 데이터의 입출력: fread, fwrite
- size_t fread(void * buffer, size_t size, size_t count, FILE * stream); // 성공시 전달인자 count, 실패시 작은값
- ex) int buf[12];, fread((void*)buf, sizeof(int), 12, fp); // 성공시 12반환, 실패시 12보다 작은 값 반환
- sizeof(int)크기의 데이터 12개를 fp로부터 읽어들여 배열 buf에 저장
- size_t fwrite(void * buffer, size_t size, size_t count, FILE * stream); // 성공시 전달인자 count, 실패시 작은값
- ex) int buf[7] = { ~ };, fwrite((void*)buf, sizeof(int), 7, fp); // 성공시 7반환, 실패시 7보다 작은 값 반환
- sizeof(int)크기의 데이터 7개를 buf로부터 읽어들여 fp에 저장
- size_t fread(void * buffer, size_t size, size_t count, FILE * stream); // 성공시 전달인자 count, 실패시 작은값
- 전에 학습한 파일 입출력 함수를 이용한 파일 입출력
- 텍스트 데이터와 바이너리 데이터를 동시에 입출력 하기
- 서식에 따른 데이터 입출력: fprintf, fscanf
- printf, scanf 함수와 동일하게 사용한다
- fprintf(fp, "%s %c %d", name, sex, age); // name문자열, sex문자, age바이너리 데이터를 파일에 출력함
- fscanf(fp, "%s %c %d", name, &sex, &age); // name문자열, sex문자, age바이너리 데이터를 파일에서 읽어들임
- 텍스트와 바이너리 데이터의 집합체인 구조체 변수의 입출력
- 구조체로 묶여있는 여러개의 데이터들을 하나의 바이너리 데이터로 인식시켜 입출력 시킬 수 있다
- fread, fwrite 함수 사용
- 서식에 따른 데이터 입출력: fprintf, fscanf
- 임의 점급을 위한 '파일 위치 지시자'의 이동
- 파일 위치 지시자란?
- FILE 구조체의 맴버 중 하나로, 파일의 위치 정보를 저장하고 있다
- fgets, fputs 를 사용하면 다음 문자열을 입출력 할 수 있게 파일 내의 위치를 참조하고 있다
- 파일 위치 지시자의 이동: fseek
- 파일 지시자를 이동시킬 때는 fseek 함수를 호출한다
- int fseek(FILE * stream, long offset, int wherefrom); // 성공시 0, 실패시 0이 아닌 값 반환
- stream으로 전달된 파일 위치 지시자를 wherefrom 으로부터 offset 바이트 만큼 이동시켜라!
- 현재 파일 위치 지시자의 위치는?: ftell
- 현재의 파일 위치 지시자의 정보를 확인하고 싶다면 ftell 함수를 호출하면 된다
- long ftell(FILE * stream) // 파일 위치 지시자의 위치 정보 반환
- 첫번째 바이트를 가리킬 경우 0을 반환하고, 세번째 바이트를 가리킬 경우 2를 반환한다.
- 파일 위치 지시자란?
Chapter.5 메모리 관리와 메모리의 동적할당
- C언어의 메모리 구조
- 메모리의 구성
- 코드영역(Code Area) : 실행할 프로그램의 코드가 저장되는 메모리 공간
- 데이터 영역(Data Area) : 전역변후와 static으로 선언되는 static 변수가 할당되는 공간(프로그램 종료시 까지 유지)
- 힙 영역(Heap Area) : 프로그래머가 원하는 시점에 할당하여 사용하고, 반환할 수 있는 영역
- 스택 영역(Stack Area) : 지역변수와 매개변수가 할당되는 영역, 함수를 빠져나가면 소멸하게 된다!
- 메모리의 동적 할당
- 전역변수와 지역변수로 해결이 되지 않는 상황
- 문자열 배열을 함수로 반환하는 상황 -> (함수내에서 선언된 배열은 지역변수 이므로 함수가 끝나서 반환한 후에 사라진다 이때 반환된 값은 주소 값이기 때문에 주소 값이 가리키는 곳에는 아무것도 없는 상태가 된다)
- 배열을 전역변수로 선언했을 경우 -> 하나의 배열로 여러 값을 입력받을 수 없게 된다, 여려개의 전역변수를 할당하면 데이터 영역에서 프로그램에 부하를 주게 된다
- Solution = 생성과 소멸의 시기가 지역변수나 전역변수와 다른 유형의 변수를 선언해 준다!
- 힙 영역의 메모리 공간 할당과 해제: malloc 과 free 함수
- void * malloc(size_t size); // 힙 영역으로의 메모리 공간 할당 / -> 메모리 공간의 첫번째 바이트 주소 값 반환
- void free(void * ptr) // 힙 영역에 할당된 메모리 공간 해제
- malloc 함수는 성공시 할당된 메모리의 주소 값, 실패시 NULL 반환
- 이 주소 값을 이용하여 데이터에 접근하면 된다 즉 포인터 연산으로 값에 접근(연산) 한다!
- molloc 함수의 반환형이 void*(보이드 형 포인터)인 이유와 힙 영역의 접근
- molloc함수의 인자로는 숫자(Byte) 만 주기 때문에 포인터 형에 대한 결정을 스스로 할 수 없다
- 따라서 형변환을 사용하여 직접 포인터 형을 맞추어서 선언해 주어야 한다
- ex) int * ptr = (int*)malloc(sizeof(int)*7) // int형 포인터로 28byte 동적할당
- free 함수를 사용하지 않을 시 발행하는 문제점
- 결국 프로그램을 종료하면 힙 영역에 있던 변수들은 전부 헤제가 된다
- 그러나 프로그램의 효율적인 메모리 활용을 위해 사용해 주어야 하는것이 좋다;;
- malloc 함수와 비슷한 colloc 함수
- void * calloc(size_t elt_count, size_t elt_size); // 성공시 할당된 메모리 주소 값, 실패시 NULL
- malloc과 기능은 동일하지만 인자의 개수가 다르다, 초기화 차이가 있다
- calloc 은 elt_size(몇 바이트 크기의 블록) elt_count(개수) 를 힙 영역에 할당해 달라는 뜻
- ex) colloc(30, sizeof(int)); = malloc(120);
- malloc은 메모리 공간을 초기화 하지 않지만 colloc은 메모리 공간의 비트를 0으로 초기화 한다
- void * calloc(size_t elt_count, size_t elt_size); // 성공시 할당된 메모리 주소 값, 실패시 NULL
- 힙에 할당된 메모리 공간 확장 시 호출하는 realloc 함수
- 원래라면 한번 할당된 공간은 확장할 수 없다
- 그러나 힙 영역에 할당된 공간은 늘릴 수 있다
- void * realloc(void * ptr, size_t size); // 성공 시 새로 할당된 메모리의 주소 값, 실패시 NULL
- int * arr = (int*)malloc(sizeof(int)*3);
- arr = (int*)realloc(arr, sizeof(int)*7);
- 이때 메모리 공간 상태에 따라 실행 결과는 2가지로 나뉜다
- 기존 메모리를 할당한 영역 뒤에 확장할 영역이 넉넉한 경우
- 전 molloc 함수의 반환 주소 값과 realloc 함수의 반환 주소 값이 동일하다
- 기존 메모리 영역 뒤 확장할 영역이 없는 경우
- 전 molloc 함수의 반환 주소값과 다른 새로운 주소 값이 반환된다(새로운 곳에 공간을 새로 할당한다)
- 기존 메모리를 할당한 영역 뒤에 확장할 영역이 넉넉한 경우
- 전역변수와 지역변수로 해결이 되지 않는 상황