6 입출력 개요

대부분의 프로그램은 무언가의 유용한 작업을 하기 위하여 입력(데이터 읽기) 또는 출력(데이터 쓰기)을 필요로 하거나, 흔히는 입출력 모두를 필요로 한다. GNU C 라이브러리는 아주 많은 입출력 함수들을 제공하고 있어서 가장 적합한 함수가 엄격하게 적용될 수 있도록 하고 있다. 이 장에서는 입력과 출력에 관련된 개념과 용어를 소개한다.GNU의 입출력 도구에 관련된 다른 장은 다음과 같다.

7장 [스트림 입출력]

형식화된 입출력을 포함하여 스트림을 처리하는 고수준 함수를 다룬다.
 
<역자주>스트림==소스로부터 입력되는 일련의 문자들

8장 [저수준 입출력]

기본적인 입출력과 파일 쓰기를 통제하는 함수를 다룬다.

9장 [파일 시스템 인터페이스]

디렉토리를 처리하거나 접근 모드나 소유권과 같은 파일속성에 관한 함수들을 다룬다.

10장 [파이프와 FIFO]

기본적인 내부처리 커뮤니케이션 도구들에 관한 정보를 담고 있다.

11장 [소켓]

네트워크를 지원하는 좀더 복잡한 내부 처리 커뮤니케이션 도구들을 다룬다.

12장 [저수준 터미널 인터페이스]

터미널이나 다른 연결 장치에 대한 입력과 출력을 처리하는 방식을 변경하는 함수들을 다룬다.


6.1 입출력의 개념

당신이 어떤 파일의 내용을 읽거나 쓸 수 있으려면 먼저 파일에 연결 채널이나 커뮤니케이션 채널을 설치하여야 한다. 이 과정을 파일열기라고 부른다. 당신은 파일을 읽기모드, 쓰기모드 또는 읽기쓰기 모드로 열 수가 있다. 열려진 파일과의 연결은 스트림으로 나타내거나 파일지시자로 나타낸다. 당신은 이것을 실제의 읽기쓰기 처리를 수행할 함수의 인수에 전달하여, 그 함수가 어떤 파일을 처리할 것인지를 알려준다. 어떤 함수들은 스트림을 받아서 처리하며, 다른 함수들은 파일지시자를 처리하도록 만들어져 있다. 당신이 파일 읽기나 쓰기를 마쳤을 때는, 그 파일을 닫아줌으로써 연결을 끝마칠 수 있다. 당신이 스트림이나 파일지시자를 일단 닫고나면, 더이상 그 파일에 입력이나 출력을 행할 수 없다.

 

6.1.1 스트림과 파일지시자

당신이 어떤 파일에 입력과 출력을 하고 싶을 때 당신의 프로그램과 파일간의 연결을 나타내기 위한 두 개의 기본적인 메커니즘을 선택해야 한다: 파일지시자와 스트림이 그것이다. 파일지시자는 int 형태의 대상물로 표현되고, 반면에 스트림은 구조체 FILE의 포인터 대상물(FILE * objects)로 표현된다. 파일지시자들은 입출력 처리에 대한 일차적이고 저수준인 인터페이스를 제공한다. 파일지시자나 스트림 모두 어떤 장치(터미널과 같은)로의 연결을 나타낼 수도 있고 보통의 파일뿐만 아니라 다른 프로세스와의 커뮤니케이션을 위한 파이프나 소켓과의 연결을 나타낼 수도 있다. 그러나, 만약 당신이 특정한 종류의 장치에 국한된 통제 처리를 하고 싶다면, 반드시 파일지시자를 사용해야만 한다. 이러한 방식에서 스트림을 사용하는 도구는 없다. 블럭화되지 않은(또는 등록된?) 입력(8.10[파일 상태 플래그] 참조)과 같이 특별한 모드로 입출력을 할 필요가 있을 때도, 당신은 반드시 파일지시를 사용하여야 한다.

스트림은 일차적인 파일지시자 도구들 위에 차곡차곡 쌓아 올려진 고수준의 인터페이스를 제공한다. 스트림 인터페이스는 아주 흡사한 모든 종류의 파일을 다루지만 당신이 선택할 수 있는 세 가지 양식의 버퍼(7.17 [스트림 버퍼] 참조)에서만 예외가 있다.

스트림 인터페이스를 사용하는 주된 이점은 스트림을 입출력 처리하는(통제처리와 다른 것으로서)함수들의 세트가 그에 상응하는 파일지시자 사용도구들에 비해서 훨씬 풍부하고 강력하다는 점이다. 파일지시자 인터페이스는 단순히 문자 블럭을 옮겨주는 함수들만을 제공하는 데 반해, 스트림 인터페이스는 문자단위 및 행단위 입출력뿐만 아니라 강력한 형식화된 입출력(printf와 scanf) 함수들까지 제공한다.

스트림은 파일지시자에 의해 완성되므로 당신은 스트림에서 파일지시자만을 뽑아내서 직접 파일지시자만을 저수준 처리할 수 있다. 당신은 처음에 파일지시자를 써서 (파일을) 연결하였다가, 나중에 스트림을 만들어서 그 파일지시자와 일치시킬 수도 있다.

일반적으로, 파일지시자로써만 작동시키고 싶은 어떤 특별한 처리가 아니라면, 파일지시자보다는 스트림을 사용하는 습관을 갖는 게 좋다. 만약 당신이 초보 프로그래머이고 사용할 함수들을 잘 모른다면, 당신은 집중적으로 형식화된 입력(7.11 [형식화된 입력] 참조)과 형식화된 출력(7.9 [형식화된 출력] 참조)만을 사용하는 게 좋다.

만약 당신이 GNU 이외의 다른 체제에서 프로그램을 운용하려 한다면, 당신은 파일지시자가 스트림보다 쉽지 않음을 알고 있어야 한다. 당신은 ANSI C를 쓰는 어떤 체제든 스트림을 제공한다고 기대해도 좋지만, GNU가 아닌 다른 체제에서는 파일지시자가 전혀 제공되지 않으며, 파일지시자를 처리하는 GNU 함수의 부수적인 부분을 완성할 뿐이다. 그러나,GNU 라이브러리의 파일지시자 함수의 대부분은 POSIX.1 표준에 포함된다.

 

6.1.2 파일 위치

열려진 파일의 속성 중의 하나는 파일에서 다음 문자를 읽어오거나 써야할 장소를 추적하고 있는 파일 위치다.GNU 체제에서는 모든 POSIX.1 체제와 마찬가지로 파일 위치는 그 파일의 시작부분으로부터의 바이트 수로 나타내지는 정수일 뿐이다.

파일위치는 보통 파일이 열렸을 때 파일의 시작위치에 놓여지고, 한 문자를 읽거나 쓸 때마다 파일위치가 증가한다. 달리 말하자면, 파일에의 접근은 정상적으로는 연속적이다.

보통의 파일들은 파일내의 어떤 위치에서든 읽기와 쓰기를 허용한다. 어떤 종류의 파일들은 이것을 허용하지 않기도 한다. 이것을 허용하는 파일들은 종종 랜덤 파일이라 불린다. 당신은 스트림에서 fseek 함수를 사용하여 (7.15 [파일 위치 결정] 참조) 파일지시자에서 lseek 함수를 사용하여 파일위치를 변경할 수 있다. 당신이 랜덤파일이 아닌 파일에서 파일위치를 변경하려 하면 ESPIPE 에러를 만날 것이다.

추가모드로 열린 스트림과 파일지시자는 특별히 출력을 위한 것이다: 그러한 파일에 대한 출력은 항상 파일위치에 상관없이 그 파일의 끝 부분에 연속적으로 추가한다. 그러나, 파일위치는 여전히 파일 내에서 읽기가 수행되는 곳을 통제하고 있다.

이와 같은 점을 생각해보면, 몇 개의 프로그램들이 하나의 파일을 동시에 읽을 수 있음을 알 수 있을 것이다. 각 프로그램이 자신의 페이스대로 파일을 읽기 위해서는 각기의 파일포인터를 가져야만 하고, 그 파일포인터는 다른 프로그램이 수행하는 어떤 일에 의해서도 영향받지 않아야 한다. 사실은, 각각의 파일열기는 다른 파일위치를 만든다. 그러므로, 만약 당신이 같은 프로그램에서 하나의 파일을 두 번 열었다 하더라도, 당신은 독자적인 파일위치를 갖는 두개의 스트림 또는 파일지시자를 갖게된다.

이와는 대조적으로, 당신이 만약 지시자를 열어서 다른 지시자와 중복되게 하였다면, 이 두개의 지시자는 같은 파일위치를 공유하게 된다. 하나의 지시자에서 파일위치를 변경시키면 다른 지시자도 영향을 받는다.


6.2 파일 명칭

어떤 파일을 연결하거나 파일 삭제와 같은 다른 처리를 위해서는 그 파일을 참조할 수 있는 어떤 방법이 필요하게 된다. 거의 모든 파일은 문자열로 된 명칭을 갖는다_테이프 드라이브나 터미널과 같은 사실상의 장치조차도 그와 같다. 이 문자열들을 파일명칭이라 부른다. 당신은 파일명칭을 지정하여 당신이 열거나 처리하고자 하는 파일이 어떤 것인지를 말해줘야 한다. 이 절에서는 파일명칭에 대한 규정들을 기술하고 운영 체제가 그것들을 취급하는 방식을 기술한다.

 

6.2.1 디렉토리

파일 명칭의 문장을 이해하려면, 파일체제가 디렉토리 계층으로 조직되는 법을 이해할 필요가 있다. 디렉토리는 다른 파일들을 명칭과 결합시켜주는 정보를 담고 있는 파일이다. 이 결합은 링크 또는 디렉토리 엔트리라 불린다. 흔히 사람들은 "디렉토리 내의 파일"이라고 말하지만, 사실은, 디렉토리는 파일에 대한 포인터를 담고 있을 뿐이지 파일 그 자체는 아니다.

디렉토리 엔트리에 담겨진 한 파일의 명칭은 파일명칭 성분으로 불린다. 일반적으로, 한 파일의 명칭은 슬래쉬('/') 문자로 분리된 하나 또는 그 이상의 연속물로 구성된다. 단 하나의 성분으로 이루어진 파일명칭은 그 디렉토리 내의 파일을 지칭한다. 여러개의 성분으로 이루어진 파일명칭은 어떤 디렉토리와 그 디렉토리 내의 파일 등을 지칭한다. POSIX 표준과 같은 어떤 다른 문서들에서는 소위 파일 명칭 대신에 경로명칭이란 용어를 사용하며, 이 안내서에서 파일명칭 성분이라고 부르는 것 대신에 파일명칭 또는 경로명칭 성분이란 용어를 사용한다. 우리는 이 용어를 사용하지 않는다. 왜냐하면,"경로"는 완전히 다른 것(찾고자하는 디렉토리들의 목록)이고, 우리가 생각하는 바로는 다른 것을 지칭하여 "경로명칭"이라고 해버리면 사용자들을 혼동시키기 때문이다.GNU 문서들에서는 항상 "파일명칭"과 "파일명칭 성분"만을 사용한다. 당신은 9장 [파일체제 인터페이스] 에서 디렉토리 처리에 관한 더 상세한 정보를 얻을 수 있다.

 

6.2.2 파일 명칭 분석

파일 명칭은 슬래쉬('/') 문자로 분리된 파일 명칭 성분으로 구성된다.GNU C 라이브러리가 지원하는 체제에서는 여러개의 연속적인 '/' 문자들은 단일한 '/' 문자와 동일하다.

파일 명칭이 어느 파일을 참조할 것인가를 결정하는 처리를 파일명칭 분석이라 부른다. 이 처리는 파일 명칭을 구성하고 있는 성분을 왼편에서 오른편으로 조사하여, 각각의 연속적인 성분을 앞의 성분에 의해 지칭된 디렉토리에 배치한다. 물론, 디렉토리로 참조되는 각각의 파일들은 실제로 존재해야 하며, 정규적인 파일 대신에 디렉토리여야 하고, 뿐만 아니라 처리에 의한 접근을 허용하는 적절한 권한이 있어야 한다.

만약 어떤 파일명칭이 '/'로 시작되면 그 파일명칭의 최초의 성분은 그 처리의 루트 디렉토리에 위치한다.(언제나 체제에서의 모든 처리는 같은 루트디렉토리를 갖는다.) 그와 같은 파일명칭은 절대적 파일명칭이라 부른다.

그렇지 않을 경우, 파일명칭에서의 최초 성분이 현재의 작업 디렉토리에 위치한다.(9.1 [작업 디렉토리] 참조) 이러한 종류의 파일명칭은 상대적 파일명칭이라 부른다.

파일명칭 성분 '.'("점")과 '..'("점-점")은 특별한 의미를 갖는다. 모든 디렉토리는 이 파일명칭 성분들을 위한 엔트리를 갖는다. 파일명칭 성분 '.'는 디렉토리 그 자체를 말하는 반면에, 파일명칭 성분 '..'은 그것의 부모 디렉토리(해당 디렉토리와의 연결을 갖는 디렉토리)를 말한다. 특별한 경우로서, 루트 디렉토리에 있는 '..'은 루트 디렉토리 그 자체를 말하는데, 왜냐하면 그것은 부모 디렉토리가 없기 때문이다; 이리하여,'/..'는 '/'과 같다. 파일명칭의 예를 보기로 하자.

'/a' '/a/b' 루트 디렉토리 내의 'a'라 불리는 디렉토리에 있는 'b'라는 파일.
'a' 현재의 작업 디렉토리에 있는 'a'라 불리는 파일.
`/a/./b' 이것은 '/a/b'와 같다.
'./a' 현재의 작업 디렉토리에 있는 'a'라는 파일.
'../a' 현재 작업 디렉토리의 부모 디렉토리 내의 'a'라는 파일.

디렉토리를 지칭하는 파일명칭이 '/'로 선택되어 끝날 수도 있다. 당신은 루트 디렉토리를 지칭하기 위하여 '/'라는 파일명칭을 규정할 수는 있지만, 공백문자는 의미 있는 파일명칭이 아니다. 만약 당신이 현재의 작업 디렉토리를 지칭하려 한다면,'.'나 '/' 라는 파일명칭을 사용하라.

다른 운영체제들과는 달리,GNU 체제는 파일명칭 문장의 일부로서 파일형태(또는 확장자)나 파일 버전 을 위한 기존의 지원자를 갖지 않는다. 많은 프로그램이나 유틸리티들은 파일명칭에 대한 규정_예를 들면, C 소스 코드를 담고 있는 파일들은 항상 '.c'를 뒤에 붙인 명칭을 가진다_을 사용하지만, 파일체제 그 자체에는 이와 같은 규정을 강제하는 것은 아무 것도 없다.

 

6.2.3 파일 명칭 에러

파일명칭 인수들을 받는 함수들은 항상 파일명칭 문장과 관련된 errno 에러조건을 검사한다. 이 에러들은 통상적인 파일명칭 에러가 생길 때마다 이 안내서 전체를 통해서 언급된다.

EACCES

처리가 파일명칭의 디렉토리 성분에 대한 허가권을 찾지 못하였다.

ENAMETOOLONG

파일명칭의 전체 길이가 PATH_MAX보다 더 크거나, 개별 파일명칭 성분이 NAME_MAX보다 더 큰 길이를 가질 때 이 에러가 사용된다.
GNU 체제에서는, 전체 파일 길이에 대한 주어진 제한은 없다. 그러나 어떤 파일 체제에서는 성분의 길이에 대한 제한들을 두고 있을 것이다.

ENOENT

파일명칭 내에서 디렉토리 성분으로서 지칭된 파일이 존재하지 않거나, 어떤 성분이 그 대상 파일이 존재하지 않는 부호링크일 때, 이 에러가 보고된다.

ENOTDIR

파일명칭 내의 디렉토리 성분으로 지칭된 파일이 존재하지만, 그것이 디렉토리가 아니다.

ELOOP

파일명칭을 찾는 동안 너무 많은 부호링크가 분해되었다. 체제는 루프를 조사할 일차적 방법으로서의 부호링크가 하나의 파일을 찾는데서 분해될 수 있는 개수를 임의로 제한하고 있다. 9.4 [부호 링크] 참조.

 

6.2.4 파일명칭의 운용

6.2 [파일명칭] 에서 언급한 파일명칭 문장에 대한 규칙은 GNU 체제와 다른 POSIX 체제에서 정상적으로 사용되는 규칙들이다. 그러나 다른 운영체제에서는 다른 규정들을 사용할 것이다. 당신이 파일명칭 운용 문제를 아는데서 그것이 중요할 수밖에 없는 두 가지 이유가 있다.

만약 당신의 프로그램이 파일명칭 문장에 가정문을 넣거나 삽입된 리터럴 파일명칭 문자열을 포함한다면 그것을 다른 문장 규정을 갖는 다른 운영 체제에서 실행하려할 때 어려움이 많을 것이다.

설령 당신이 다른 운영체제로 작동하는 장치에서 당신의 프로그램을 실행하려는 의사가 없다하더라도 다른 명칭 규정을 사용하는 파일들에 접근하는 일은 여전히 있음직한 일이다. 예를 들어, 당신은 네트워크 상에서 다른 운영체제를 작동시키는 다른 어떤 컴퓨터의 파일에 접근하거나 다른 운영체제에 의해 포맷된 디스크에서 읽기 쓰기를 할 수 도 있을 것이다.

ANSI C 표준에서는 파일명칭 문장에 관한 언급이 거의 없고 다만 파일명칭이 문자열이란 설명만 있다. 운영체제가 달라짐에 따라 파일명칭의 길이에 대한 제한규정과 파일명칭에 사용될 수 있는 문자의 종류가 변화할 뿐 아니라, 구조화된 디렉토리와 파일 형태, 확장자와 같은 개념에 대한 규정과 문장이 달라진다. 파일 버전과 같은 개념들은 어떤 운영체제에서는 지원되지만 그렇지 않은 경우도 있다.

POISIX.1 표준에서는 파일명칭에서 허용되는 문자와 관련하여 파일명칭 문장에 대한 추가적인 제한을 보충 할 수 있도록 하고 있으며, 또한, 파일명칭과 파일명칭 성분 문자열의 길이에 대한 추가적인 제한도 보충할 수 있도록 하고 있다. 그러나, GNU 체제에서는 당신은 이러한 제한에 대해 염려할 필요가 없다; 널 문자를 제외한 어떠한 문자든지 파일명칭 문자열에 허용되며, 파일명칭 문자열 길이에 대한 제한도 없다.