태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.
블로그 이미지

haRu™'s Thinks

느리게 걷기, 천천히 생각하기, 그리고 한 발 뒤에서 바라 보기… by haruroh


'Unix Programming'에 해당되는 글 32건

  1. 2007.12.22 11. Sockets
  2. 2007.12.22 10. Pipes and FIFO
  3. 2007.12.22 9. 파일시스템 인터페이스
  4. 2007.12.22 8. 저수준 입출력
  5. 2007.12.22 7. 스트림의 입출력
  6. 2007.12.22 6. 입출력 개괄
  7. 2007.12.22 5. 문자열과 배열 유틸리티
  8. 2007.12.22 4. 문자 처리
  9. 2007.12.22 3. 메모리 점유
  10. 2007.12.22 2. 에러 보고

11. Sockets

목차 이전 : 10. 파이프와 FIFO 다음 12 : 저수준 터미널 인터페이스


11 소켓

이 장에서는 소켓을 사용하여 프로세스간의 통신에 관한 GNU장치에 대해 서술한다.

소켓은 일반적인 프로세스간의 통신 채널이다. 파이프처럼 소켓은 파일 서술자처럼 표현된다. 그러나 파이프와는 달리 소켓은 관련 없는 프로세스간의 통신, 네트워크를 통하여 서로 다른 컴퓨터에서 수행되고 있는 프로세스간의 통신을 제공한다. 소켓은 주로 다른 컴퓨터간의 통신의 수단이다. telnet, rlogin, ftp, talk, 그리고 네트워크 프로그램들은 소켓을 사용한다. 모든 운영체제가 소켓을 지원하는 것은 아니다. GNU라이브러리에서 'sys/socket. h' 은 운영체제와 상관없이 존재한다. 그러나 만약 시스템이 소켓을 지원하지 않는다면, 소켓함수는 항상 실패한다.

 
미완성: 우리는 현재 인터넷 인터페이스 설정이나 브로드캐스트 메시지기능에 관하여는 문서화하지 못했다.


11. 1 소켓 개념

당신이 소켓을 만들 때, 반드시 당신이 어떤 통신 스타일을 쓸 것인지, 어떤 프로토콜을 사용할 것인지 명시해야 한다. 소켓의 통신 스타일은 소켓으로 데이터를 보내고 받는 유저레벨 구문을 정의한다. 통신 스타일을 선택하는 것은 다음과 같은 질문의 응답이 된다.

데이터 전송의 단위는 무엇인가?

어떤 통신 스타일은 큰 구조체를 가지지 않는, 바이트의 열로 된다. 다른 것들은 (패킷 같은) 레코드 안에 바이트를 묶어 놓는다.

보통 작업을 하는 동안에 데이터를 잃어버릴 수 있나?

어떤 통신 스타일에서 보내진 데이터가 (시스템이나 네트워크 충돌을 제외하고) 보내진 순서대로 모두 도달해야 한다. ; 다른 스타일은 때로는 보통 동작 시에도 데이터를 잃어버리거나, 하나 이상의 같은 패킷이 전달될 수도 있고, 순서가 뒤바뀔 수도 있다.
신뢰 있는 통신스타일을 사용하는 프로그램을 설계하는 것은 보통 데이터 손실이나, 순서가 뒤바뀌는 것을 검출하고 필요에 따라 재 전송하는 대책을 포함한다.

상대측과 완전한 통신을 하느냐?

어떤 통신스타일은 전화 거는 것과 같이 상대측과 원격 소켓 연결을 만들고 나서, 자유롭게 데이터를 교환한다. ; 다른 스타일은 편지 쓰는 것처럼 당신이 보내고자하는 메시지의 주소를 써서 보낸다.
 
당신은 또한 소켓의 이름공간에서 이름을 선택해야 한다. 소켓이름("주소")은 특정한 이름공간의 의미심장한 내용이 있다. 이름공간은 또한 "도메인"이라고 불린다. 그러나, 우리는 같은 용어의 다른 사용과 혼돈할 수 있어 그 말을 피하겠다. 각 이름공간은 'PF_'로 시작하는 상징적인 이름을 가진다. 'AF_'로 시작하는 유사한 상직적 이름은 그 이름공간에 주소 포맷을 가리킨다.
 
마지막으로, 당신은 통신을 제공하는 프로토콜을 선택해야 한다. 프로토콜은 데이터를 주고받는데 사용되는 저수준 메커니즘을 말한다. 각 프로토콜은 특정한 이름공간과 통신스타일에 대하여 유효하다. ; 이름공간은 종종 프로토콜 패밀리라고 불린다. 왜냐하면 이름 공간이 'PF_'로 시작하기 때문이다.
프로토콜의 규칙은 비록 다른 컴퓨터에서일지라도 두 프로그램간의 데이터 전송을 제공한다. 이러한 규칙의 대부분은 운영체제에 의하여 다루어지고, 당신이 그것에 대해 알 필요는 없다. 당신이 프로토콜에 대하여 알아야 할 것은 이것이다. :

두 소켓간의 통신을 위하여, 그것들은 반드시 같은 프로토콜을 나타내야 한다.

각 프로토콜은 특정한 스타일과 이름공간 결합에 의미를 가지며, 적당하지 않은 결합의 사용은 할 수 없다. 예를 들어 TCP 프로토콜은 바이트 스트림 스타일의 통신과 인터넷 이름공간에 맞추어져 있다.
스타일과 이름공간의 각 조합에서 프로토콜 번호를 0으로 나타내어 요구하면 그것은 기본프로토콜(디폴트 프로토콜)로 된다. 그리고, 당신은 보통 하는 것(디폴트를 사용하는 것)이 이런 것이다.


11. 2 통신스타일

GNU라이브러리는 각기 다른 특성을 지닌 몇 종류의 소켓을 지원하고 있다. 이 절에서는 지원되는 소켓 형태를 서술한다. 여기 나타난 심볼릭 상수는 'sys/socket. h'에 정의되어 있다.

int SOCK_STREAM Macro

SOCK_STREM스타일은 파이프와 같다. (10장[파이프와 FIFO] 을 보라). 그것은 특정한 원격 소켓과의 연결 후에 동작한다. 그리고 바이트의 스트림처럼 신뢰성 있는 데이터 전송을 한다. 이 스타일의 사용은 11. 8절에 자세히 나와 있다.

int SOCK__DGRAM Macro

SOCK_DGRAM 스타일은 개별적으로 주소가 쓰여진 패킷을 전달하는데 사용된다. 그것은 SOCK_STREAM과 정반대이다. 당신이 이 종류의 소켓에 데이터를 써넣는다. 그러면 그 데이터는 하나의 패킷이 된다. SOCK_DGRAM 소켓은 연결을 가지지 않는다. 당신은 각 패킷에 반드시 반복적으로 주소를 써넣어야 한다. 데이터를 전송하고자 하는 당신의 요구에 관하여 시스템의 책임은, 각 패킷이 가장 잘 배달될 수 있도록 시도하는 것이다. 그것은 네 번째와 다섯 번째 실패 후에 여섯 번째에 성공할 수 있다. ; 일곱 번째 패킷은 여섯 번째 전에 도착될 수도 있다. 그리고 여섯 번째 후에 두 번 도착할 수도 있다. SOCK_DGRAM은 일반적으로 응답해야할 시간내에 응답이 없을 때 간단히 패킷을 재 전송하는데 적합하다. 데이터그램 소켓을 사용하는 방법에 대한 자세한 정보는 11. 9절[데이터그램]을 보라

int SOCK__RAW Macro

이 스타일은 저수준 네트워크 프로토콜과 인터페이스를 억세스하도록 해 준다. 보통 사용자 프로그램은 이 스타일을 자주 사용할 필요가 없다.


11. 3 소켓 주소

소켓의 이름은 보통 "주소"라고 불린다. 소켓주소와 관계된 기호(symbol)와 함수들은, 때때로 "이름"이라는 말이나 "주소"를 사용되어, 어떤 규칙 없이 이름 지어진다. 당신은 소켓이 고려되는 곳에 이러한 말을 동일한 것처럼 생각할 수 있다. socket함수로 새롭게 만든 소켓은 주소를 가지지 않는다. 당신이 그것에게 주소를 주어야만, 다른 프로세스가 통신을 위한 주소를 찾을 수 있게 된다. 이것을 주소를 소켓에 묶는다고 하고, bind함수를 이용한다. 만약 다른 프로세스들이 주소일 찾고, 주소를 이용하여 통신을 시작하는 것이라면, 당신은 소켓의 주소를 생각해 볼 필요가 한다. 당신은 다른 소켓의 주소를 규명할 수 있지만, 그것은 보통 무의미하다; 처음, 소켓으로부터 데이터를 보내거나 연결을 초기화하기 위해 그것을 사용할 때, 만약 구체적으로 나타내진 것이 없다면, 시스템은 주소를 자동으로 설정한다.

서버는 주소에 기초하여 판별되기 때문에, 보통 클라이언트는 주소를 명시해야 한다. ; 예를 들어, rsh 와 rlogin 프로토콜은 클라이언트의 소켓주소를 살피고, 만약 그것이 IPPORT_RESER

VED보다 작지 않다면, 암호검사를 거쳐야 한다. ( 11.5.3절의 [포트] 을 보라)

소켓주소의 자세한 것은 당신이 사용하는 어떤 이름공간에 의존하여 변한다. 자세한 정보를 위해서는 11. 4절 [파일 이름공간] 을 보거나, 11. 5절 [인터넷 이름공간] 참조. 이름공간에 상관없이 , 소켓의 주소를 세트하고 사용하기 위해 당신은 같은 bind와 getsockname함수를 사용한다. 이러한 함수들은 주소를 받아들이기 위해 struct sockaddr *라는 위조 자료형(phony data type)을 사용한다. 실제적으로, 주소는 당신이 사용하는 주소포맷과는 다른 자료형의 구조로 되어 있다. 그러나, 당신은 그것을 bind하기 위해 넘겨줄 때, 그것의 주소를 struct sockaddr *로 자료형 변환(cast)을 해주어야 한다.

 

11. 3. 1 주소 포맷

bind와 getsockname함수는 소켓의 주소의 포인터를 의미하는 struct sockaddr *이라는 일반적인 자료형을 사용한다. 당신은 주소를 번역하거나 만들기 위해 이 자료형을 효율적으로 사용할 수 없다; 대신, 소켓의 이름공간을 위한 자료형을 사용해야만 한다. 그러므로, 보통 실제적인 사용은 적합한 이름공간의 형태의 주소를 만드는데 사용하고 나서, bind나 getsockname을 호출할 때 struct sockaddr *로 포인터형태를 변환한다. 당신이 struct sockaddr 자료형으로부터 얻을 수 있는 정보 중 하나는, 주소를 완전히 이해하기 위해 사용되는 자료형이 어떤 것인지 알려주는 "주소 포맷" 지정자이다. 이 절에서의 기호는 'sys/socket. h'라는 헤더파일안에 정의되어 있다.

struct sockaddr 자료형

struct sockaddr형 자체는 다음 멤버를 가지고 있다:

    short int sa_family

    이것은 이 주소의 주소 포맷용 코드이다. 그것은 다음에 나오는 데이터의 포맷을 지시한다.

    char sa_data[14]

    이것은 실제적인 소켓주소 데이터이다. 이것은 포맷에 의존한다. 그것의 길이는 또한 포맷에 의존하고 아마 14보다 클 것이다. sa_data의 14라는 길이는 원래 변할 수 있다. 각 주소포맷은 AF_로 시작하는 기호이름으로 시작된다. 그것들은 각각 이름공간에 대응되는 'PF_' 기호에 맞춰져 있다.

AF_FILE

이것은 파일 이름공간에 맞는 주소포맷이다. (PF_FILE은 이 이름공간의 이름이다. ) 이 주소 포맷의 정보를 보려면 11. 4. 2절 [파일 이름공간 구체적 내용] 을 보라.

AF_UNIX

이것은 호환성을 위한 AF_FILE과 같은 말이다. (PF_UNIX는 PF_FILE과 역시 동의어이다. )

AF_INET

이것은 인터넷 이름공간에 맞는 주소포맷이다. (PF_INET은 이 이름공간의 이름이다. ) 11. 5. 1절 [인터넷 주소 포맷] 을 보아라.

AF_UNSPEC

이것은 특정한 주소포맷을 지정하지 않는다. 그것은 "연결된" 데이터그램 소켓의 기본(default)목적지 주소를 지우는 것 같은 드문 경우에만 사용된다. 11. 9. 1절[데이터그램 보내기] 을 보아라. 여기에 맞는 이름 공간지정자 기호도 완전성을 위해 존재한다. 그러나, 그것을 프로그램에서 사용할 필요는 없다. 'sys/socket. h' 에서 대부분 실제로 구현되지 않은 많은 다른 종류의 네트워크에 'AF_'로 시작하는 기호를 정의하고 있다. 어떻게 그것을 사용하는 것에 관한 정보를 우리가 받으면, 우리는 그것을 정말로 문서화할 것이다.

 

11. 3. 2 소켓의 주소를 세팅하기

소켓에 주소를 할당하기 위하여 bind함수를 사용하라. bind의 프로토타입은 'sys/socket. h'헤더파일에 있다. 사용예는 11. 4절[파일 이름공간], 또는 11. 5. 7절[Inet 예제] 을 보라.

int bind (int socket, struct sockaddr *addr, size_t length) Function

bind함수는 "socket"소켓에 주소를 지정한다. addr과 length인수는 주소를 구체화한다. 자세한 주소포맷은 이름공간에 의존한다. 주소의 첫 번째 부분은 항상 포맷지정자이다. 포맷지정자는 이름공간을 명시하고, 그 주소가 그 이름공간의 포맷의 주소임을 말한다. 성공하면 0을 리턴하고, 실패하면 -1을 리턴한다.
 
다음은 이 함수에 정의되어 있는 errno 에러조건이다.

EBADF : 소켓 인수가 유효하지 않은 파일 지정자이다.

ENOTSOCK : 서술자 socket이 소켓이 아니다.

EADDRNOTAVAIL : 지시된 주소가 이 장치에서는 유효하지 않다.

EADDRINUSE : 또 다른 소켓이 이 주소로 이미 사용되고 있다.

EINVAL : socket소켓이 이미 주소를 가지고 있다.

EACCESS

당신은 요구된 주소를 제어할 허가권을 가지고 있지 않습니다. (인터넷 도메인에서, 단지 슈퍼유저만이 0에서 IPPORT_RESERVED-1까지 범위의 포트넘버를 사용할 수 있다. 11. 5. 3절[ports] 보라. )

추가적인 조건은 소켓의 각각 이름공간에 의존한다.

11. 3. 3 소켓의 주소 읽기

getsockname함수를 사용하여, 인터넷 소켓의 주소를 확인할 수 있다. 이 함수의 프로토타입은 'sys/socket. h'라는 헤더파일에 있다.

int getsockname (int socket, struct sockaddr *addr, size_t *length_ptr) 함수

getsockname함수는 addr과 length_ptr 인수로 명시되는 곳의 socket이라는 소켓의 주소에 관한 정보를 리턴한다. length_ptr은 포인터임을 주목하라; 당신이 addr의 크기 할당을 초기화할 것이다. 당신은 addr의 할당크기를 초기화 할 것이고, 주소데이터의 실제크기를 포함한 것이 리턴될 것이다.
 
주소데이터의 포맷은 소켓 이름공간에 의존한다. 정보의 길이는 주어진 이름공간에 보통 맞추어진다. 그래서, 보통 당신은 필요한 공간이 정확히 얼마인지 알 수 있고, 그것만큼 제공할 수 있다. 실제적인 사용은 소켓의 이름공간에 적합한 자료형을 사용하는 값으로 공간을 할당하고 나서, 그것을 getsockname으로 넘기기 위해 그 주소를 struct sockaddr*로 형변환(cast)한다.
 
리턴 값 0은 성공이고, -1은 에러이다. 다음 에러조건은 이 함수에서 정의된 것이다.

EBADF : 소켓인수가 유효한 파일 서술자가 아니다.

ENOTSOCK : 서술자 socket이 소켓이 아니다.

ENOBUFS

동작을 수행할만한 충분한 버퍼가 없다. 당신은 이름공간의 소켓의 주소를 읽을 수 없다. 이것은 시스템의 나머지 사항에 일치한다. 일반적으로, 그 파일의 서술자로부터 파일의 이름을 얻어낼 방법은 없다.


11. 4 The File Namespace

이 부분은 상징적인 이름(소켓을 만들 때 요구하는) 은 PF_FILE이라는 파일 이름공간에 대해 상세히 설명한다.

 

11. 4. 1 파일 이름공간 개념들

파일이름공간에서, 소켓 주소는 파일 이름들이다. 당신은 소켓의 주소처럼 당신이 원하는 어떤 파일 이름을 명시할 수 있다. 그러나 당신은 반드시 그것을 포함하는 디렉토리에 쓰기 허가를 가지고 있어야 한다. 소켓에 연결하기 위해, 당신은 그것에 대한 읽기 허가도 가지고 있어야 한다. 보통 /tmp디렉토리 안에 그러한 파일들을 넣는다.

파일이름공간의 특별한 점 하나는 연결을 할 때 그 이름이 한 번만 사용된다는 것이다. 즉, 그것을 한 번 사용하고 나면, 그 주소는 의미가 없고, 존재하지 않을지도 모른다.

또 다른 특징은 만약 소켓의 이름을 포함하는 파일시스템을 다른 기기가 공유하지 않는다면 다른 기기의 어떤 소켓으로 연결할 수 없다는 것이다. 당신은 디렉토리 리스트 안에서 소켓을 볼 수 있으나, 그것에 연결하는 것은 불가능하다. 어떤 프로그램들은 클라이언트가 자기 프로세스 ID를 보내도록 요구하는 것과 프로세스ID를 사용하여 클라이언트간을 식별하는 것을 이용하는 것 등을 이용한다. 그러나, 우리는 당신이 설계하는 프로토콜들 안에서 이 방법을 사용하지 말기를 원한다. 우리는 같은 파일시스템을 사용하는 다른 기기로부터의 연결들을 허락해야 할지도 모르기 때문이다. 대신에, 만약 그것을 가지는 것을 원한다면, 각 클라이언트에게 식별번호를 보낸다.

파일 이름공간안의 소켓을 닫은 후에, 당신은 파일시스템으로부터 파일이름을 지워야 한다. unlink나 remove를 사용하여 이것을 하라. 9. 5절 [파일 삭제하기] 을 보라.

파일 이름공간은 단지 어떤 통신 스타일에 대해 하나의 프로토콜만 지원한다. 그것은 프로토콜 번호 0이다.

 

11. 4. 2 파일이름 공간의 자세한 것

파일 이름 공간에 소켓을 만들려면, socket또는 sockerpair의 이름공간 인수에 상수 PF_FILE을 사용하라. 이 상수는 'sys/socket. h'에 정의되어 있다.

int PF__FILE Macro

이것은 소켓주소가 파일이름인 파일이름 공간과 그것이 연합된 프로토콜 패밀리를 가리킨다.

int PF__UNIX Macro

이것은 호환성을 위한 PF_FILE의 동의어이다.
파일이름공간의 소켓이름을 명시하는 구조체는 'sys/un. h'라는 헤더파일에 정의되어 있다.

struct sockaddr__un Data Type

이 구조체는 파일이름공간을 소켓 주소로 명시하는데 사용된다.

short int sun_family

이것은 소켓주소를 위한 형태나 주소패밀리를 명시한다. 당신은 파일이름공간을 지정하기 위하여 AF_FILE값을 저장해야 한다. 11. 3절 [소켓주소] 을 보라.

char sun_path[108]

이것은 사용되는 파일이름이다.
미완성: 왜 108이 매직넘버인가? RMS는 이것을 길이가 0인 배열을 만드는 것과 파일이름의 길이에 기초하여 적합한 저장공간을 할당하기 위해 allocate를 사용하는 것과 같은 예를 꼬집어 제안한다.

당신은 sun_family요소의 크기와 파일이름문자열의 (할당 크기가 아닌)문자열 길이의 합인, 파일이름공간에 소켓주소를 위한 길이 매개변수를 계산해야 한다.

 

11. 4. 3 파일-이름공간 소켓의 예

여기 있는 것은 파일이름공간의 소켓을 만들고 이름지어주는 방법을 보여주는 예이다.

#include <stddef. h>
#include <stdio. h>
#include <errno. h>
#include <stdlib. h>
#include <sys/socket. h>
#include <sys/un. h>
 
int
make_named_socket (const char *filename)
{
struct sockaddr_un name;
int sock;
size_t size;
 
/* 소켓 만들기 */
sock = socket (PF_UNIX, SOCK_DGRAM, 0);
if (sock < 0) {
perror ("socket");
exit (EXIT_FAILURE);
}
 
/* Bind a name to the socket. */
name. sun_family = AF_FILE;
strcpy (name. sun_path, filename);
 
/* The size of the address is the offset of the start of the filename, plus its length, plus one for the terminating null byte. */
size = (offsetof (struct sockaddr_un, sun_path) + strlen (name. sun_path) + 1);
 
if ( bind (sock, (struct sockaddr *) &name, size) <0) {
perror ("bind");
exit (EXIT_FAILURE);
}
return sock;
}


11. 5 인터넷 이름공간

이 장에서는 프로토콜과 소켓이 인터넷 이름공간에서 사용되는 이름짓는 방법에 대해 서술한다. 인터넷 이름공간에서 소켓을 만들려면, socket이나 socketpair의 이름 공간인수로 이 이름공간의 PF_INET 기호를 사용한다. 이 매크로는 'sys/socket. h'에 정의되어 있다.

int PF__INET Macro

이것은 인터넷 이름공간을 지정하고 프로토콜의 연합된 패밀리를 지정한다.

인터넷 이름공간의 소켓 주소는 다음과 같은 성분을 포함한다:

당신이 연결하기 원하는 장치의 주소. 인터넷 주소는 몇 가지 방법으로 표현될 수 있다. 이러한 방법들은 11. 5. 1절 [호스트 주소]11. 5. 2. 4절 [호스트 이름] 에서 논의한다.

그 장치의 포트 번호. 11. 5. 3절 [포트] 을 보라

당신은 주소와 포트번호가 네트워크 바이트 순서라고 불리는 정규화 된 포맷으로 표현된다는 것을 알아야 한다. 이에 관한 정보는 11. 5. 5절 [바이트 순서] 을 보라

 

11. 5. 1 인터넷 소켓 주소 포맷

인터넷 이름공간에서, 소켓주소는 호스트의 주소와 그 호스트의 포트를 포함한다. 추가적으로 당신이 선택한 프로토콜은 주소의 일부분처럼 효과적으로 제공된다. 왜냐하면 지역적인 포트번호는 특정한 프로토콜 안에서 의미를 가지기 때문이다. 인터넷 이름공간안의 소켓 주소의 표현에 관한 자료형은 'netinet/in. h' 헤더파일에 정의되어 있다.

struct sockaddr__in Data Type

이것은 인터넷 이름공간안의 소켓주소를 표현하는데 사용되는 자료형이다. 그것은 다음 멤버를 가진다.

short int sin_family

이것은 소켓주소의 포맷이나 주소 패밀리를 지시한다. 당신은 이 멤버내에 AF_INET값을 저장해야 한다. 11. 3절 [소켓주소] 을 보라

struct in_addr sin_addr

이것은 호스트 장치의 인터넷 주소이다. 어떻게 여기에 넣을 값을 얻는지에 관하여는 11. 5. 2절 [호스트 주소]11. 5. 2. 4 [호스트 이름] 을 보라.

unsigned short int sin_port

이것은 포트번호이다. 11. 5. 3절 [포트] 을 보라.

당신이 bind나 setsockname을 호출할 때, 만약 당신이 인터넷 이름공간 소켓 주소를 사용한다면, '길이' 매개변수로 sizeof (struct sockaddr_in)을 명시해야 한다.

 

11. 5. 2 호스트 주소

인터넷의 각 컴퓨터는 하나 또는 그 이상의 인터넷 주소를 가진다. 주소는 인터넷의 모든 것들 사이에서 그 컴퓨터를 지시하는 숫자이다. 사용자는 통상 숫자로된 호스트 주소를 사용한다. 그것은 '128. 52. 46. 32'처럼 네 숫자로 되어 있고 점으로 나뉘어져 있다. 각 컴퓨터는 또한 하나 또는 그 이상의 호스트 이름을 가진다. 그것은 'churchy. gnu. ai. mit. edu'처럼 점으로 나뉘어진 단어의 문자열이다.

사용자가 호스트를 명시하게 하는 프로그램은 전형적으로 숫자주소와 호스트 이름 모두를 수용한다. 그러나 프로그램은 연결을 위해 숫자로된 주소를 필요로 한다. 즉 호스트 주소를 사용하기 위해, 당신은 그것이 상징하고 있는 숫자 주소로 변환해야 한다.

 

11. 5. 2. 1 인터넷 호스트 주소

인터넷 호스트 주소는 데이터의 4바이트를 포함하는 숫자이다. 그것들은 두부분으로 나뉘는데, 네트워크 번호와 그 네크워크내의 지역 네트워크 주소 번호가 그것이다. 네트워크 숫자는 처음 하나, 둘 또는 세 개의 바이트를 포함한다. 나머지 바이트는 지역 주소이다.

네트워크 번호들은 네트워크 정보 센터 (NIC, Network Information Center)에 등록되어 있다. 그것들은 세 개의 클래스 A, B, C로 분할된다. 각각 컴퓨터마다의 지역네트워크 주소는 각 네트워크의 관리자에 의해 등록되어 있다.

클래스 A 네트워크는 0에서 127범위의 한 바이트 숫자를 가진다. 클래스 A 네트워크의 수는 매우 작지만, 각각은 굉장히 많은 수의 호스트를 지원한다. 중간크기의 클래스 B 네트워크는 두바이트의 네트워크 번호를 가진다. 첫 번째 바이트의 범위는 128에서 191까지이다. 클래스 C 네트워크는 가장 작다. 그것들은 세 바이트의 네트워크 번호를 가지며, 첫 번째 바이트의 범위는 192에서 255까지이다. 그러므로, 인터넷 주소의 처음 1, 2, 3바이트는 네트워크를 명시하는 것이다. 인터넷 주소의 나머지 바이트는 네트워크 안에서의 주소를 명시한다. 클래스 A네트워크 0은 모든 네트워크에 브로드캐스트용으로 예약되어 있다. 추가적으로 각 네트워크 내에서의 호스트 번호가 0인 것은 그 네트워크 내의 모든 호스트들에게 방송(broadcast)하기 위해 예약되어 있다. 클래스 A 네트워크 127은 루프백(loopback)용으로 예약되어 있다. 당신은 그러므로 인터넷 주소 '127. 0. 0. 1'을 사용하면 자신의 호스트장치를 참조 할 수 있다.

단일 장치는 여러 개의 네트워크의 멤버가 될 수 있으므로, 그것은 여러 개의 호스트 주소를 가질 수 있다. 그러나, 같은 호스트 주소를 가진 장치가 하나 이상 있어서는 안 된다.

인터넷 주소를 위한 숫자와 이름 표현은 네 가지 형태가 있다.

  • a. b. c. d 이것은 각개별적으로 주소의 모든 네 개의 바이트를 나타낸다.
  • a. b. c 주소의 마지막 부분인 c는 2바이트로 취급된다. 이것은 네트워크 번호 a. b를 가진 클래스 B의 호스트 주소를 나타내는데 유용하다.
  • a. b 주소의 마지막 부분인 b는 3바이트로 취급된다. 이것은 네트워크 주소 a를 가진 클래스A의 호스트 주소를 표시하는데 유용하다.
  • a 만약 하나의 부분만 주어진다면, 이것은 호스트 주소에 직접 일치하게 된다.

주소의 각부분에서, 기수를 표시하기 위해 보통 C관례를 사용한다. 다른 말로 하면, '0x' 또는 '0X'로 시작하는 것은 16진수 기수를 내포한다. '0'으로 시작하는 것은 8진수를 내포하고, 다른 것은 10진수 기수로 가정한다.

 

11. 5. 2. 2 호스트 주소 자료형

인터넷 호스트 주소는 정수 형들(unsigned long int형)같은 형식으로 표현된다. 다른 말로 하면, 정수들은 struct in_addr의 형태의 구조체 안에 들어가게 된다. 만약 용법이 일치되면 좋을 것이나, 그렇지 못하다면, 구조체로부터 정수를 뽑아 내거나, 정수를 구조체에 넣는 것이 쉽지 않을 것이다. 다음 인터넷 주소에 관한 기본적인 정의는 'netinet/in. h'헤더 파일 안에서 볼 수 있다:

struct in_addr 자료형

이 자료형은 인터넷 주소를 포함하는 정확한 문맥에 사용된다. 그것은 단지 s_addr이라는 하나의 필드를 가진다. 그것은 unsigned long int 처럼 호스트 주소번호를 저장한다.

unsigned long int INADDR__LOOPBACK Macro

당신은 이 장치의 실제주소를 찾는 대신에, 이 상수를 사용할 수 있다. 그것은 보통 'localhost(로컬호스트)'라 불리는 인터넷 주소 '127. 0. 0. 1'이다. 이 특별한 상수는 당신의 장치의 주소를 살피는데 생기는 문제를 해결해 준다. 또한, 시스템은 한 장치에서 자기자신으로 전송하는 경우 어떤 네트워크전송을 피하기 위해, 주로 INADDR_LOOPBACK을 특별히 구현하고 있다.

unsigned long int INADDR__ANY Macro

주소를 바인딩할 때, "어떠한 오는 주소(any incoming address)"에 대한 것을 이 상수를 사용할 수 있다. 11. 3. 2절 [주소 설정] 를 보라. 이것은 당신이 인터넷 연결을 받아들이길 원할 때, sockaddr_in구조체의 sin_addr멤버에 주로 들어가는 주소이다.

unsigned long int INADDR__BROADCAST Macro

이 상수는 방송메시지를 보내는데 사용되는 주소이다.

unsigned long int INADDR__NONE Macro

이 상수는 어떤 함수들이 에러를 지시하기 위해 반환된다.

 

11. 5. 2. 3 호스트 주소 함수들

인터넷 주소를 조작하는 추가적인 함수들은 'arpa/inet. h'안에 선언되어 있다. 그것들은 네트워크 바이트 순서로 인터넷 주소를 표현한다. 즉 그것들은 네트워크 번호와 네트워크 안에서의 지역 주소를 호스트 바이트 순서로 표현한다. 네트워크와 호스트 바이트 순서에 관한 설명은 11. 5. 3절 [바이트 순서] 을 보라.

int inet__aton (const char *name, struct in_addr *addr) 함수

이 함수는 인터넷 호스트 주소 이름을 표준 숫자와 점 표시형식에서 이진데이터 형으로 변환한다. 그리고, 그것을 addr이 지시하고 있는 struct in_addr안에 저장한다.

unsigned long int inet__addr (const char *name) 함수

이 함수는 인터넷주소 이름을 숫자와 점으로 된 것에서 이진 데이터로 변환한다. 만약 입력이 유효하지 않으면, inet_addr은 INADDR_NONE를 반환한다. 이것은 위에서 서술한 inet_aton의 구식의 인터페이스이다. INADDR_NONE은 유효한 주소 ( 255. 255. 255. 255 ) 이기 때문에 그것은 쓸모 없이 되었다. 그리고 inet_aton 은 에러 반환을 지시하는 더 깔끔한 방법을 제공한다.

unsigned long int inet__network (const char *name) 함수

이 함수는 숫자와 점으로 표현되는 주소이름으로 부터 네트워크 번호를 뽑아낸다. 만약 입력을 제대로 하지 않으면, inet_network는 -1을 반환한다.

char * inet__ntoa (struct in_addr addr) 함수

이 함수는 인터넷 호스트 주소 addr을 숫자와 점으로 표현되는 문자열로 변환한다. 반환되는 값은 정적으로 할당된 버퍼의 포인터이다. 계속 호출하면 같은 버퍼에 덮어쓰기를 하므로, 만약 그것을 저장하기 위해서 그 문자열을 복사해두어야 한다.

struct in_addr inet__makeaddr (int net, int local) 함수

이 함수는 지역 네트워크 내의 지역 주소 local과 네트워크 번호 net를 묶어 인터넷 호스트 주소를 만든다.

int inet__lnaof (struct in_addr addr) 함수

이 함수는 인터넷 호스트 주소 addr의 일부인 네트워크 내에서의 지역 주소를 반환한다.

int inet__netof (struct in_addr addr) Function

이 함수는 인터넷 호스트 주소의 일부인 네트워크 번호를 반환한다.

 

11. 5. 2. 4 호스트 이름들

인터넷 주소를 위한 숫자와 점으로 표현하는 것 외에, 당신은 의미 있는 이름을 사용하여 호스트를 참조할 수 있다. 의미 있는 이름을 사용하는 것의 장점은 역시 외우기 쉽다는 것이다. 예를 들어 인터넷 주소 '128. 52. 46. 32'를 가진 장치는 'churchy. gnu. ai. mit. edu'로도 표현된다. 또한 'gnu. ai. mit. edu' 도메인내의 다른 컴퓨터들은 간단하게 'churchy'로 이 장치를 참조할 수 있다.

내부적으로, 시스템은 호스트 이름과 호스트 번호 사이의 매핑의 정보를 유지하는 데이터베이스를 사용한다. 이 데이터베이스는 보통 '/etc/hosts'이거나 네임서버에 의해 제공된다. 이 데이터 베이스에 접근하기 위한 함수와 기호들은 'netdb. h'에 선언되어 있다. 만약 당신이 'netdb. h'를 포함한다면, 그것들은 무조건적으로 정의되어 있는 BSD들의 특징이다.

struct hostent 자료형

이 자료형은 호스트 데이터베이스 안의 내용을 표현하는 데 사용된다. 그것은 다음 멤버를 가진다.

char *h_name

이것은 호스트의 "사무적인" 이름이다.

char **h_aliases

스트링의 null로 끝나는 벡터로 표현된 호스트의 또다른 이름이다.

int h_addrtype

이것은 호스트 주소의 형태이다. 실제적으로, 이것의 값은 항상 AF_INET이다. 원래 다른 종료의 주소도 인터넷 주소처럼 데이터베이스 안에 표현될 수 있다. 만약 그렇게 한다면, 당신은 이 필드에 AF_INET이 아닌 어떤 값을 찾아야 한다. 11. 3[소켓 주소들] 을 보라.

int h_length

이것은 각 주소의 바이트 길이다.

char **h_addr_list

이것은 호스트의 주소들의 벡터이다. (호스트들은 다중 네트워크에 연결되고 각각 여러 개의 다른 주소를 가진다는 것을 회상하라) 벡터는 널 포인터로 종료된다.

char *h_addr

이것은 h_addr_list[0]과 동의어이다. 다른 말로, 이것은 첫 번째 호스트 주소이다.
호스트 데이터베이스가 고려되는 것과는 달리, 각 주소는 h_length 바이트 길이의 메모리 블록이다. 그러나 다른 문맥에서, 그것들은, 당신이 이것을 in_addr 또는 unsigned long int로 변환할 수 있는 암시적인 가정이다. struct hostent 구조체의 호스트 주소들은 항상 네트워크 바이트 순서로 주어진다. 11. 5. 5절 [바이트 순서] 을 보라. 당신은 특정한 호스트에 관한 정보를 찾기 위해 호스트 데이터베이스를 검색하려면 gethostbyname 또는 gethostbyaddr를 사용할 수 있다. 그 정보는 정적으로 할당된 구조체에 반환된다. 만약 당신이 그것을 호출로부터 저장하기 위해서는 정보를 복사해야 한다.

struct hostent * gethostbyname (const char *name) 함수

gethostbyname함수는 name으로 이름지어진 호스트에 관한 정보를 반환한다. 만약 검색에 실패하였으면, 널 포인터를 반환한다.

struct hostent * gethostbyaddr (const char *addr, int length, int format) 함수

gethostbyaddr함수는 인터넷 주소 addr의 호스트에 관한 정보를 반환한다. 인수의 length는 addr 주소의 (바이트 단위의)크기이다. format는 주소형식을 명시한다. 즉 인터넷 주소라면 AF_INET값으로 명시하면 된다. 만약 검색에 실패를 하면, gethostbyaddr는 널 포인터를 반환한다. 만약 gethostbyname 또는 gethostbyaddr을 이용한 이름검색이 실패하면, 당신은 변수h_errno의 값을 살펴보고 그 원인을 찾을 수 있다. (그것은 그러한 함수가 errno를 세팅하도록 더 깔끔하게 설계되어 있다. 그러나 h_errno의 사용은 다른 시스템에 대해 호환성 있게 되어 있다. )
h_erro사용하기 전에, 당신은 다음과 같이 그것을 선언해 주어야 한다.

extern int h_errno; 아래는 h_errno안에서 찾을 수 있는 error코드들이다:

HOST_NOT_FOUND

어떤 호스트가 data base안에 없다.

TRY_AGAIN

이 상태는 네임서버에 연결되지 않을 때 발생한다. 만약 당신이 나중에 다시 시도한다면, 성공할 수도 있다.

NO_RECOVERY

복구할 수 없는 에러 발생.

NO_ADDRESS

호스트 데이터베이스는 이름에 대한 항목을 포함하나, 그것은 적합한 인터넷 주소를 가지지 않았다.
 
당신은 또한 sethostent, gethostent, endhostnet를 사용하여 한 번에 한 항목씩 전체 호스트 데이터베이스를 죽 살펴본다. 이러한 함수들을 사용할 때는 주의해야 한다. 왜냐하면 그것들은 재진입성을 가지지 않기 때문이다. (즉, 스케줄을 점유한다)

void sethostent (int stayopen) 함수

이 함수는 데이터베이스를 살펴보는 것을 시작하기 위해서 호스트 데이터베이스를 연다. 당신은 항목들을 읽기 위해 그러고 나서 gethostent를 호출해야 한다. 만약 stayopen인수가 0이 아니면, 다음으로 gethostbyname 또는 gethostbyaddr를 호출하는 것이 데이터베이스를 닫지 않도록 (그것들이 그래야 하는 것처럼) 이것은 플래그를 세트한다.

struct hostent * gethostent () 함수

이 함수는 호스트 데이터베이스 안의 다음 항목을 반환한다. 만약 더 이상 항목이 없으면, 널 포인터를 리턴한다.

void endhostent () 함수

함수는 호스트 데이터 베이스를 닫는다.

 

11. 5. 3 인터넷 포트

인터넷 이름공간에서의 소켓주소는, 장치의 인터넷 주소에 추가적으로 주어진 장치의 소켓을 구별하는 포트번호로 구성된다. 포트번호는 0에서 65535번까지의 범위이다. IPPORT_RESERVED보다 작은 포트번호는 표준서버에서 finger나 telnet과 같은 것을 위해 예약되어 있다. 시스템 내부에 이러한 것들을 유지하고 있는 데이터베이스가 있고, 당신은 getservbyname함수를 사용하여 서비스 이름과 포트번호의 맵을 만들 수 있다. ; 11. 5. 4절 [서비스 데이터베이스] 을 보라.

만약 데이터베이스 안에 정의된 표준 중의 하나가 아닌 서버를 작성하려면, 그것을 위한 포트번호를 선택해야 한다. IPPORT_USERRESERVED 보다 더 큰 숫자를 사용하라. 어떤 번호들은 서버들을 위해 예약되어 있고 시스템에 의해 자동으로 생성되지 않을 것이다. 다른 사용자들에 의한 서버사용과의 충돌을 피하는 것은 당신에게 달렸다.

당신이 그것의 주소를 명시하지 않고 소켓을 사용할 때, 시스템은 포트번호를 하나 생성한다. 이 번호는 IPPORT_RESERVED와 IPPORT_ USERRESERVED사이의 값이다.

인터넷에서, 같은 소켓주소(호스트 주소 + 포트주소)로 통신을 시도하지 않는다면, 같은 포트번호로 두 개의 다른 소켓을 가지는 것은 실제로 적당한 방법이다. 상위프로토콜이 그것을 요구할 때, 특별한 상황을 기대하고 같은 포트번호를 복사하지 말아라. 보통, 시스템은 그러한 일을 수행하지 않을 것이다. bind는 보통 다른 포트번호를 요구한다. 포트번호를 다시 사용하기 위해서는, 소켓옵션 SO_REUSEADDR를 세트하라. 11. 11. 2절 [소켓-수준 옵션]을 보라.

다음 매크로들은 'netinet/in. h'에 정의되어 있다.

 int IPPORT__RESERVED 매크로

IPPORT_RESERVED 보다 작은 값의 포트는 슈퍼유저용으로 예약되어 있다.

int IPPORT__USERRESERVED 매크로

명백히 사용하기 위해 예약된, IPPORT_USERRESERVED보다 크거나 같은 포트이다. 그것들은 보통 자동으로 할당된다.

 

11. 5. 4 서비스 데이터베이스

유명한 서비스의 경로를 유지하는 데이터베이스는 보통 '/etc/services'파일이거나 네임서버의 동등한 파일이다. 이 서비스 데이터베이스를 억세스하기 위해 'netdb. h'에 선언되어 있는 다음 유틸리티들을 사용할 수 있다.

struct servent 자료형

이 자료형은 서비스 데이터베이스로부터의 항목에 관한 정보를 나타낸다.

char *s_name

이것은 서비스의 "공식적"인 이름이다.

char **s_aliases

문자열의 배열로 표현된 서비스의 또다른 이름들이다. 이 배열은 마지막에 널 포인터가 들어간다.

int s_port

이것은 서비스의 포트 번호이다. 포트번호는 네트워크 바이트 순서로 되어 있다. ; 11. 5. 5절 [바이트 순서]를 보라.

char *s_proto

이것은 이 서비스와 사용되는 프로토콜의 이름이다. 11. 5. 6절 [프로토콜 데이터베이스]을 보라.

특정한 서비스에 관한 정보를 얻으려면, getservbyname이나 getservby port 함수를 사용하라. 이 정보는 정적으로 할당된 구조체 안으로 반환된다. 즉 그것을 여러 번 호출하는 것으로부터 보호하려면 그 정보를 복사해 놓아야만 한다.

struct servent * getservbyname (const char *name, const char *proto) 함수

getservbyname함수는 name이라고 proto라는 프로토콜을 사용하고 이름지어진 서비스에 관한 정보를 반환한다. 만약 그러한 서비스를 찾지 못하면, 널 포인터를 반환한다.
이 함수는 클라이언트들뿐만 아니라 서버들에서도 유용하다. 서버에서는 듣고자 할 때 어떤 포트를 사용할 것인가를 결정할 때, 이 함수를 사용한다. (듣는 것에 관하여서는 11. 8. 2절[듣기] 을 보라)

struct servent * getservbyport (int port, const char *proto) 함수

getservbyport함수는 proto프로토콜을 사용하고 port라는 포트를 사용하는 서비스에 관한 정보를 반환한다. 당신은 또한 setservent, getservent, endservent를 사용하여 서비스 데이터베이스를 조사할 수 있다. 이러한 함수들은 재진 입성을 가지지 않기 때문에, 사용할 때 주의해야 한다.

void setservent (int stayopen) 함수

이 함수는 당신이 조사하려는 서비스 데이터베이스를 연다.
만약 stayopen 인수가 0이 아니면, getservbyname또는 getservbyport를 다음에 호출을 하였을 때 데이터베이스를 닫지 않도록(보통은 닫는다), 플래그를 세트한다. 여러 번의 호출이 데이터베이스를 여러 번 열지 않도록 할 때, 이것은 더욱 효율적이다.

struct servent * getservent (void) 함수

이 함수는 서비스 데이터베이스 안의 다음 항목을 반환한다. 만약 더 이상의 항목이 없으면, 널 포인터를 반환한다.

void vndservent (void) 함수

이 함수는 서비스 데이터베이스를 닫는다.

 

11. 5. 5 바이트 순서 변환

컴퓨터의 종류가 틀리면, 한 워드 내에서 바이트의 순서가 틀리게 사용 될 수 있다. 어떤 컴퓨터들은 워드안에 상위 바이트가 먼저 오기도 하고 (이를 "큰 종결자(big-endian)"라 부른다), 다른 것들은 상위 바이트가 나중에 오기도 한다. (이를 "작은 종결자(little-endian)"라 한다)

다른 바이트 수를 사용하는 장치들이 통신하기 위해, 인터넷 프로토콜들은 네트워크 상에서 전송되는 데이터를 위해 규정된 바이트 수 명시하고 있다.

인터넷 소켓 연결을 설정할 때, 당신은 sockaddr_in구조체의 sin_port와 sin_addr멤버를 네트워크 바이트 수로 표현해야만 한다. 소켓을 통해 전송되는 정수 데이터를 코딩할 때 역시 그것을 네트워크 바이트 순서로 변환해야 한다. 만약 그렇게 하지 않는다면, 프로그램은 다른 종류의 장치와 통신을 하는데 실패할 것이다.

포트번호나 호스트 주소를 얻기 위해 getservbyname, gethostbyname, inet_addr를 사용한다면, 사용되는 값들은 이미 네트워크 바이트 순서로 되어 있는 것이다. 당신은 그것들을 sockaddr_in에 직접 복사할 수 있다.

그렇지 않은 경우, 값들을 정확히 변환하지 않으면 안 된다. sin_port멤버로 사용하기 위해 htons와 ntohs를 사용하여 값들을 변환하라. sin_addr멤버로 사용하기 위해 htonl과 ntohl를 사용하여 값들을 변환하라. (struct in_addr은 unsigned long int와 같은 것임을 기억하라) 이러한 함수들은 'netinet/in. h'안에 선언되어 있다.

unsigned short inthtons (unsigned short int hostshort) 함수

이 함수는 호스트 바이트 수 된 short형 정수 hostshort를 네트워크 바이트 순서로 변환한다.

unsigned short int ntohs (unsigned short int netshort) 함수

이 함수는 네트워크 바이트 수 된 short형 정수 netshort를 호스트바이트순서로 변환한다.

unsigned long int htonl (unsigned long int hostlong ) 함수

이 함수는 호스트 바이트순서로된 long형 정수 hostlong을 네트워크 바이트 수 변환한다.

unsigned long int ntohl (unsigned long int netlong ) 함수

이 함수는 네트워크 바이트 수 된 long형 정수 netlong을 호스트 바이트 수 변환한다.

 

11. 5. 6 프로토콜 데이터베이스

소켓에서 사용되는 통신 프로토콜은 어떻게 데이터를 교환할 것인가에 대한 저수준 세부사항을 제어한다. 예를 들면, 프로토콜은, 전송상의 에러검출을 위한 검사합계(checksum)같은 것이나, 메시지의 경로설정 명령 같은 것들을 구현한다. 일반 사용자 프로그램들은 이러한 세부사항들에 대해서 직접적인 영향을 받지는 않는다.

인터넷 이름공간의 기본 통신 프로토콜은 통신스타일에 의존한다. 스트림 통신의 기본 프로토콜은 TCP("전송 제어 프로토콜")이다. 데이터그램 통신의 기본 프로토콜은 UDP("유저 데이터그램 프로토콜")이다. 신뢰성 있는 데이터그램 통신의 기본 프로토콜은 RDP("신뢰성 있는 데이터그램 프로토콜")이다. 당신은 아마 거의 대부분 기본 프로토콜을 사용할 것이다.

인터넷 프로토콜은 일반적으로 숫자 대신 이름으로 명시된다. 호스트가 알고 있는 네트워크 프로토콜은 데이터베이스 안에 저장되어 있다. 이것은 보통 '/etc/protocols'파일 이에서 얻어지거나 , 네임서버에 의해 동등하게 제공될 수 있다. 당신은 getprotobyname함수를 사용하여 데이터베이스 내에서 프로토콜 이름에 연계된 프로토콜 번호를 찾아 낼 수 있다.

여기에 프로토콜 데이터베이스를 억세스하기 위한 유틸리티들의 자세한 정의가 있다. 이것들은 'netdb. h'에 선언되어 있다.

struct protoent 자료형

이 자료형은 네트워크 프로토콜 데이터베이스의 항목들을 표현하는데 사용된다. 그것은 다음 멤버를 가진다.

char *p_name

이것은 프로토콜의 공식적인 이름이다.

char **p_aliases

이것들은 프로토콜의 또다른 이름들이다. 문자열의 배열로 되어 있으며, 배열의 마지막 요소는 널 포인터이다.

int p_proto

이것은 (호스트 바이트 수 된) 프로토콜 번호이다. 소켓의 protocol인수로 이 멤버를 사용하라

구체적인 프로토콜에 관하여 프로토콜 데이터베이스를 검색하기 위해 getprotobyname과 getprotobynumber를 사용할 수 있다. 정적으로 할당된 구조체에 정보가 반환된다. 여러 번 호출되는 것으로부터 정보를 보호하기 위해 그것을 복사해야 놓아야 한다.

struct protoent * getprotobyname (const char *name) 함수

getprotobyname함수는 name이라는 이름의 네트워크프로토콜에 관한 정보를 반환한다. 만약 같은 이름의 프로토콜이 없으면, 널 포인터를 반환한다.

struct protoent * getprotobynumber (int protocol) 함수

getprotobynumber함수는 protocol이라는 숫자를 가진 네트워크 프로토콜에 관한 정보를 반환한다. 만약 이러한 프로토콜이 없다면 널 포인터를 반환한다. setprotoent, getprotoent, endprotoent를 사용하여 전체 프로토콜 데이터베이스를 한 번에 한 프로토콜씩 조사할 수 있다. 이들 함수는 재진 입성을 가지지 않기 때문에 주의해서 사용해야 한다.

void setprotoent (int stayopen) 함수

이 함수는 검색을 위해 프로토콜 데이터베이스를 연다. 만약 stayopen인수가 0이 아니면, getprotobyname 또는 getproto-bynumber의 호출이 데이터베이스를 (보통 닫는데) 닫지 않도록 플래그를 세트한다. 이것은 여러 번의 호출로 데이터베이스가 여러 번 열리지 않도록 하는데 더욱 유용하다

struct protoent * getprotoent (void) 함수

이 함수는 프로토콜 데이터베이스의 다음 정보를 반환한다. 만약 더 이상의 항목이 없으면 널 포인터를 반환한다.

void endprotoent (void) Function

이 함수는 프로토콜 데이터베이스를 닫는다.

 

11. 5. 7 인터넷 소켓 예제

여기에 인터넷 이름공간 안에 소켓을 만들고 이름짓는 방법을 보여주는 예제가 있다. 프로그램이 수행되는 장치에 새로 소켓이 만들어진다.

장치의 인터넷 주소를 찾고 사용하는 것 대신, 호스트 주소에는 INADDR_ANY를 명시하였다. 시스템은 장치의 실제주소로 그것을 대치한다.

#include <stdio. h>
#include <stdlib. h>
#include <sys/socket. h>
#include <netinet/in. h>
 
int
make_socket (unsigned short int port)
{
int sock;
struct sockaddr_in name;
 
/* 소켓을 만든다 */
sock = socket (PF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror ("socket");
exit (EXIT_FAILURE);
}
 
/* 소켓에 이름을 준다 */
name. sin_family= AF_INET;
name. sin_port = htons (port);
name. sin_addr. s_addr = htonl (INADDR_ANY);
if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0)
{
perror ("bind");
exit (EXIT_FAILURE);
}
return sock;
}
 
여기에 또다른 예제가 있다. 이것은 주어진 호스트 이름문자열과 포트번호를 sockaddr_in구조체 안에 어떻게 넣는 지를 보여준다:
 
#include <stdio. h>
#include <stdlib. h>
#include <sys/socket. h>
#include <netinet/in. h>
#include <netdb. h>
 
void
init_sockaddr (struct sockaddr_in *name, const char *hostname,
unsigned short int port)
{
struct hostent *hostinfo;
name->sin_family = AF_INET;
name->sin_port = htons (port);
hostinfo = gethostbyname (hostname);
if (hostinfo == NULL) {
fprintf (stderr, "Unknown host %s. \n", hostname);
exit (EXIT_FAILURE);
}
name->sin_addr = *(struct in_addr *) hostinfo->h_addr;
}


11. 6 다름 이름공간들

다른 이름공간과 프로토콜패밀리들도 지원되지만 별로 사용되지 않으므로 아직 문서화되지 않았다. PF_NS는 Xerox 네트워크 소프트웨어 프로 콜에 관련된 것이다. PF_ISO 는 개방시스템상호연결(OSI)에 관련된 것이다. PF_CCITT는 CCITT의 프로토콜들에 관련된 것이다. 'socket. h'에 실제로 구현되지 않은 기호와 다른 이름의 프로토콜이 정의되어 있다.

PF_IMPLINK는 호스트와 인터넷 메시지 처리기와의 통신에 사용된다. 이에 관한 정보와 가끔 사용되는 지역 경로설정 프로토콜인 PF_ROUTE에 관하여는 (미래에 나올) GNU Hurd Manual을 보라.


11. 7 소켓 열고 닫기

이 절에서는 소켓을 열고 다는 실제적인 라이브러리 함수에 관하여 서술한다. 모든 이름공간과 연결스타일에 대해 같은 함수가 사용된다.

11. 7. 1 소켓 만들기

소켓을 만들기 위해서는 'sys/socket. h'에 선언되어 있는 소켓함수를 사용한다.

int socket (int namespace, int style, int protocol) 함수

이 함수는 소켓을 만들고 통신스타일 style을 명시한다. 통신스타일은 11. 2절[통신스타일4] 에 열거된 소켓 스타일중 하나를 사용하여야 한다. namespace인수는 이름공간을 명시하고, PF_FILE이거나 PF_INET이어야 한다( 11. 4절[파일 이름공간], 11. 5절[인터넷 이름공간]을 보라). protocol은 프로토콜을 정한다 (11. 1절 [소켓개념]을 보라); protocol에는 보통 0이 사용된다.
 
socket으로부터의 반환 값은 새로운 소켓의 파일서술자이다. 에러가 발생하면 -1이 반환된다.
다음은 이 함수가 정의해 놓은 에러 상황 errno이다:

EPROTONOSUPPORT

The protocol or style is not supported by the namespace specified.

EMFILE : 프로세스가 이미 너무 많은 파일서술자들을 열어 놓았다.

ENFILE : 시스템이 이미 너무 많은 파일서술자들을 열어 놓았다.

EACCESS : 프로세스가 명시된 style이나 protocol에 맞는 소켓을 만들 권한을 갖고 있지 않다.

ENOBUFS : 시스템 내부 버퍼 공간이 부족하다.

socket 함수에 의해 반환되는 파일서술자는 읽기와 쓰기 기능 모두를 지원한다. 그러나 파이프 같은 소켓들은 파일 위치에 관한 동작은 하지 못한다. socket함수를 어떻게 호출하는 지에 대한 예는 11. 4절 [파일 이름공간], 또는 11. 5. 7절 [Inet 예제]을 보라.

 

11. 7. 2 소켓 닫기

소켓의 사용이 끝나면, 간단히 그 파일서술자를 close함수를 이용해 닫으면 된다. 8. 1절 [파일 열고 닫기]을 보라. 만약 연결을 통하여 전송되기를 기다리는 데이터가 여전히 있으면, 보통 close는 이 전송이 완료되도록 시도한다. SO_LINGER소켓 옵션을 사용하여 시간제한 주기를 명시해 주면, 이 과정을 제어할 수 있다. 11. 11절 [소켓 옵션] 을 보라.

당신은 'sys/socket. h'에 서술되어 있는 shutdown을 호출하므로 써 연결하의 송신만 또는 수신만을 종료시킬 수 있다.

int shutdown (int socket, int how ) 함수

shutdown함수는 socket이라는 소켓의 연결을 종료시킨다. how인수는 어떤 기능을 수행할 것인가를 명시한다.
 
0. 이 소켓으로부터 데이터 수신을 중단한다. 만약 더 이상의 데이터가 도착하면, 그것을 무시한다.
 
1. 이 소켓으로의 전송을 중단한다. 보내기 위해 대기중인 데이터는 취소된다. 이미 보낸 데이터의 응답을 기다리는 것도 중단한다. 만약 그것을 잃어버릴지라도 재전송하지 않는다.
 
2. 수신과 송신 모두를 중단한다.
성공하면 반환 값이 0이고, 실패면 -1이다. 다음은 이 함수에 정의되어 있는 errno 에러 상 황이다.
 
EBADF : socket이 유효한 파일 서술자가 아니다.
ENOTSOCK : socket이 소켓이 아니다.
ENOTCONN : socket이 연결되어 있지 않다

 

11. 7. 3 소켓 쌍

소켓 쌍은 연결된 (그러나 이름없는) 소켓의 쌍으로 구성된다. 그것은 pipe와 유사하고 무척 같은 방법으로 사용된다. 소켓 쌍은 'sys/socket. h'에 선언된 socketpair함수로 만들어진다. 소켓 쌍은 파이프와 대단히 유사하다:

주된 차이점은 소켓 쌍은 양방향이라는 것이다. 파이프는 하나의 입력만 되는 쪽과 출력만 되는 한 쪽만을 가지고 있다. (10장[Pipe와 FIFO]를 보라 )

int socketpair (int namespace, int style, int protocol, int filedes[2]) 함수

이 함수는 소켓 쌍을 만들며, filedes[0]과 filedes[1]안에 파일서술자를 반환한다. 소켓 쌍은 전이중(full-duplex)성 통신 채널이기 때문에 한쪽에서 읽거나 쓰기가 가능하다. namespace, style, protocol인자는 socket함수에서처럼 해석된다. style은 11. 2절[통신스타일]에 나열된 것이다. namespace인자는 이름공간을 명시하며, 반드시 AF_FILE이어야 한다. (11. 4절[파일이름공간] 을 참조하라); protocol은 통신 프로토콜을 서술하고 0도 의미 있는 값이다. 만약 style 이 비연결 통신스타일을 명시하면, 당신이 얻게 되는 두 소켓은 연결되지 않는다. 엄밀히 말해, 그들 각각은 서로의 목적지 주소를 알고 있어, 서로에게 패킷을 보낼 수 있게 된다.
 
socketpair함수는 성공시에 0을, 실패시에 -1을 반환한다. 다음은 이 함수에 정의되어 있는 errno에러 상황이다:

EMFILE : 프로세스가 너무 많은 파일 서술자를 열어 놓고 있다.

EAFNOSUPPORT : 명시된 이름공간이 지원되지 않는 것이다.

EPROTONOSUPPORT : 명시된 프로토콜이 지원되지 않는 것이다.

EOPNOTSUPP 명시된 : 프로토콜이 소켓 쌍을 만드는 것을 지원하지 않는다.


11. 8 연결하는데 소켓사용

대부분의 통신 스타일은 특정한 다른 소켓과 연결을 만들고 소켓과 소켓을 통하여 데이터를 교환한다. 연결을 만드는 것은 비대칭이다. ; 한쪽(클라이언트)은 다른 쪽(서버)이 소켓을 만들고 연결요구를 기다릴 때까지 연결을 요구하도록 되어 있다.

11. 8. 1절 [연결하기] 은 클라이언트 프로그램이 서버와 연결을 초기화하기 위해 무엇을 해야 하는지를 서술한다.

11. 8. 2절 [듣기]11. 8. 3[연결을 수락하기] 에서는 서버 프로그램이 클라이언트로부터 연결요구를 기다리고 받아들이는 것에 대한 서술을 한다.

11. 8. 5[데이터전송]은 어떻게 연결된 소켓을 통하여 데이터를 전송하는 방법을 서술한다.

 

11. 8. 1 연결 만들기

연결을 만들 때, 클라이언트는 서버가 연결을 기다리고 수락하는 동안 연결을 만들어야 한다. 여기에 클라이언트가 'sys/socket. h'에 선언된 connect함수를 이용하여 무엇을 해야 할 것인지를 논의하고 있다.

int connect (int socket, struct sockaddr *addr, size_t length) 함수

connect함수는 addr와 length의 길이로 명시된 주소를 가지는 소켓의 socket이라는 파일서술자를 이용하여 소켓으로부터 연결을 초기화 한다. (이 소켓은 전형적으로 다른 장치의 것이다. 그것은 서버처럼 이미 설정되어야 한다. ) 이 인수들이 어떻게 해석되는지에 관한 정보는 11. 3절[소켓주소]을 보라
 
보통, connect는 서버가 요구에 응답할 때까지 기다린 후 반환하게 된다. 응답을 기다리지 않고 즉시 반환하도록 소켓 socket을 블록킹되지 않도록(non-blocking) 설정할 수 있다. 블록킹되지 않는 모드에 관한 정보는 8. 10절 [파일 상태 플랙]을 보라.
 
connect로부터의 반환치는 보통 0이다. 만약 에러가 발생 시에는 -1을 반환한다.  
이 함수에 선언되어 있는 errno에러 상황은 다음과 같다.

EBADF : 소켓socket이 유효한 파일 서술자가 아니다.

ENOTSOCK : 소켓socket이 소켓이 아니다.

EADDRNOTAVAIL : 명시된 주소가 원격장치(상대방기기)에 사용할 수 없는 것이다.

EAFNOSUPPORT : addr의 이름공간이 이 소켓에서 지원하지 않는 것이다.

EISCONN : 소켓socket이 이미 연결되어 있다.

ETIMEDOUT : 연결설정을 시도하다가 시간을 초과하였다.

ECONNREFUSED : 서버가 연결설정을 직접 거절하였다.

ENETUNREACH : 주어진 addr의 네트워크는 이 호스트로부터 도달할 수 없다.

EADDRINUSE : 주어진 addr의 소켓주소가 이미 사용 중이다.

EINPROGRESS : 소켓socket이 블록 킹되지 않는 것이고, 연결을 즉시 설정할 수 없었다.

EALREADY : 소켓socket이 블록 킹되지 않는 것이고, 이미 진행중인 미결정의 연결이 있다.

 

11. 8. 2 연결 듣기

이제 서버프로세스가 소켓에서 연결을 수락하기 위해 무엇을 해야 하는지 고려해보자. 이것은 listen함수를 사용하여 소켓에서 연결요구를 가능하게 하고 나서, accept함수 (11. 8. 3절 [연결수락]을 보라)를 사용하여 요구를 받아들이는 것을 포함한다. listen함수는 비연결 통신 스타일을 지원하지 않는다. 연결 요구가 있을 때까지 수행을 시작하지 않도록 네트워크 서버를 작성할 수 있다. 11. 10. 1절[Inetd 서버들]을 보라.

인터넷 이름공간에서, 한 포트로의 연결에 접근을 제어하는 보호 메커니즘이 없다. 어떤 장치에서 어떤 프로세스가 당신의 서버에 연결을 만들 수 있다. 만약 당신이 당신의 서버에 접근을 제어하기를 원한다면, 연결요구에 연계된 주소를 검사하거나, 다른 협상과정이나 식별기능이 있는 프로토콜을 구현하면 된다.

파일 이름공간에서, 보통 파일 보호 비트는 소켓 연결에 접근하는 사람을 제어할 수 있다.

int listen (int socket, unsigned int n) 함수

listen함수는 소켓socket을 연결을 수락이 가능하도록 하여, 그것을 서버소켓으로 만든다.
인수 n은 미결된 연결의 큐의 길이를 명시한다.
 
listen함수는 성공시에 0을 실패시 -1을 반환하며, 이 함수에 정의된 errno에러 상황은 다음과 같다.

EBADF : 인수socket는 유효한 파일서술자가 아니다.

ENOTSOCK : 인수socket이 소켓이 아니다.

EOPNOTSUPP : 소켓socket는 이 기능을 지원하지 않는다.

 

11. 8. 3 연결 수락

서버가 연결요구를 수신하였을 때, 연결요구를 수락함으로써 연결이 끝날 수 있다. accept함수를 사용하여 그것을 수행한다. 서버로 설정된 소켓은 여러 클라이언트로부터의 연결요구를 수락할 수 있다. 서버의 원 소켓(original socket)은 연결의 부분이 되지 않는다. 대신에, accept는 연결에 특정한 새로운 소켓을 만든다. accpect는 이 소켓에 대한 서술자를 반환한다. 서버의 원 소켓은 다음 연결요구를 듣기 위하여 계속 남아있다.

서버소켓의 비결된 연결요구의 수는 제한되어 있다. 만약 클라이언트들로 부터의 연결 요구가 서버가 그것을 처리할 수 있는 거 보다 더 빠르다면, 큐가 가득 채워 질 것이고, 추가적인 요구는 ECONNREFUSED에러로 거절된다. 비록 시스템 내부의 큐가 제한되어 있을 지라도 listen함수의 인수로 이 큐의 최대 길이를 명시할 수 있다.

int accept (int socket, struct sockaddr *addr, size_t *length_ptr) 함수

이 함수는 서버소켓 socket의 연결요구를 수락하는데 사용된다. 만약 미결된 연결이 없고 소켓socket이 블록킹되도록 세트되어있으면 accept함수는 계속 기다린다. (블록킹 안되는 소켓으로 기다리게 하도록 선택할 수도 있다. ) 블록킹 안되는 모드에 관한 정보는 8. 10절 [파일 상태 플랙] 을 보라. addr과 length_ptr인수는 초기화된 연결의 클라이언트 소켓의 이름에 관한 정보를 반환하는데 사용된다. 11. 3절 [소켓주소]에 정보포맷에 관한 정보가 있다. 연결을 수락하는 것은 socket부분을 연결의 일부로 만들지 않는다. 대신에, 연결될 새로운 소켓을 생성한다.
 
accept의 정상적인 반환치는 새로운 소켓의 파일서술자이다. accept후에, 원래의 소켓인 socket은 열려 있고, 연결되지 않은 상태로 남아있게 된다. 그리고 그것이 닫힐 때까지 듣기를 계속할 것이다. accept를 호출하여 이 소켓을 통하여 추가적인 연결을 수락할 수 있다.
 
만약 에러가 발생하면 accept는 -1을 반환한다.
다음은 이 함수에 정의되어 있는 errno에러조건이다:

EBADF : socket인수가 유효한 파일서술자가 아니다.

ENOTSOCK : 서술자 socket인수가 소켓이 아니다.

EOPNOTSUPP : 서술자 socket이 이 기능을 지원하지 않는다.

EWOULDBLOCK : socket이 블록킹안되는 모드로 세트되어 있고, 즉시 연결될 수 있는 미결의 연결이 없다.

accept함수는 비연결성 통신 스타일의 소켓에는 사용될 수 없다.

 

11. 8. 4 누가 나에게 연결되어 있나

int getpeername (int socket, struct sockaddr *addr, size_t *length_ptr) 함수

getpeername함수는 socket이 연결되어 있는 소켓의 주소를 반환한다; addr과 lengh_ptr에 의해 명시된 메모리 공간에 그 주소가 저장된다. *length_ptr안에 주소의 길이를 저장한다.
주소의 포맷에 관하여는 11. 3절 [소켓 주소]을 보라. 어떤 운영체제에서, getpeername은 단지 인터넷 도메인의 소켓에서만 동작한다.
 
성공시에 0을 실패시에 -1을 반환한다. 다음은 이 함수에 정의된 errno에러조건이다:

EBADF : socket인수가 유효한 파일 서술자가 아니다.

ENOTSOCK : socket서술자가 소켓이 아니다.

ENOTCONN : socket소켓이 연결되어 있지 않다.

ENOBUFS : 충분한 내부 버퍼가 없다.

 

11. 8. 5 데이터 전송

일단 소켓이 상대측과 연결이 되면, 데이터를 전송하는 일반적인 write와 read 동작을 수행할 수 있다. (8. 2절 [I/O 추상]을 보라) 소켓은 양방향 채널이므로 한쪽에서 읽고 쓰기가 가능하다. 소켓의 동작을 명시하는 몇 가지 I/O모드가 있다. 그러한 모드를 명시하려면, 더 일반적인 read와 write대신에 recv와 send 함수를 사용해야 한다. recv와 send함수는 특별한 I/O모드를 제어하기 위한 여러 가지 플랙을 명시하는데 사용되는 추가적인 인수를 필요로 한다. 예를 들어 out-of-band 데이터를 읽거나 쓰기 위하여 MSG_OOB플랙을 명시할 수 있고, 입력을 엿보기 위하여 MSG_PEEK를 세트하거나, 출력시 라우팅정보의 포함을 제어하기 위하여 MSG_DONTROUTE플랙을 명시할 수 있다.

 

11. 8. 5. 1 데이터 보내기

send함수는 'sys/socket. h'에 선언되어 있다. 만약 flag인수를 0으로 하는 경우면, send대신에 write를 쓸 수 있다. 8. 2절 [I/O 추상]을 보라. 만약 소켓이 연결되어 있는 상태라면, send나 write를 사용할 때 SIGPIPE라는 시그널을 얻을 것이다. (21. 2. 6절 [여러 가지 시그널]을 보라)

int send (int socket, void *buffer, size_t size, int flags) 함수

send함수는 write와 비슷하지만, 추가적인 flags라는 플랙을 가지고 있다. flags에 가능한 값들은 11. 8. 5. 3절[소켓 데이터 옵션]에 서술되어 있다. 이 함수는 전송된 바이트의 수를 반환하며, 실패시 -1을 반환한다. 만약 소켓이 블록 킹되지 않는 모드라면, send가 (write처럼) 데이터의 일부만 전송한 뒤 반환될 것이다. 블록 킹되지 않는 모드에 관하여는 8. 10절 [파일 상태 플랙] 을 보라. 어쨌거나, 성공시의 반환치는 에러 없이 전송된 메시지를 가리킬 뿐, 에러 없이 수신되는 것처럼 필수적인 것이 아님에 주의하라.
 
다음은 이 함수에 정의된 errno에러 조건이다.

EBADF : socket인수가 유효한 파일 서술자가 아니다.

EINTR

데이터를 보내기 전에, 시그널에 의해 인터럽트가 걸렸다. 21. 5절 [인터럽트 추상]을 보라.

ENOTSOCK : socket서술자가 소켓이 아니다.

EMSGSIZE : 소켓종류가 메시지를 작게 보낼 것을 요구하는데, 이 메시지가 너무 크다.

EWOULDBLOCK

블록 킹되지 않는 모드가 소켓에 세트되어 있으나, 쓰기 동작이 블록 킹될 것이다. (보통 send는 동작이 끝날 수 있을 때까지 블록 킹된다. )

ENOBUFS : 충분한 내부버퍼공간이 없다.

ENOTCONN : 당신은 이 소켓에 연결되어 있지 않다.

EPIPE

이 소켓은 연결되어 있지만, 연결이 깨져있다. 이 경우 send는 SIGPIPE시그널을 먼저 생성한다. 만약 시그널이 무시되거나 블록 킹되거나, 핸들러가 반환된다면, send는 EPIPE로 실패를 알리게 된다.

 

11. 8. 5. 2 데이터 수신하기

recv함수는 'sys/socket. h' 헤더 파일에 선언되어 있다. 만약 flags인수가 0이면 recv대신에 read를 써도 된다. 8. 2절 [I/O추상] 을 참조하라.

int recv (int socket, void *buffer, size_t size, int flags) 함수

recv함수는 read와 유사하지만, 추가적으로 flags라는 플랙을 가지고 있다. flags에 가능한 값은 11. 8. 5. 3절 [소켓 데이터 옵션] 에 서술되어 있다.
만약 블록 킹되지 않는 모드로 소켓에 세트되어 있고, 읽을 데이터가 없으면, recv는 기다리지 않고 즉시 실패로 끝난다. 블록 킹하다 않는 모드에 관한 정보는 8. 10절 [파일 상태 플랙]을 보라.
 
이 함수는 수신된 바이트 수를 반환하거나, 실패시 -1을 반환한다.
다음은 이 함수에 선언된 errno에러 조건이다.

EBADF : socket인수가 유효한 파일서술자가 아니다.

ENOTSOCK : socket서술자가 소켓이 아니다.

EWOULDBLOCK

소켓에 블록 킹하다 않는 모드가 세트되어 있고, 읽기 동작이 블록될 것이다. (보통 recv는 읽기 가능한 입력이 있을 때까지 블럭킹된다. )

EINTR

데이터를 읽기도 전에 시스널에 의해 인터럽트가 걸렸다. 21. 5절[인터럽트 추상]을 보라.

ENOTCONN : 당신은 이 소켓에 연결되어 있지 않다.

 

11. 8. 5. 3 소켓 데이터 옵션

send와 recv에 사용되는 flags인수는 비트마스크이다. 당신은 이 인수에 값을 얻기 위해 아래 매크로로 비트별-OR연산을 수행할 수 있다. 'sys/socket. h'헤더파일에 모두 정의되어 있다.

int MSG__OOB 매크로

out-of-band데이터를 송신하거나 수신한다. 11. 8. 8절 [Out-of-band 데이터]을 보라.

int MSG__PEEK 매크로

수신된 큐로부터 데이터를 보기만 하고 그것을 지우지는 말아라. 이것은 send가 아닌 recv같은 입력함수에서만 의미가 있다. send.

int MSG__DONTROUTE 매크로

이 메시지에 라우팅정보를 포함하지 마라. 이것은 출력 함수에서만 의미를 갖는다. 그리고 분석이나 라우팅 프로그램에서만 흥미롭게 사용된다. 그것에 대하여는 여기서 더 이상 설명하지 않는다.

 

11. 8. 6 Byte Stream 소켓 예제

이것은 인터넷 이름공간의 byte stream을 위한 연결을 만드는 프로그램의 예제이다. 한 번 서버에 연결하고 특별한 일을 하지 않는다. 그것은 단지 텍스트 문자열을 서버에게 보내고 종료한다.

#include <stdio. h>
#include <errno. h>
#include <stdlib. h>
#include <unistd. h>
#include <sys/types. h>
#include <sys/socket. h>
#include <netinet/in. h>
#include <netdb. h>
 
#define PORT 5555
#define MESSAGE "Yow!!! Are we having fun yet?!?"
#define SERVERHOST "churchy. gnu. ai. mit. edu"
 
void
write_to_server (int filedes)
{
int nbytes;
 
nbytes =write (filedes, MESSAGE, strlen (MESSAGE) + 1);
if (nbytes < 0) {
perror ("write");
exit (EXIT_FAILURE);
}
}
 
int
main (void)
{
extern void init_sockaddr (struct sockaddr_in *name,
const char *hostname,
unsigned short int port);
int sock;
struct sockaddr_in servername;
 
/* 소켓 만들기 */
if ((sock = socket (PF_INET, SOCK_STREAM, 0)) < 0) {
perror ("socket (client)");
exit (EXIT_FAILURE);
}
 
/* 서버에 연결 */
init_sockaddr (&servername, SERVERHOST, PORT);
if (connect (sock,(struct sockaddr *) &servername,sizeof (servername)) < 0) {
perror ("connect (client)");
exit (EXIT_FAILURE);
}
 
/* 서버에 데이터 보내기 */
write_to_server (sock);
close (sock);
exit (EXIT_SUCCESS);
}

 

11. 8. 7 Byte Stream Connection 서버 예제

서버 쪽은 좀더 복잡하다. 여러 클라이언트가 동시에 서버에 연결할 수 있도록 하기를 원하므로, 간단히 하나의 read나 recv를 호출하여 하나의 클라이언트로부터 입력을 기다려서는 안 된다. 대신에, 모든 열린 소켓으로부터 입력을 기다리도록(8. 6절 [I/O 기다리기]), select를 사용하는 것이 올바른 것이다. 이것은 또한 추가적인 연결 요구를 다루도록 허락한다.

이 특별한 서버는 클라이언트로부터 메시지를 한 번 받는 다는 것 외에 특별히 흥미로운 일을 하지 않는다. 그것은 (클라이언트가 연결을 종료하는 결과로써) 파일의 끝(end-of-file)조건을 검출하였을 때, 그 클라이언트에 대한 소켓을 닫는다.

이 프로그램은 소켓주소를 세트하기 위해 make_socket과 init_sockaddr를 사용하였다. ; 11. 5. 7 [Inet 예제]을 보라

#include <stdio. h>
#include <errno. h>
#include <stdlib. h>
#include <unistd. h>
#include <sys/types. h>
#include <sys/socket. h>
#include <netinet/in. h>
#include <netdb. h>
 
#define PORT 5555
#define MAXMSG 512
 
int
read_from_client (int filedes)
{
char buffer[MAXMSG];
int nbytes;
 
nbytes = read (filedes, buffer, MAXMSG);
if (nbytes < 0) { /* 읽기 에러 */
perror ("read");
exit (EXIT_FAILURE);
} else if (nbytes == 0) /* 파일의 끝 */
return -1;
else { /* 데이터 읽기 */
fprintf (stderr, "Server: got message: `%s'\n", buffer);
return 0;
}
}
 
int
main (void)
{
extern int make_socket (unsigned short int port);
int sock;
int status;
fd_set active_fd_set, read_fd_set;
int i;
struct sockaddr_in clientname;
size_t size;
 
/* Create the socket and set it up to accept connections. */
sock = make_socket (PORT);
if (listen (sock, 1) < 0) {
perror ("listen");
exit (EXIT_FAILURE);
}
 
/* Initialize the set of active sockets. */
FD_ZERO (&active_fd_set);
FD_SET (sock, &active_fd_set);
while (1)
{
/* Block until input arrives on one or more active sockets. */
read_fd_set = active_fd_set;
if (select (FD_SETSIZE, &read_fd_set, NULL, NULL, NULL) < 0)
{
perror ("select");
exit (EXIT_FAILURE);
}
 
/* Service all the sockets with input pending. */
for (i = 0; i < FD_SETSIZE; ++i) {
if (FD_ISSET (i, &read_fd_set)) {
if (i == sock) {
/* Connection request on original socket. */
size = sizeof (clientname);
if (accept (sock,(struct sockaddr *) &clientname, &size) < 0) {
perror ("accept");
exit (EXIT_FAILURE);
}
fprintf (stderr,
"Server: connect from host %s, port %hd. \n",
inet_ntoa (clientname. sin_addr),
ntohs (clientname. sin_port));
FD_SET (status, &active_fd_set);
} else {
/* Data arriving on an already-connected socket. */
if (read_from_client (i) < 0) {
close (i);
FD_CLR (i, &active_fd_set);
}
} /* if 의 끝 */
} /* if 의 끝 */
} /* for 의 끝 */
} /* while 의 끝 */
} /* main 의 끝 */

 

11. 8. 8 Out-of-Band 데이터

연결을 가진 스트림은 일반 데이터보다 높은 우선 순위로 배달되는 데이터인 out-of-Band데이터를 가능하게 한다. 전형적으로 out-of-band데이터를 전송하는 이유는 예외적인 상황의 통보 때문이다. out-of-band데이터를 보내는 방법은 MSG_OOB플랙을 세트한 (11. 8. 5. 1절[데이터 전송]참고) send를 사용하는 것이다.

수신 프로세스가 순서대로 그것을 받지 않으므로, out-of-band 데이터는 더 높은 우선 순위로 받아진다. 즉, 다음 가능한 out-of-band 데이터를 읽으려면, MSG_OOB플랙을 세트하여 recv를 사용한다. (11. 8. 5. 2절 [데이터 수신]을 보라). 보통 읽기 명령은 out-of-band데이터를 읽지 않는다. 그것들은 단지 일반 데이터만 읽는다. 소켓이 out-of-band데이터가 있음을 알게 되면, 소유프로세스나 소켓의 프로세스 그룹에 SIGURG시그널을 보낸다. F_SETOWN명령으로 fcntl함수를 사용하여 소유자를 명시할 수 있다. 8. 12절 [인터럽트 입력]을 보라. 또한 이 시그널의 핸들러를 설정할 수 있는데, out-of-band데이터를 읽는 등의 적절한 동작을 취하기 위해, 21장[시그널 핸들링]을 보라.

다른 것으로, select함수를 사용하여, 미결의 out-of-data를 검사하거나 out-of-band데이터가 있을 때까지 기다릴 수 있다. ; 그것은 소켓 예외 조건을 기다릴 수 있다. select에 관한 많은 정보는 8. 6절 [I/O를 기다리기]을 보라.

(SIGURG를 사용하거나 select를 사용할 것이던지) out-of-band 데이터의 통고는 out-of-band데이터가 진행 중에 있음을 가리킨다. ; 즉 데이터는 아직 실제로 도착하지 않았다. 만약 그것이 완전히 도착하기 전에 out-of-data를 읽으려고 하면, recv 는 EWOULDBLOCK에러로 실패하게 된다.

out-of-band데이터 전송은 자동으로 일반 데이터의 흐름 안에 "마크"를 둔다. 그것은 어디에 out-of-band데이터가 어디에 있을지를 보여준다. out-of-band 데이터가 먼저 보내진 모든 것들을 취소시킨다는 의미를 가질 때 유용하다. 여기에 수신 프로세스에서 마크전에 보내진 보통 데이터인지 아닌지 어떻게 테스트하는지가 나와있다.

success = ioctl (socket, SIOCATMARK, &result);

이 함수는 out-of-band 마크 앞의 일반 데이터를 무시하는 것이다.
int
discard_until_mark (int socket)
{
while (1)
{
/* This is not an arbitrary limit; any size will do. */
char buffer[1024];
int result, success;
 
/* If we have reached the mark, return. */
success = ioctl (socket, SIOCATMARK, &result);
if (success < 0)
perror ("ioctl");
if (result)
return;
 
/* Otherwise, read a bunch of ordinary data and discard it This is guaranteed not to read past the mark if it starts before the mark. */
success = read (socket, buffer, sizeof buffer);
if (success < 0)
perror ("read");
}
}

만약 마크앞의 일반 데이터를 무시하지 않으려면, 그것을 어떻게든 읽기 위해, out-of-band데이터를 위한 내부시스템버퍼 공간을 만들어야 한다. 만약 out-of-band데이터를 읽으려 하는데 EWOULDBLOCK에러를 받는다면, 약간의 일반데이터를 (그것을 얻었을 때 사용할 수 있도록 저장을 하며) 읽기를 시도하라. 그리고 그것이 공간을 만드는지를 보아라. 여기 예제가 있다.

struct buffer
{
char *buffer;
int size;
struct buffer *next;
};
 
/* Read the out-of-band data from SOCKET and return it as a `struct buffer', which records the address of the data and its size.
It may be necessary to read some ordinary data in order to make room for the out-of-band data. If so, the ordinary data is saved as a chain of buffers found in the `next' field of the value. */
 
struct buffer *
read_oob (int socket)
{
struct buffer *tail = 0;
struct buffer *list = 0;
 
while (1) {
/* This is an arbitrary limit. Does anyone know how to do this without a limit? */
char *buffer = (char *) xmalloc (1024);
struct buffer *link;
intsuccess;
int result;
 
/* Try again to read the out-of-band data. */
success = recv (socket, buffer, sizeof buffer, MSG_OOB);
if (success >= 0) {
/* We got it, so return it. */
struct buffer *link
=(struct buffer*) xmalloc(sizeof(struct buffer));
link->buffer = buffer;
link->size = success;
link->next = list;
return link;
}
 
/* If we fail, see if we are at the mark. */
if (ioctl (socket, SIOCATMARK, &result) < 0)
perror ("ioctl");
 
if (result) {
/* At the mark; skipping past more ordinary data cannot help. So just wait a while. */
sleep (1);
continue;
}
 
/* Otherwise, read a bunch of ordinary data and save it. This is guaranteed not to read past the mark if it starts before the mark. */
 
if (read (socket, buffer, sizeof buffer) < 0) /* Save this data in the buffer list. */
{
struct buffer *link
= (struct buffer *) xmalloc(sizeof (struct buffer));
link->buffer = buffer;
link->size = success;
 
/* Add the new link to the end of the list. */
if (tail)
tail->next = link;
else
list = link;
tail = link;
} /* if 의 끝 */
} /* while 의 끝 */
}


11. 9 데이터그램 소켓 명령

이번 절에서는 어떻게 연결 없는 통신스타일(SOCK_DGRAM과 SOCK_RDM스타일)을 사용하는지에 대해 서술한다. 이러한 스타일을 사용하여 패킷 안에 데이터를 묶어 넣고, 각 패킷은 독립적인 통신을 하게 된다. 그리고 각 패킷마다 목적지를 명시해야 한다.

데이터그램 패킷은 편지와 같다. 당신은 개별적으로 하나씩 목적지를 명시하여 보낸다. 그것들은 순서가 바뀌어 도착할 수도 있다.

listen과 accept함수는 비연결 통신 스타일을 사용하는 소켓에서 사용할 수 없다.

11. 9. 1 데이터그램 전송

데이터그램소켓으로 데이터를 보내는 보통 방법은 'sys/socket. h'에 선언되어 있는 sendto함수를 사용하는 것이다. 데이터그램 소켓에 connect를 호출할 수 있지만, 나중에 데이터전송을 위해 기본경로를 한 번 명시한다. 소켓이 기본경로를 가지면, send를 사용할 수 있다. (11. 8. 5. 1절 [데이터 전송]을 보라) 또한 패킷을 전송하기 위해 write를 사용할 수 있다. (8. 2절 [I/O 추상] 을 보라) addr인수에 AF_UNSPEC의 주소포맷을 사용하여 connect를 호출함으로써 기본경로를 취소할 수 있다. connect함수에 관한 더 많은 정보는 11. 8. 1절 [연결하기] 을 보라.

int sendto (int socket, void *buffer size_t size, int flags, struct sockaddr *addr, size_t length) 함수

sendto함수는 목적지가 addr과 length인수로 명시되어 있는 socket을 통하여 버퍼에 있는 데이터를 전송한다. size인수는 전송할 바이트 수를 말한다.
 
flags는 send에서와 같은 방법으로 해석된다. 11. 8. 5. 3절 [소켓 데이터 옵션]을 보라.
반환치와 에러상태도 역시 send와 비슷하다. 그러나 시스템이 에러를 검출하게 할 수는 없다. 대부분의 에러는 패킷이 없어지거나 수신할 주소가 명시되어 있지 않은 것이다. 그래서 당신의 OS는 보통 에러가 난 것을 알지 못할 것이다. 예전의 호출에 관련된 문제 때문에 sendto는 에러가 났다고 할 수도 있다.

 

11. 9. 2 데이터그램 수신

recvfrom함수는 데이터그램 소켓으로부터 패킷을 읽고 또한 어디서부터 온 것인지 알려준다. 이 함수는 'sys/socket. h'에 선언되어 있다.

int recvfrom(int socket, void *buffer, size_t size, int flags, struct sockaddr *addr, size_t *length_ptr)함수

recvfrom함수는 socket소켓으로부터 패킷하나를 읽어 buffer라는 버퍼에 넣는다. size인수는 한 번 읽을 때 최대의 바이트 수이다. 만약 패킷이 size바이트보다 더 크면, 패킷의 앞부분만 얻을 수 있고 specify flags를 원하지 않으면 사용된다. (8. 2절[I/O Primitives]를 보라).
 
만약 (어디서부터 올 것인지 아는 경우, 누가 보냈던지 똑같이 취급하기 때문에) 누가 패킷을 보냈는지 알 필요가 없다면 recvfrom대신에 평탄한 recv를 쓸 수 있다. (11. 8. 5. 2절 [데이터 수신] 을 보라)

 

11. 9. 3 데이터그램 소켓 예제

여기에 파일이름공간의 데이터그램 스트림을 통해 메시지를 보내는 예제프로그램의 세트가 있다. 클라이언트와 서버프로그램들 모두가 소켓을 만들고 이름짓기 위하여, 11. 4절 [파일 이름공간]에 설명되어 있는 make_named_socket 함수를 사용한다.

먼저 여기 서버프로그램이 있다. 그것은 메시지가 도착할 때까지 계속 기다리고 메시지가 오면 다시 송신자에게 되돌려주는 것을 반복한다. 분명히 특별히 유용한 프로그램이 아니지만, 일반적인 아이디어를 보여준다.

#include <stdio. h>
#include <errno. h>
#include <stdlib. h>
#include <sys/socket. h>
#include <sys/un. h>
 
#define SERVER "/tmp/serversocket"
#define MAXMSG 512
 
int
main (void)
{
int sock;
char message[MAXMSG];
struct sockaddr_un name;
size_t size;
int nbytes;
 
/* Make the socket, then loop endlessly. */
 
sock = make_named_socket (SERVER);
while (1)
{
/* Wait for a datagram. */
size = sizeof (name);
nbytes = recvfrom (sock, message, MAXMSG, 0,
(struct sockaddr *) & name, &size);
if (nbytes < 0)
{
perror ("recfrom (server)");
exit (EXIT_FAILURE);
}
 
/* Give a diagnostic message. */
fprintf (stderr, "Server: got message: %s\n", message);
/* Bounce the message back to the sender. */
nbytes = sendto (sock, message, nbytes, 0, (struct sockaddr *) & name, size);
if (nbytes < 0)
{
perror ("sendto (server)");
exit (EXIT_FAILURE);
}
}
}

 

11. 9. 4 데이터그램 읽기 예제

여기 위에 있는 서버프로그램에 맞는 클라이언트 프로그램이 있다.

그것은 서버에게 데이터그램을 송신하고 나서 응답을 기다린다. 클라이언트를 위한 소켓에는 (서버와 같이) 주어진 이름을 가지고 있음을 주시하라. 그것은 서버가 메시지를 바로 클라이언트에게 되돌리게 한다. 소켓이 연계된 연결상태를 가지지 않기 때문에, 서버는 클라이언트의 이름을 참조하여 그렇게 할 수 있는 것이다.

#include <stdio. h>
#include <errno. h>
#include <unistd. h>
#include <stdlib. h>
#include <sys/socket. h>
#include <sys/un. h>
 
#define SERVER "/tmp/serversocket"
#define CLIENT "/tmp/mysocket"
#define MAXMSG 512
#define MESSAGE "Yow!!! Are we having fun yet?!?"
 
int
main (void)
{
externint make_named_socket (const char *name);
int sock;
char message[MAXMSG];
struct sockaddr_un name;
size_t size;
int nbytes;
 
/* Make the socket. */
sock = make_named_socket (CLIENT);
 
/* Initialize the server socket address. */
name. sun_family = AF_UNIX;
strcpy (name. sun_path, SERVER);
size = strlen (name. sun_path) + sizeof (name. sun_family);
 
/* Send the datagram. */
nbytes = sendto (sock, MESSAGE, strlen (MESSAGE) + 1, 0,
 
(struct sockaddr *) & name, size);
if (nbytes < 0) {
perror ("sendto (client)");
exit (EXIT_FAILURE);
}
 
/* Wait for a reply. */
nbytes = recvfrom (sock, message, MAXMSG, 0, NULL, 0);
if (nbytes < 0) {
perror ("recfrom (client)");
exit (EXIT_FAILURE);
}
 
/* Print a diagnostic message. */
fprintf (stderr, "Client: got message: %s\n", message);
 
/* Clean up. */
remove (CLIENT);
close (sock);
}

데이터그램소켓 통신은 비신뢰성임을 명심하라. 이 예제에서, 메시지가 서버에 도달하지 못하거나 서버가 응답을 하지 않을 지라도, 클라이언트 프로그램은 막연히 기다린다. 만약 원한다면, 그것을 종료시키고 리셋 시키는 프로그램을 유저가 수행하여야 한다. 자동적인 해결법은 select(8. 6절 [I/O기다리기])를 사용하여 응답의 시간제한을 설정하여, 시간제한이 초과되면 메시지를 재 전송하거나 소켓을 끝내고 종료하는 것이다.


11. 10 inetd 데몬

위에서 듣기를 하는 서버프로그램을 작성하는 법을 설명했다. 어떤 서버는 그것에 접속하기 위해 미리 수행되고 있어야 한다. 인터넷포트를 위한 서비스를 제공하는 다른 방법은 inetd 데몬 프로그램을 이용하여 듣기를 하도록 하는 것이다. inetd는 명시된 포트의 세트에서의 메시지를 (select를 사용하여) 기다리며 수행되는 프로그램이다. 그것이 메시지를 받았을 때, (만약 소켓스타일이 연결이 필요하면) 연결을 수락하고 나서, 해당 서버프로그램을 수행하도록 자식프로세스를 만들어 낸다. '/etc/inetd. conf'파일 내에 포트들과 프로그램들을 명시한다.

 

11. 10. 1 inetd 서버들

inetd에 의해 수행되는 서버프로그램을 작성하는 것은 매우 간단하다. 누군가 해당 포트로 연결을 연결하려는 때마다, 새로운 서버 프로세스가 시작된다. 연결은 단지 이 순간에만 존재한다. 소켓은 서버프로세스에서 표준 입력 서술자와 표준 출력 서술자 (서술자 0과 1)처럼 된다. 가끔은 프로그램이 일반적인 I/O기능을 필요로 할 때도 있다. ; 실제로, 소켓에 대해 아무 것도 모르는 범용 필터 프로그램도 inetd에 의해 바이트 스트림 서버처럼 동작할 수 있다.

비연결성 통신 스타일의 서버에도 inetd를 사용할 수 있다. 연결이 필요 없으므로, 이러한 서버들을 위해 inetd는 연결을 수락하려는 시도를 하지 않는다. 그것은 바로 서술자 0으로부터 수신된 데이터그램패킷을 읽을 수 있는 서버프로그램을 시작한다. 서버프로그램은 한 요구를 다룰 수 있고 종료할 수 있다. 또는 더 이상의 도착이 없을 때까지 요구를 읽는 것을 계속하고 나서 종료할 수도 있다. inetd를 설정할 때, 위의 두 가지 기술 중 어떤 것을 사용할 것인지 명시해야 한다.

 

11. 10. 2 inetd 설정

'/etc/inetd. conf'파일은 inetd에게 들을 포트번호와 수행될 서버프로그램이 무엇인지 말해준다. 보통 이 파일의 각 요소는 한 줄씩이다. 그러나, 그것을 화이트스페이스로 시작하는 요소의 첫 번째 줄을 제외한 모든 줄로 나눌 수 있다. '#'로 시작하는 줄은 주석 문이다.

여기 '/etc/inetd. conf'안의 두 기본적인 요소가 있다.

ftp stream tcp nowait root /libexec/ftpd ftpd
talk dgram udp wait root /libexec/talkd talkd

한 요소는 다음 형식을 가진다.

서비스 스타일 프로토콜 대기 사용자명 프로그램 인수

서비스 부분은 이 프로그램이 어떤 서비스를 제공하는 지를 말한다. 그것은 '/etc/services'안에 서비스의 이름이 정의되어 있을 것이다. inetd는 이 요소를 위해 들을 어떤 포트를 사용할 것인가 결정하는데 서비스를 사용한다.

스타일과 프로토콜 부분은 통신스타일과 소켓을 듣는데 사용되는 프로토콜을 명시한다. 스타일은 'SOCK_'이 빠지고, 소문자로 변환된 통신스타일의 이름이다. 예를 들면 stream 또는 dgram이다. 프로토콜은 '/etc/protocols'에 나열된 프로토콜 중 하나이다. 전형적인 프로토콜이름은 바이트 스트림 통신을 위한 'tcp'와 비신뢰성 데이터그램을 위한 'udp'가 있다.

대기 부분은 'wait' 또는 'nowait' 중 하나이다. 만약 통신스타일이 비연결성이고, 서버가 시작시에만 많은 다중 요구를 다룬다면 'wait'을 사용하라. 만약 inetd가 각 메시지나 요구가 들어 올 때마다 새로운 프로세스를 시작할 경우 'nowait'를 사용하라. 만약 스타일이 연결성이면 대기 부분은 'nowait'를 써라.

사용자명은 서버를 수행하는 사용자의 이름이다. inetd는 root로서 수행된다. 그래서 마음대로 그 자식의 사용자ID를 세트할 수 있다. 가능하면 사용자명에 'root'를 피하는 것이 좋다. ; 그러나 telnet과 ftp같은 어떤 서버는 사용자 이름과 암호를 읽는다. 이러한 서버들은, 네트워크로부터 오는 데이터에 의한 명령 같이 로그인 을 할 수 있도록 처음에 root가 될 필요가 있다.

프로그램 부분과 인수 부분은 서버를 시작할 때 수행되는 명령어를 명시한다. 프로그램은 수행 가능한 파일의 절대 경로의 파일이름이어야 한다. 인수는 화이트스페이스로 구분된 단어로 구성되어, 프로그램의 명령어라인 인수가 된다. 인수의 첫 번째 단어는 인수 0으로 (디렉토리 없이) 프로그램의 이름 그 자체로 된다.

만약 '/etc/inetd. conf'를 수정하면, inetd프로세스에게 SIGHUP신호를 inetd에게 파일을 다시 읽고, 새로운 내용을 얻게 하여야 한다. ps를 사용하여 inetd의 프로세스ID가 바뀌었는지 확인하면 된다.


11. 11 소켓 옵션

이 장에서는 소켓의 행동과 그 밑에 있는 통신 프로토콜을 변경하는 여러 가지 옵션을 설정하고 읽는 방법을 서술한다. 소켓옵션을 다룰 때, 옵션이 속하는 레벨을 명시해 주어야 한다. 이것은 옵션이 소켓인터페이스에 적용되는 지, 저수준 통신 프로토콜 인터페이스에 적용되는 지를 서술한다.

 

11. 11. 1 소켓 옵션 함수

여기에 소켓옵션을 변경하고 시험하는 함수가 있다. 'sys/socket. h'안에 그것들이 선언되어 있다.

int getsockopt (int socket, int level, int optname, void *optval, size_t *optlen_ptr) 함수

getsockopt함수는 socket소켓의 level레벨의 optname 옵션의 옵션 값에 대한 정보를 얻는다.
옵션 값은 optval이 가리키는 버퍼에 저장된다. 호출 전에, 이 버퍼의 크기인 *optlen_ptr안에 제공해야 한다. 반환할 때, 실제로 버퍼에 저장된 정보의 바이트 수를 담고 있다.
대부분의 옵션은 optval버퍼를 정수 값으로 해석된다.
 
성공시 getsockopt의 실제반환값은 0이고 실패시 -1이다.  
다음에 errno에러조건이 정의되어 있다.

EBADF : socket인수가 유효한 파일 서술자가 아니다.

ENOTSOCK : 서술자 socket이 소켓이 아니다.

ENOPROTOOPT : optname이 주어진 레벨에 맞지 않다.

int setsockopt (int socket, int level, int optname, void *optval, size_t optlen) 함수

이 함수는 socket소켓의 level레벨에 소켓옵션 optname을 설정하는데 사용된다. 옵션의 값은 optlen크기를 가진 optval버퍼에 전달된다. setsockopt의 반환치와 에러코드는 getsockopt과 같다.

 

11. 11. 2 소켓-레벨 옵션

int SOL__SOCKET 상수

이 절에 서술되어 있는 소켓레벨 옵션을 다루려면, getsockopt 나 setsocket의 level인수로 이 상수를 사용하라. 여기에 socket-level 옵션 이름의 표가 있다. 모두 'sys/socket. h'헤더 파일에 정의되어 있다.

SO_DEBUG

하위 프로토콜 모듈의 디버깅 정보의 저장할 것인가 여부를 설정한다. 그 값은 int형을 가진다. 0이 아닌 값은 저장하겠다는 것을 의미한다.

SO_REUSEADDR

이 옵션은 bind( 11. 3. 2절 [주소 설정]을 보라)가 이 소켓의 지역주소를 재 사용하는 것을 허락할 것인지 여부를 제어한다. 만약 이 옵션을 가능하도록 한다면, 같은 인터넷 포트로부터 두 소켓을 가질 수 있게 된다; 그러나, 시스템은 인터넷을 혼란시키는 두 별개의 이름을 가진 소켓을 사용하는 것을 허락하지 않는다. 이 옵션이 있는 이유는 FTP같은 상위 프로토콜이 같은 소켓번호의 재사용을 유지하기를 원하기 때문이다. 값은 int형을 가지며, 0이 아닌 수는 "그렇다(yes)"를 의미한다.

SO_KEEPALIVE

이 옵션은 하위 프로토콜이 주기적으로 메시지를 연결된 소켓으로 전송할 것인지 여부를 제어한다. 만약 서로 이러한 메시지의 응답을 실패하면 연결이 끊어지도록 한다. 값은 int형이고 0이 아닌 값은 "그렇다"를 의미한다.

SO_DONTROUTE

이 옵션은 송신하는 메시지가 일반 메시지 라우팅 기능을 우회할 것인지 여부를 제어한다. 만약 설정되면, 메시지는 직접 네트워크 인터페이스로 보내진다. 값은 int형이며 0이 아닌 값은 "그렇다"의 의미이다.

SO_LINGER

이 옵션은 신뢰도 있는 전달을 보장하는 어떤 형태의 소켓이 닫힐 때 여전히 전송되지 못한 메시지들을 가지고 있을 때, 무엇이 일어날 것인지를 명시한다. 11. 7. 2절 [소켓 닫기]을 보라. 값은 구조체 linger이다.

struct linger 자료형

이 구조체는 다음 멤버를 가진다:
int l_onoff
이 부분은 참거짓으로 해석된다. 만약 0이 아니면, close는 데이터가 전달되거나 시간제한이 경과될 때까지 프로그램의 수행이 중단된다.
int l_linger
이것은 초단위로 시간제한을 명시한다.

SO_BROADCAST

이 옵션은 데이터그램이 소켓으로부터 브로드캐스트될 것인지 여부를 제어한다. 값은 int형이고 0이 아닌 값은 "그렇다"를 의미한다.

SO_OOBINLINE

만약 이 옵션이 설정되면, 소켓에 수신된 out-of-band 데이터가 보통 입력 큐에 위치하게 된다. 이것은 MSG_OOB플랙 명시 없이 read나 recv를 사용하여 그것을 읽을 수 있도록 한다. 11. 8. 8절 [Out-of-Band 데이터]을 보라. 값은 정수형이며 0이 아닌 값은 "그렇다"를 의미한다.

SO_SNDBUF

이 옵션은 출력버퍼의 크기를 얻거나 설정한다. 값은 size_t형이고, 바이트 크기이다.

SO_RCVBUF

이 옵션으 입력버퍼의 크기를 얻거나 설정한다. 값은 size_t형이고, 바이트 크기이다.

SO_STYLE, SO_TYPE

이 옵션은 getsockopt에만 사용될 수 있을 것이다. 그것은 소켓의 통신 스타일을 얻는데 사용된다. SO_TYPE은 전통적인 이름이고 SO_STYLE은 GNU에서 채택한 이름이다. 값은 int형이고, 통신스타일을 나타낸다. 11. 2절 [통신 스타일]을 보라.

SO_ERROR

이 옵션은 getsockopt에만 사용할 수 있을 것이다. 소켓의 에러상태를 리셋 하는데 사용된다. 값은 int형이고 전번의 에러 상태를 나타낸다.


11. 12 네트워크 데이터베이스

많은 시스템은 시스템 개발자들에게 알려진 네트워크 리스트를 기록하는 데이터베이스를 가지고 있다. 이것은 보통 '/etc/networks' 또는 네임서버의 같은 파일을 유지한다. 이 데이터베이스는 route같은 라우팅 프로그램에서 유용하다. 그러나 네트워크를 통해 단순히 통신하는 프로그램에게는 별 필요가 없다. 우리는 'netdb. h'에 선언된 이 데이터베이스 접근 함수를 볼 것이다.

struct netent 자료형

이 자료형은 네트워크 데이터 베이스의 요소에 관한 정보를 표현하는데 사용된다. 그것은 다음 멤버를 가진다.

char *n_name

이것은 네트워크의 공식적인 이름이다.

char **n_aliases

네트워크의 또다른 이름들이 문자열의 벡터처럼 표현되어 있다. 널 포인터로 배열이 끝난다.

int n_addrtype

이것은 네트워크 번호의 종류이다. ; 이것은 인터넷 네트워크에 대해 항상 AF_INET이다.

unsigned long int n_net

이것은 네트워크 번호이다. 네트워크 번호는 호스트 바이트 순서로 반환된다. 11. 5. 5절 [바이트 순서]을 보라.

특정 네트워크에 관한 정보를 위해 네트워크 데이터베이스를 검색하기 위해 getnetbyname 또는 getnetbyaddr함수를 사용하라. 정보는 정적으로 할당된 구조체에 정보가 반환된다. ; 만약 그것을 저장할 필요가 있으면 그 정보를 복사해두어야 한다.

struct netent * getnetbyname (const char *name) 함수

getnetbyname함수는 name이라는 네트워크에 관한 정보를 반환한다. 만약 그와 같은 네트워크가 없으면 널 포인터를 반환한다.

struct netent * getnetbyaddr (long net, int type) 함수

getnetbyaddr함수는 type라는 종류와 net라는 숫자를 가진 네트워크에 관한 정보를 반환한다. 인터넷 네트워크의 종류를 위해서 AF_INET값을 명시해야 한다. getnetbyaddr은 만약 그러한 네트워크가 없으면 널 포인터를 반환한다. 또한 setent, getnetent 와 endnetent를 사용하여 네트워크 데이터 베이스를 일일이 살필 수 있다. 이러한 함수를 사용할 때는 주의하라. 그것들은 재진입성이 없기 때문이다.

void setnetent (int stayopen) 함수

이 함수는 네트워크 데이터베이스를 맨 앞부터 읽을 수 있도록 한다. 만약 stayopen인수가 0이 아니면, 플랙을 세트하여 추후의 getnetbyname이나 getnetbyaddr 호출이 (보통 그 함수들이 닫으므로)데이터베이스를 닫지 않도록 한다. 만약 각 호출시 데이터베이스를 다시 여는 것을 피하여 그러한 함수를 여러 번 호출할 때 매우 유용하다.

struct netent * getnetent (void) 함수

이 함수는 네트워크 데이터베이스의 다음 요소를 반환한다. 만약 더 이상의 요소가 없으면 널 포인터를 반환한다.

void endnetent (void) 함수

이 함수는 네트워크 데이터 베이스를 닫는다.


목차 이전 : 10. 파이프와 FIFO 다음 12 : 저수준 터미널 인터페이스

'Programming > Unix Programing' 카테고리의 다른 글

13. 수학 함수  (0) 2007.12.22
12. Low-Level Terminal Interface  (0) 2007.12.22
11. Sockets  (0) 2007.12.22
10. Pipes and FIFO  (0) 2007.12.22
9. 파일시스템 인터페이스  (0) 2007.12.22
8. 저수준 입출력  (0) 2007.12.22
Comment 0 Trackback 0
Top

10. Pipes and FIFO

목차 이전 : 9. 파일 시스템 인터페이스 다음 : 11. 소켓


10 파이프와 FIFO

파이프는 프로세스사이의 통신을 위한 메커니즘이다; 한 프로세스에 의해 파이프에 쓰여진 것은 다른 프로세스에 의해 읽혀질 수 있다. 데이터는 선입선출(FIFO) 순서로 다뤄진다. 파이프는 이름을 가지고 있지 않다.

그것이 한 번 사용하기 위해 만들어지고 양끝은 그 파이프를 만든 단일 프로세스로부터 상속되어져야만 한다. 특별한 파일인 FIFO는 파이프와 유사하지만, 파이프가 이름이 없고 임시적인 연결인 것에 비해, FIFO는 이름을 갖고 있다. 프로세스는 그것을 통해 통신의 목적으로, 이름을 가진 FIFO를 개방한다.

파이프나 FIFO는 동시에 양끝을 개방해야한다. 만일 당신이 파이프나 FIFO에 쓰기 위한 프로세스를 가지지 않은 파이프나 FIFO로부터 읽으려면, 읽기는 파일의 끝을 리턴한다. 읽기 프로세스를 가지지 않은 파이프나 FIFO에 쓰기는 에러가 발생된 것으로 취급된다. 그것은 SIGPIPE 신호로 일반화되고, 만일 그 신호가 블록 되어졌다면 에러코드 EPIPE로 에러임을 나타낸다.

특별한 파일인 파이프나 FIFO도 파일 위치시키기(positioning)를 허용한다. 읽기와 쓰기 동작은 순차적으로 일어난다; 파일의 앞에서부터 읽어서 그 끝에 출력한다.


10. 1 파이프 만들기

파이프를 만드는 함수로는 pipe함수가 있다. 이것은 파이프의 양끝을 읽기와 쓰기로 만든다. 이것은 단일한 프로세스에 사용하기에 유용하지 않고, 특별히 한 프로세스가 하나 혹은 더 많은 자식 프로세스들을( 23. 4절 [Creating a Process] 참조) 생성하기 전에 단지 파이프를 만든다. 그리고 나서 그 파이프는 부모 혹은 자식 프로세스들, 또는 두 개의 형제 프로세스 사이의 통신을 위해서 사용되어진다. 파이프 함수는 헤더파일 'unistd. h'에 선언되어 있다.

함수 : int pipe (int filedes[2])

pipe 함수는 파이프를 만들고 파이프의 끝에서(각각) 읽기와 쓰기를 위한 파일 기술자인 filedes[0] 과 filedes[1]을 저장한다. 파일기술자 0은 표준 입력이고, 파일 기술자 1은 표준 출력이다.
 
만일 성공하면, pipe는 0을 리턴하고, 실패하면 -1을 리턴한다. 다음의 errno는 이 함수를 위해 정의된 에러 상황이다.

EMFILE : 그 프로세스가 개방된 너무 많은 파일을 갖고 있다.

ENFILE

전제 시스템 안에 개방된 파일이 너무 많다. ENFILE에 대한 상세한 것은 2. 2절 [Error Codes]를 참조하라.
 
이곳의 예는 파이프를 만드는 간단한 예이다. 이 프로그램은 자식 프로세스를 만들기 위해서 fork함수( 23. 4절 [Creating a Process] 참조)를 사용한다. 부모 프로세스는 파이프에 데이터를 쓰고 자식 프로세스에 의해 그 데이터가 읽혀진다.
 
#include <sys/types. h>
#include <unistd. h>
#include <stdio. h>
#include <stdlib. h>
 
/* 파이프로부터 문자들을 읽고 그들은 표준 출력에 출력하라 */
void
read_from_pipe (int file)
{
FILE *stream;
int c;
stream = fdopen (file, "r");
while ((c = fgetc (stream)) != EOF)
putchar (c);
fclose (stream);
}
 
/* 파이프에 어떤 텍스트를 써라 */
void
write_to_pipe (int file)
{
FILE *stream;
stream = fdopen (file, "w");
fprintf (stream, "hello, world!\n");
fprintf (stream, "goodbye, world!\n");
fclose (stream);
}
 
int
main (void)
{
pid_t pid;
int mypipe[2];
/* 파이프를 만들어라 */
if (pipe (mypipe)) {
fprintf (stderr, "Pipe failed. \n");
return EXIT_FAILURE;
}
/* 자식 프로세스를 만들어라 */
if ((pid = fork ()) == 0) { /* 이것은 자식 프로세스이다. */
read_from_pipe (mypipe[0]);
return EXIT_SUCCESS;
} else if (pid < 0) {
/* fork가 실패했다. */
fprintf (stderr, "Fork failed. \n");
return EXIT_FAILURE;
} else { /* 이것은 부모 프로세스이다. */
write_to_pipe (mypipe[1]);
return EXIT_SUCCESS;
}
}


10. 2 부프로세스를 위한 파이프

파이프는 부프로세스로 실행되어지고 있는 한 프로그램으로부터 데이터를 받거나 혹은 데이터를 보내는데 일반적으로 사용된다. 이것들은 pipe( 파이프를 만드는 ), fork(부프로세스를 만들기 위한 ), dup2( 표준 입력 또는 출력 채널로 파이프를 사용하기 위한 부프로세스를 위해 ), 그리고 exec( 새로운 프로그램은 실행하기 위한)을 복합적으로 사용한다. 또는 popen과 pclose를 사용할 수 있다.

popen과 pclose를 사용함으로 인해서 얻는 이득은 사용하기 쉽고 좀더 간단한 인터페이스를 갖는다는 것이다. 그러나 직접적으로 저수준 함수를 사용함으로 인해서 프로그램의 유연성은 얻을 수가 없다.

함수 : FILE * popen (const char *command, const char *mode)

popen 함수는 system함수와 매우 가까운 연 관을 갖고 있다; 23. 1절 [Running a Command] 참조. 그것은 부프로세스로써 command 쉘 명령을 실행한다. 그렇지만 그 명령이 수행되기를 기다리는 대신에, 그것은 부프로세스에 파이프를 만들고 그 파이프에 해당하는 스트림을 리턴한다.
 
만일 mode 인수가 "r"로 정해진다면, 당신은 부프로세스의 표준 출력 채널로부터 데이터를 받아들인 스트림으로부터 데이터를 읽을 수 있다. 부프로세스는 부모 프로세스로부터 표준 입력 채널을 상속받는다. 유사하게, 만일 당신이 mode 인수를 "w"로 정한다면, 당신은 부프로세스의 표준 입력 채널로 데이터를 보내기 위한 스트림에 데이터를 쓸 수 있다.
 
부프로세스는 부모 프로세스로부터 표준 출력 채널을 상속받는다. 에러가 발생하면, popen은 널 포인터를 리턴한다. 이것은 파이프나 스트림이 만들어지지 않았거나, 부프로세스가 생성되지 않았거나, 혹은 프로그램이 실행되지 않았거나 할 때 발생할 것이다.

함수 : int pclose (FILE *stream)

pclose 함수는 popen으로 만들어진 스트림을 닫을 때 사용한다. 그것은 시스템 함수로써, 자식 프로세스가 종료되고 그 상황 값을 리턴하기를 기다린다.

여기에 다른 프로그램을 통한 출력 필터로 popen과 pclose를 어떻게 사용하는지 보여주는 예가 있다.

#include <stdio. h>
#include <stdlib. h>
 
void
write_data (FILE * stream)
{
int i;
for (i = 0; i < 100; i++)
fprintf (stream, "%d\n", i);
if (ferror (stream)) {
fprintf (stderr, "Output to stream failed. \n");
exit (EXIT_FAILURE);
}
}
 
int
main (void)
{
FILE *output;
if ((output = popen("more", "w")) == NULL) {
fprintf (stderr, "Could not run more. \n");
return EXIT_FAILURE;
}
write_data (output);
pclose (output);
return EXIT_SUCCESS;
}


10. 3 FIFO 특별한 파일들

특별한 파일인 FIFO는 다른 방법으로 만들어진다는 점을 제외하고는 파이프와 유사하다. 파이프가 작자 불명의 통신 채널인 점에 비해 FIFO 특별파일은 mkfifo를 호출함으로써 파일 시스템에 삽입된다. 일단 당신이 이 방법으로 FIFO 특별파일을 만들면, 어느 프로세스도 일반적 파일을 다루는 것과 같은 방법으로 읽기나 쓰기를 위해 그것을 개방 할 수 있다. 그렇지만, 그것에 입력이나 출력 명령을 행하기 전에 양끝이 동시에 개방되도록 해야한다. 보통의 블록을 읽기 위해서 FIFO를 개방한 동안 어떤 다른 프로세스는 출력을 위해서 같은 FIFO를 개방한다. mkfifo 함수는 헤더파일 'sys/stat. h'에 선언되어 있다.

함수 : int mkfifo (const char *filename, mode_t mode)

mkfifo 함수는 filename의 이름을 가진 FIFO특별파일을 만든다. mode 인수는 파일의 허가를 설정하기 위해 사용되어진다; 9. 8. 7절 [Setting Permissions] 참조.
 
일반적으로, 성공하면 mkfifo는 0을 리턴한다. 에러가 발생한 경우에는 -1을 리턴한다. 보통의 파일 이름 문법에러에 더하여 ( 6. 2. 3절 [File Name Errors] 참조) 다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EEXIST : 그 이름을 가진 파일이 이미 존재한다.

ENOSPC : 디렉토리나 파일 시스템이 확장되어진 수 없다.

EROFS : 그 파일이 들어갈 디렉토리가 오직 읽기 모드 파일 시스템에 존재한다.


10. 4 파이프 입/출력의 원소수

읽거나 쓰여진 파이프의 데이터에서 만일 그 데이터의 크기가 PIPE_BUF 보다 작다면 원소이다. 이것은 시스템 안의 어떤 것도 부분적으로 수행된 상황을 말해줄 수 없는, 즉각적으로 만들어진 단위의 데이터를 참조함을 의미한다. 원소단위 입/출력은 바르게 시작되지 않았을 것이다. (그것은 데이터나 버퍼 공간을 확보하기 위해서 기다릴 필요가 있었지만, 일단 시작되었고, 그것은 즉시 끝난다. ) 데이터의 많은 양을 읽거나 쓰는 것은 원소단위가 되어지지 않을 것이다. 예를 들어, 다른 프로세스가 점유하고 있는 기술자로부터의 출력 데이터는 흩어지게 될 것이다. 27. 6절 [Limits for Files] 를 참조해서 PIPE_BUF 파라미터에 대한 자세한 정보를 얻어라.


목차 이전 : 9. 파일 시스템 인터페이스 다음 : 11. 소켓

'Programming > Unix Programing' 카테고리의 다른 글

12. Low-Level Terminal Interface  (0) 2007.12.22
11. Sockets  (0) 2007.12.22
10. Pipes and FIFO  (0) 2007.12.22
9. 파일시스템 인터페이스  (0) 2007.12.22
8. 저수준 입출력  (0) 2007.12.22
7. 스트림의 입출력  (0) 2007.12.22
Comment 0 Trackback 0
Top

9. 파일시스템 인터페이스

목차 이전 : 8. 저수준 입출력 다음 : 10. 파이프와 FIFO


9 파일 시스템 인터페이스

이 장은 파일들을 다루기 위한 GNU C 라이브러리 함수들을 설명하고 있다. 7장 [I/O on Streams] 에서 설명된 입/출력 함수들과는 달리, 이 함수들은 파일안의 내용보다는 파일 그 차제를 다루는 것에 대한 함수들이다.

이 절에서 설명된 것들은 디렉토리들을 조사하고, 갱신하기 위한 함수들과 파일들을 삭제하거나 재 명명하기 위한 함수들과, 엑세스 허용과, 갱신시간과 같은 파일 속성들을 조사하고, 설정하기 위한 함수들이다.


9. 1 작업 디렉토리

각 프로세스는 한 디렉토리와 연관되어 있고, 그것을 현재 작업 디렉토리라 부르거나, 간단하게 작업 디렉토리라 부른다. 디렉토리 이름은 파일이름과 같은 방법으로 사용된다. ( 6. 2. 2절 [File Name Resolution] 참조 )

당신이 로그인하고 새로운 세션(session)을 시작할 때, 당신의 작업 디렉토리는 시스템 유저 데이터베이스 안에 당신의 로그인 계정과 연관된 홈 디렉토리(home directory)로 초기 설정된다. 당신은 getpwuid 나 getpwnam 을 사용해서 사용자의 홈 디렉토리를 찾을 수 있다; 25. 12절[User Database] 참조.

 
--- 역자주 : 세션(session) : 사용자가 컴퓨터와의 연결이 시작된 순간부터 단절되는 순간까지를 하나의 논리적인 단위로 구분해놓은 것

사용자는 cd와 같은 쉘 명령어를 사용해서 작업 디렉토리를 변경할 수 있다. 이 절에서 설명하고 있는 함수들은 이들 명령에 의해 사용되고, 또한 작업 디렉토리를 조사하고, 변경하려는 다른 프로그램들에 의해 사용된다. 이들 함수들의 프로토타입은 헤더파일 'unistd. h'에 선언되어 있다.

함수 : char * getcwd (char *buffer, size_t size)

getcwd 함수는 현재의 작업 디렉토리를 나타내는 파일 이름을, 당신이 제공한 문자 배열 버퍼에 그것을 저장하여 리턴한다. size인수는 버퍼의 할당 크기이다.
이 함수에서 GNU 라이브러리는 당신이 buffer 인수에 널 포인터를 지정하는 것을 허용한다. 그러면 getcwd는 malloc( 3. 3절 [Unconstrained Allocation] )을 사용해서 자동적으로 버퍼를 할당한다. 만일 그 size가 0보다 크다면 버퍼는 그 크기로 할당되고 그렇지 않다면 버퍼는 결과를 저장하기에 필요한 만큼의 크기로 할당된다. 리턴 값은 성공하면 버퍼이고, 실패하면 널 포인터이다.
 
다음의 errno는 이 함수에서 정의하고 있는 에러 상황이다.

    EINVAL : size인수가 0인데 buffer가 널 포인터가 아니다.

    ERANGE

    size인수가 작업 디렉토리 이름의 길이보다 작다. 당신은 배열을 더 크게 할당하고 다시 시도하라.

    EACCES : 읽기 요청이나, 파일이름의 구성요소 찾기가 거부되었다.

이곳에 getcwd의 표준동작만을 사용한 GNU의 getcwd (NULL, 0)의 동작을 보여주는 예가 있다.

char *gnu_getcwd ()
{
int size = 100;
char *buffer = (char *) xmalloc (size);
while (1) {
char *value = getcwd (buffer, size);
if (value != 0)
return buffer;
size *= 2;
free (buffer);
buffer = (char *) xmalloc (size);
}
}
 
/* 다 아시겠지만. . . 잠시 설명을 합죠~ xmalloc을 이용해서 100바이트 만큼의 버퍼를 할당한 다음. . .
while 루프를 돌리네요. while루프의 조건을 1로 해놓고 루프안의 조건에 따라서 루프를 벗어나게 되는데. . . getcwd함수를 이용해서 이름을 얻었는데, 그 크기가 0이 아니면 버퍼를 리턴하고 루프를 벗어나고 그렇지 않으면 size에 2를 곱해서 (즉. . 버퍼의 크기를 늘리기 위해 ) 원래의 버퍼는 해제하고 다시 큰 크기의 버퍼를 할당받아서 다시 루프의 처음으로 돌아가고. . 끝. . */
 
xmalloc에 대한 정보는 3. 3. 3절 [Malloc Examples]를 참조하라. xmalloc은 라이브러리 함수가 아니라 대부분의 GNU 소프트웨어에서 사용되는 사용자정의 이름이다.

함수 : char * getwd (char *buffer)

이것은 getcwd와 유사하다. GNU 라이브러리는 BSD와의 호환성을 위해 getwd를 제공하고 있다. 버퍼는 적어도 PATH_MAX 바이트 길이의 배열을 가리키는 포인터가 된다.

함수 : int chdir (const char *filename)

이 함수는 filename으로 프로세스의 작업 디렉토리를 설정하기 위해 사용된다. 보통 성공하면 리턴 값은 0이다. 만일 -1이 리턴되면 에러가 발생했음을 나타낸다. 이 함수에서 정의하고 있는 에러상황 errno는 대부분 파일 이름의 문법적 에러이고( 6. 2. 3절 [File Name Errors] 참조) ENOTDIR은 파일 이름이 디렉토리가 아님을 나타낸다.


9. 2 디렉토리 억세스하기

이 절에서 설명하고 있는 것들은 한 디렉토리 파일의 내용을 읽는데 쓰여 진다. 이것은 당신의 프로그램이 아마도 메뉴의 일부분으로써, 디렉토리 안의 모든 파일들의 리스트를 원할 때 유용하다. opendir 함수는 디렉토리 엔트리들(entries)을 그 요소로 갖고 있는 디렉토리 스트림을 개방한다. readdir는 struct dirent objects로서 표현된 그 엔트리들을 디렉토리 스트림에서 얻기 위하여 사용한다. 각 엔트리를 위한 파일의 이름은 이 구조체의 d_name 안에 저장되어있다. 7장 [I/O on Streams] 에 설명된, 원래의 파일들을 위한 스트림 도구들과 이곳에 있는 것들과는 유사하다.

 

9. 2. 1 디렉토리 엔트리의 형식

이 절은 당신이 디렉토리 스트림으로부터 획득한, 단일 디렉토리 엔트리에서 무엇을 발견할 수 있는지를 설명하고 있다. 모든 심볼들은 헤더파일'dirent. h'에 선언되어 있다.

 데이터타입 : struct dirent

이것은 디렉토리 엔트리들에 대한 리턴 정보로서 사용되는 구조체 타입이다. 그것은 다음과 같은 필드들은 포함하고 있다.

char *d_name

이것은 널 문자로 끝나는 파일이름 요소이다. 이것은 모든 POSIX 시스템 상에서 당신이 셀 수 있는 유일한 필드이다.

ino_t d_fileno

이것은 파일 시리 얼 넘버이다. BSD와의 호환성을 위해서, 당신은 d-ino로서 이 멤버를 대치할 수 있다.

size_t d_namlen

이것은 널 종료 문자가 포함되지 않은, 파일 이름의 길이이다. 이 구조체는 앞으로 부가적인 멤버들을 포함할 것이다. 파일이 다중의 이름들을 가질 때, 각각의 이름들은 자신의 디렉토리 엔트리를 갖는다. 단일 파일에 속한 디렉토리 엔트리들을 가리키는 유일한 방법은 d_fileno 필드에 동일한 값을 줌으로써 할 수 있다. 크기, 갱신 시간 과 같은 파일 속성과, 파일 그 자체의 일부분과 같은 것은 어느 특별한 디렉토리 엔트리가 아니다. 9. 8절 [File Attributes] 참조.

 

9. 2. 2 디렉토리 스트림 개방하기

이 절은 디렉토리 스트림을 어떻게 개방하는지 설명하고 있다. 모든 심볼들은 헤더파일 'dirent. h'에 선언되어 있다.

데이터타입 : DIR

DIR 데이터타입은 디렉토리 스트림을 나타낸다. 당신은 struct dirent 나 DIR 데이터타입들로 objects들을 할당받을 수 없다. 대신에 당신은 다음에 있는 함수들을 사용함으로 인해서 리턴된 포인터를 사용해서 이들 objects를 참조할 수 있다.

함수 : DIR * opendir (const char *dirname)

opendir 함수는 파일 이름이 dirname인 디렉토리를 읽기 위해서 디렉토리 스트림을 개방하고 리턴한다. 그 스트림은 DIR * 타입을 갖는다. 만일 성공하지 못하면 opendir는 널 포인터를 리턴한다. 보통 쉽게 일어나는 파일 이름 문법 에러들에 더하여( 6. 2. 3 [File Nmae Errors] 참조),
 
다음의 errno는 이 함수에서 정의하고 있는 에러 상황이다.

    EACCES : dirname의 이름을 가진 디렉토리를 읽기 위한 요청이 거부되었다.

    EMFILE : 그 프로세스가 너무 많은 파일들을 개방했다.

    ENFILE

    디렉토리를 포함하고 있는 전체 시스템이나, 아마도 파일 시스템이 그 순간에 더 이상 파일을 개방하는 것은 지원하지 않는다. ( 이 문제는 GNU시스템에서는 발생할 수 없다.

DIR 타입은 파일 기술자와, 그리고 open 함수에서 사용되는 opendir 함수에서 사용되어진다. 8장 [Low-Level I/O] 참조. 디렉토리 스트림들과 포함된 파일 기술자들은 exec에서 폐쇄된다. 23.5절 [Executing a File] 참조 )

 

9. 2. 3 디렉토리 스트림 읽고 닫기

이 절은 디렉토리 스트림으로부터 디렉토리 엔트리들을 어떻게 읽고, 그렇게 한 후 그 스트림을 어떻게 닫을 것인지를 설명하고 있다. 모든 심볼들은 헤더파일 'dirent. h'에 선언되어 있다.

함수 : struct dirent * readdir (DIR *dirstream)

dl 함수는 디렉토리로부터 다음 엔트리를 읽는다. 보통 리턴 값은 파일에 대한 정보를 갖고 있는 구조체의 포인터이다. 이 구조체는 정적으로 할당되고 그후에 일어나는 호출에 의해서 갱신되어질 수 있다.
 
이식성 노트 : 어떤 시스템에서는, readdir는 `. '과 `. . '가 어느 디렉토리에서 유용한 파일이름 일지라도, 그것들을 위한 엔트리들을 리턴하지 않을 것이다. 6. 2. 2 [File Name Resolution] 참조
 
만일 그 디렉토리 안에 더 이상 엔트리가 없거나, 에러가 검출되면, readdir은 널 포인터를 리턴한다.
다음의 errno는 이 함수에서 정의한 에러 상황이다.
EBADF : dirstream인수가 유용하지 않다.

함수 : int closedir (DIR *dirstream)

이 함수는 디렉토리 스트림 dirstream을 폐쇄한다. 성공하면 0을 리턴하고 실패하면 -1을 리턴한다.
 
다음의 errno는 이 함수에서 정의하고 있는 에러상황이다.
    EBADF : dirstream인수가 유용하지 않다.

 

9. 2. 4 디렉토리의 파일을 리스트 하는 간단한 프로그램

이곳에 현재 작업 디렉토리의 파일들의 이름을 출력하는 간단한 프로그램이 있다.

#include <stddef. h>
#include <stdio. h>
#include <sys/types. h>
#include <dirent. h>
 
int main (void)
{
DIR *dp;
struct dirent *ep; /* 현재의 디렉토리를 개방해서 dp에 넣어라!!! */
dp = opendir (". /");
/* 만일 개방이 됐다면 dp의 엔트리를 읽어서 ep에 넣고 그 구조체에서 d_name멤버를 출력하라. */
if (dp != NULL) {
while (ep = readdir (dp))
puts (ep->d_name);
(void) closedir (dp);
}
/* 개방이 안됐다면 에러 메시지 출력. . . */
else
puts ("Couldn't open the directory. ");
 
return 0;
}

디렉토리 안에 나타나는 파일들의 순서는 불규칙적이다. 이것보다 더 유용한 프로그램은 그들을 출력하기 전에 엔트리들을 정렬(아마도 알파벳 순서로. . )하는 것이다; 15. 3절 [Array Sort Function] 참조.

 

9. 2. 5 디렉토리 스트림의 랜덤 억세스

이 절은 이미 개방된 디렉토리 스트림으로부터 읽었던 한 부분을 어떻게 다시 읽을 수 있는지를 설명하고 있다. 모든 심볼들은 헤더파일 'dirent. h'에 선언되어 있다.

함수 : void rewinddir (DIR *dirstream)

rewinddir함수는 디렉토리 스트림 dirstream을 재 설정하는데 사용되어지기 때문에 만일 당신이 readdir을 호출하면 그것은 다시 디렉토리의 첫 번째 엔트리에 대한 정보를 리턴한다. 이 함수는 또한 opendir로 개방된 후 디렉토리 안의 파일들이 더해지거나, 제거된 적이 있는지에 대한 것도 지적한다. ( 이들 파일들을 위한 엔트리들은 당신이 마지막으로 호출했던 opendir이나 rewinddir 이후에 더해지거나 제거된 것이라면 readdir을 통해 리턴될 수도, 리턴되지 않을 수도 있다.

함수 : off_t telldir (DIR *dirstream)

telldir 함수는 디렉토리 스트림 dirstream의 파일 위치를 리턴한다. 당신은 그 위치에 디렉토리 스트림을 저장하기 위한 함수 seekdir에 이 값을 사용할 수 있다.

함수 : seekdir (DIR *dirstream, off_t pos)

seekdir함수는 pos로 디렉토리 스트림 dirstream의 파일 위치를 설정한다. 그 값 pos는 이 특별한 스트림 상에서 미리 telldir를 호출한 것의 결과이어야 한다; 디렉토리를 폐쇄하고, 다시 열기는 telldir에 의해 리턴된 값들을 무효로 만들 수 있다.


9. 3 하드 링크

POSIX 시스템에서는, 한 개의 파일이 동시에 많은 다른 이름을 소유할 수 있다. 그 이름들 모두는 동등하게 실제로 존재하고, 그들중 어느 것도 다른 것들을 취할 수 있다.

파일에 이름을 더하기 위해서, link 함수를 사용하라. ( 새로운 이름은 파일에 하드링크라고 불려진다. ) 파일에 만들어진 새로운 링크는 파일의 내용들은 복사하지 않는다; 그것은 그냥 간단히 기존에 있는 파일에 새로운 이름을 하나 더할 뿐이다.

하나의 파일은 여러개의 디렉토리들에 이름을 가질 수 있어서, 파일 시스템의 구조는 정적 계층 도나 트리 모양이 아니다.

단일한 파일 시스템 안에 존재하는 특별한 파일에서, 그 파일 이름들은 모두 단일한 파일 시스템의 디렉토리 안에 존재해야만 한다. link는 다른 파일 시스템으로부터 그 파일에 하드링크를 만들려고 시도하면 에러를 출력한다.

 
역자주 : 그러니까. . 어떤 파일에서 링크된 것들도 모두 원래의 파일과 같은 파일 시스템 안에 존재해야지, 다른 곳에 있으면 안된다는 말씀. 즉, 파일 시스템들간에 파일을 링크하는 것은 불가능하다.

link함수의 프로토타입은 헤더파일 'uinstd. h'에 선언되어 있다.

함수 : int link (const char *oldname, const char *newname)

link함수는 oldname의 이름을 가진 현존하고 있는 파일에 newname이라는 이름을 갖는 새로운 링크를 만든다. 이 함수는 성공하면 0을 리턴하고 실패하면 -1을 리턴한다. 보통의 파일 이름 문법 에러에 더하여서( 6. 2. 3절[File Name Errors] 참조),
 
다음 errno는 이 함수에서 정의하고 있는 에러상황이다.

EACCES : 새로운 링크가 쓰여져야 할 디렉토리가 기록 불가능이다.

EEXIST

이미 newname의 이름을 가진 파일이 있다. 만일 새로운 링크로 기존의 링크를 대치하게 원한다면, 당신은 먼저 기존의 링크를 제거해야 한다.

EMLINK

그것엔 이미 oldname에 의해 이름된 너무 많은 링크가 있다. ( 링크의 최대개수는 LINK_MAX이다. 27. 6절 [Limits for Files] 참조) 잘 만들어진 파일시스템들은 결코 이 에러를 발생시키지 않는다. 왜냐하면 그들은 당신의 디스크가 가질 수 있는 만큼의 많은 링크를 허용하기 때문이다. 그렇지만 당신은 다른 기계상의 파일 시스템을 네트웍으로 억세스 할 수도 있으므로, 당신의 시스템이 이런 에러를 발생시키지 않는다고 하더라도, 당신은 여전히 링크의 개수에 신경을 써야한다.

ENOENT

oldname이란 이름을 가진 파일이 존재하지 않는다. 당신은 그 파일이 없으면, 파일에 링크를 만들 수 없다.

ENOSPC

새로운 링크를 포함해야할 디렉토리나 파일 시스템이 "가득 찼고" 더 이상 확장되지 않는다.

EPERM

어떤 곳에서는 특권이 부여된 사용자에게만 디렉토리에 링크를 만드는 것을 허용하고, 다른 사용자들에게는 완전히 이 명령을 금지한다. 이 에러는 그 문제를 알리기 위해 사용된다.

EROFS

디렉토리가 읽기 모드 파일 시스템이기 때문에 새로운 링크를 갖고 있는 디렉토리로 갱신되어 질 수 없다.

EXDEV

새로운 이름으로 지정된 디렉토리가 링크하려는 파일과는 다른 파일 시스템 상에 존재한다.


9. 4 심볼릭 링크

GNU 시스템에서는 소프트 링크 혹은 심볼릭 링크를 지원한다. 이것은 일종의 "파일"로서 본질적으로는 다른 파일이름을 가리키는 포인터이다. 하드링크와는 달리, 심볼릭 링크는 디렉토리나 서로 다른 파일 시스템에 아무런 제한없이 만들 수 있다. 당신은 또한 아직 어느 파일의 이름으로 존재하지 않는 이름에 심볼릭 링크를 만들 수도 있다. ( 이런 링크의 개방은 그 이름의 파일이 만들어질 때까지 실패할 것이다. ). 마찬가지로, 만일 심볼릭 링크가 현존하는 파일에서 만들어지고 나중에 그 파일이 지워졌다면, 심볼릭 링크는 더이상 그 파일이 존재하지 않음에도 불구하고 동일한 파일이름을 가리키며 남아있다.

심볼릭 링크라고 불려지는 이유는 당신이 링크를 개방하려 시도할 때 그들이 행하는 특별한 작업방법 때문이다. open함수는 당신이 링크의 지정된 이름을 인수로 준다면, 그 링크에 포함된 파일이름을 읽어서, 대신에 그 파일 이름을 개방한다. stat함수는 마찬가지로, 링크의 그 자체 대신에 심볼릭 링크에 연결된 그 파일상에서 동작한다. link함수는 하드 링크를 만든다.

하지만, 파일을 지우거나, 재 명명하거나 하는 명령들을 링크 그 자신에서 행해진다. readlink와 lstat 함수는 또한 심볼릭 링크에서 동작하는데, 왜냐하면 그들의 목적이 링크에 대한 정보를 얻는 것이기 때문이다. 이들 함수를 위한 프로토타입은 'uinstd. h'이다.

함수 : int symlink (const char *oldname, const char *newname)

symlink함수는 oldname에 newname으로 이름된 심볼릭 링크를 만든다. symlink로부터 리턴 값은 보통 0이다. 만일 에러가 발생하면 -1을 리턴한다. 보통 파일 이름 문법 에러에 더하여서( 6. 2. 3절 [File Name Errors] 참조)
 
다음 errno는 이 함수에서 정의된 에러 상황이다.

EEXIST : 이미 newname이름을 가진 파일이 존재한다.

EROFS : newname 파일이 읽기-모드 파일 시스템 상에 존재할 것이다.

ENOSPC : 디렉토리나 파일 시스템이 새로운 링크를 만들도록 확장되어질 수 없다.

EIO : 디스크에 데이터를 쓰거나, 읽는 동안 하드웨어 에러가 발생했다.

함수 : int readlink (const char *filename, char *buffer, size_t size)

readlink함수는 심볼릭 링크 filename의 값을 얻는다. 링크가 가리키는 파일 이름이 버퍼에 카피된다. 이 파일 이름 문자열은 널 종료문자로 끝나지 않는다; readlink는 보통 카피된 문자들의 개수를 리턴한다. size인수는 카피할 문자들의 최대 수를 지정하고, 보통 버퍼의 할당 크기가 된다. 만일 리턴 값이 size와 같다면, 당신은 그 버퍼에 완전한 이름이 리턴된 것인지, 아닌지를 알 수가 없다. 그러므로 다시 버퍼를 크게 하고, readlink를 다시 호출하라.
 
이곳에 그 예가 있다.
char *readlink_malloc (char *filename)
{
int size = 100;
 
while (1)
{
char *buffer = (char *) xmalloc (size);
int nchars = readlink (filename, buffer, size);
if (nchars < size)
return buffer;
free (buffer);
size *= 2;
}
}
 
-1의 값이 리턴되었다면 에러가 발생한 것이다. 보통 파일이름 문법에러에 대하여( 6. 2. 3절 [File Name Errors] 참조)
 
다음의 errno는 이 함수에서 정의한 에러상황이다.

EINVAL : 주어진 filename이 심볼릭 링크가 아니다.

EIO : 디스크 상에서 읽거나, 쓰고 있는 동안 하드웨어 에러가 발생했다.


9. 5 파일 지우기

당신은 unlink 나 remove 의 함수로 파일을 지울 수 있다. ( 이들 이름들은 동의어이다. )

삭제(Deletion)는 실제로 파일이름을 지운다. 만일 그 이름이 파일의 유일한 이름이라면, 그 파일은 삭제된다. 하지만 만일 그 파일이 다른 이름으로 존재하고 있다면( 9. 3절 [Hard Links] 참조), 그것은 다른 이름 아래서 억세스 가능한 상태로 남는다.

함수 : int unlink (const char *filename)

unlink함수는 파일이름 filename을 지운다. 만일 이 파일이름이 그 파일의 유일한 이름이라면, 그 파일 자체가 삭제된다. (실제로는, 만일 어느 프로세스가 이처럼 unlink될 때 그 파일을 개방된 파일로 갖고 있다면, 모든 프로세스들이 그 파일을 닫을 때까지 삭제는 연기된다. )unlink함수는 헤더파일 'unistd. h'에 선언되어 있다. 이 함수는 성공하면 0을 리턴하고 에러이면 -1을 리턴한다. 보통의 파일이름 문법에러에 더하여서( 6.2.3절 [File Name errors] 참조)
 
다음의 errno는 이 함수를 위하여 정의된 에러 상황이다.

EACCESS : 파일을 제거하기 위한 쓰기 요청이 디렉토리에서 거부되었다.

EBUSY

이 에러는 그 파일이 제거되어질 수 없는, 시스템에서 사용하고 있는 파일임을 나타낸다. 당신이 어디서건 이 에러를 보았다면 그것은 당신이 지정한 파일이름이 루트 디렉토리이거나 파일 시스템에서 마운트하고 있다는 것이다.

ENOENT : 제거하려는 파일이름이 존재하지 않는다.

EPERM

어떤 시스템에서는, unlink는 디렉토리 제거에 사용할 수 없거나, 오직 특권이 부여된 사용자만이 이 방법을 사용할 수 있거나 한다. 이와 같은 문제들을 피하기 위해서, 디렉토리를 지우려면 rmdir를 사용하라.

EROFS

지우려는 파일 이름의 디렉토리가 읽기 모드 파일 시스템 상에 존재하고 있기 때문에 갱신되어 질 수 없다.

함수 : int remove (const char *filename)

remove 함수는 unlink의 다른 이름으로 ANSI C 이다. unlink는 POSIX. 1 이름이다. remove는 'stdio. h'에 선언되어 있다.

함수 : int rmdir (const char *filename)

rmdir는 디렉토리를 지운다. 디렉토리를 제거하기 전에는 디렉토리는 반드시 비어있어야 한다. 즉, 그 디렉토리는 `. ' 와 `. . ' 의 엔트리만 갖고 있어야 한다. rmdir 은 unlink처럼 동작한다.
 
다음의 두 개의 부가적 errno는 rmdir을 위해서 정의된 에러상황이다.

EEXIST, ENOTEMPTY

지우려는 디렉토리가 비어있지 않다. 이들 두 개의 에러는 동일하다. 어떤 시스템에서는 어떤 하나를 사용하고, 다른 시스템에서는 다른 하나를 사용한다. 이 함수를 위한 프로토타입은 헤더파일 'uinstd. h'에 선언되어 있다.


9. 6 파일 재 명명하다

rename함수는 파일의 이름을 변경하는데 사용한다.

함수 : int rename (const char *oldname, const char *newname)

rename함수는 파일 이름 oldname을 newname으로 재 명명한다. oldname 이름 하에서의 파일 검색 형식은 나중에 newname으로 재명명 되더라도, 역시 변함이 없다. (만일 그 파일이 oldname외에 다른 이름들은 가졌다면, 그것들의 이름에도 변함이 없다. ) newname 이름의 파일을 갖고 있는 디렉토리는 그 파일( oldname 이름의 파일 )과 같은 파일 시스템 안에 있어야만 한다. 재명명의 특별한 경우는 동일한 파일에 oldname과 newname 두 개의 이름이 존재할 때이다. 이 경우에 대응하기 위한 일관된 방법은 oldname을 제거하는 것이다. 그렇지만, POSIX는 이 경우에 rename은 아무 일도 하지 않고, 일치하는 파일이 없었을 때, 성공하게 된다. 우리는 당신의 운영체제가 무엇을 할 것인지 알지 못한다.
 
GNU시스템은 당신이 POSIX 시스템과 호환성을 가지도록, 명백하게 요청하지 않더라도, 옳은 일(oldname의 제거)을 할 것이다. 만일 oldname이 디렉토리가 아니라면, 이미 존재하고 있던 newname의 이름을 가진 파일은 재명명의 동작이 수행될 때 지워진다. 그렇지만, 만일 newname이 디렉토리라면, rename은 이 경우 실패로 끝난다. 만일 oldname이 디렉토리일 때, newname의 이름을 가진 어떤 것도 존재해서는 안되고, 또는 그것이 비어있는 디렉토리 이름이거나 해야한다.
 
후자의 경우에, newname의 이름을 가진 디렉토리를 우선 지우고 재명명을 하는 것이다. newname은 재명명을 시도할 디렉토리로 oldname의 서브디렉토리를 지정하지 않아야만 한다. rename은 기존에 oldname이란 이름을 갖고 있는 파일을 newname이라는 새로운 이름으로 자동적으로 변경할 때 쓰여 진다. old를 new로 변경할 동안 "그 사이에" newname의 이름을 가진 것이 존재하지 않는다면 더 이상 신경쓸것이 아무 것도 없다. 만일 rename이 실패하면, -1을 리턴한다.
 

파일 이름 문법 에러에 ( 6.2.3절 [File Name Errors] 참조) 더하여, 다음 errno는 이 함수에서 정의된 에러 상황이다.

    EACCES

    newname이나 oldname을 갖고 있는 디렉토리들 중의 하나가 쓰기 요청을 거부했다. 또는 newname과 oldname은 디렉토리인데 쓰기 요청이 그들 중에 하나에서 거부되어졌다.

    EBUSY

    oldname이나 newname이름을 가진 디렉토리가 시스템이 작업하는 동안 재명명을 거부하는 방법으로 현재 그 디렉토리를 시스템에서 사용하고 있다. 디렉토리가 파일 시스템으로 마운트(mount)된 것이고, 디렉토리가 프로세스들의 현재 작업 디렉토리이다.

    EEXIST : newname 디렉토리가 비어있지 않다.

    ENOTEMPTY : newname 디렉토리가 비어있지 않다.

    EINVAL : oldname 디렉토리가 newname을 포함하는 디렉토리이다.

     
    역자주 : newname이 oldname의 서브디렉토리.

    EISDIR : newname은 디렉토리 이름인데, oldname은 디렉토리 이름이 아니다.

    EMLINK

    newname의 부모 디렉토리가 너무 많은 링크들을 가지고 있다. 잘 만들어진 파일 시스템들은 결코 이 에러를 발생시키지 않는다, 왜냐하면 그들은 당신의 디스크가 허용하는 한 많은 링크를 허용하기 때문이다. 하지만 당신은 다른 기계상의 파일 시스템을 네트웍으로 억세스할 수 있으므로, 이 에러에 대한 가능성에 대해서 항상 대비해야만 한다.

    ENOENT : oldname 이란 이름을 가진 파일이 존재하지 않는다.

    ENOSPC

    newname을 포함할 디렉토리가 더 이상의 엔트리를 받아들일 수 없고, 그곳에는 파일 시스템 안에 확장할 공감이 남아있지 않다.

    EROFS : 오직 읽기 모드인 파일 시스템상의 디렉토리에 쓰기를 시도하고 있다.

    EXDEV

    oldname과 newname이라는 두 개의 파일 이름이 서로 다른 파일 시스템에 존재하고 있다.


9. 7 디렉토리 만들기

디렉토리는 mkdir 함수로 만들어진다. ( 같은 일을 하는 mkdir 이라는 쉘 명령어도 있다. )

함수 : int mkdir (const char *filename, mode_t mode)

mkdir 함수는 filename의 이름을 가진 새로운, 빈 디렉토리를 만든다. mode 인수는 새로운 디렉토리에 대한 접근 권한을 정한다. 9.8.5절 [Permission Bits]를 참조로 해서 접근 권한에 대한 자세한 정보를 얻어라.

수행이 성공적으로 끝나면 0의 값을 리턴하고, 그렇지 않을 경우에는 -1을 리턴한다. 보통의 파일 이름 문법 에러들에 더하여( 6.2.3 [File Name Errors] 참조 )

다음의 errno는 이 함수를 위해 정의된 에러 상황들이다.

    EACCES : 새로운 디렉토리를 더하게될 부모 디렉토리에서 파일 쓰기 허용이 거부되었다.

    EEXIST : filename의 이름을 가진 파일이 이미 존재한다.

    EMLINK

    부모 디렉토리가 너무 많은 링크들은 가지고 있다. 잘 만들어진 파일 시스템에서는 이러한 에러를 결코 발생시키지 않는다, 왜냐하면, 그들은 당신의 디스크가 저장할 수 있는 만큼의 링크를 허용하기 때문이다. 하지만 당신은 다른 기계 상에 존재하는 파일 시스템을 네트웍을 통해서 억세스할 수 있으므로 이러한 에러가 발생될 가능성에 대해서 항상 대비해야 한다.

    ENOSPC : 파일 시스템이 새로운 디렉토리를 만들만한 충분한 공간을 가지고 있지 않다.

    EROFS

    디렉토리의 부모 디렉토리가 오직 읽기 모드 파일 시스템 상에 존재하고 있고, 그래서 갱신할 수 없다.

이 함수를 사용하려면, 당신의 프로그램에 헤더파일 'sys/stat. h'를 포함해야 한다.


9. 8 파일 속성들

한 파일에 쉘 명령어 'ls -l'을 사용하면, 당신은 파일의 크기, 파일의 소유자, 마지막 갱신된 때, 등등에 대한 정보를 얻을 수 있다. 이러한 정보들의 종류를 파일 속성이라고 부른다; 이것은 파일 그 자체와 연관되어 있고, 그 이름들의 하나가 특별한 것은 아니다.

이 절에서는 당신이 어떻게 파일들의 속성들에 대한 정보를 얻고, 그들을 갱신할 수 있는지를 설명하고 있다.

 

9. 8. 1 파일 속성들의 값이 무엇을 의미하는가

당신이 파일 속성을 읽을 때, 그들은 struct stat라고 불리는 구조체로 보내져온다. 이 절은 그 속성들의 이름과, 그들의 데이터 타입들과, 그들이 무엇을 의미하고 있는지에 대해 설명하고 이다. 파일의 속성을 읽기 위한 함수는, 9. 8. 2절 [Reading Attributes]를 참조하라.

이 절에서 정의된 모든 심볼들은 헤더파일 'sys/stat. h'에 있다.

데이터타입 : struct stat

stat 구조체형은 파일의 속성들에 대한 정보를 리턴하기 위해 사용되어진다. 그들은 적어도 다음과 같은 멤버들을 포함하고 있다.

mode_t st_mode

파일의 모드를 정한다. 이것은 파일 타입 정보와( 9. 8. 3 [Testing File Type] 참조) 파일 접근 권한 비트를(file permission) 포함하고 있다. ( 9. 8. 5절 [Permission Bits] 참조)

ino_t st_ino

파일 일련 번호( file serial number), 로써 같은 디바이스 상에서 모든 다른 파일들과 이 파일을 구분한다.

dev_t st_dev

파일을 포함하고 있는 디바이스를 기술한다. st_ino와 st_dev, 이 둘을 가지고, 파일을 유일하게 식별한다.

nlink_t st_nlink

파일에 관련된 하드 링크의 개수이다. 이 파일을 엔트리로 가지고 있는 얼마나 많은 디렉토리들이 있는지 추적해서 그 값을 유지한다. 만일 개수가 0 으로 감소하면, 그 파일 자체가 없어진 것이다. 심볼릭 링크들은 총 개수에 포함되지 않는다.

uid_t st_uid

파일 소유자의 유저 ID 이다. 9.8.4 절 [File Owner] 참조.

gid_t st_gid

파일의 그룹 ID이다. 9.8.4절 [File Owner] 참조.

off_t st_size

이것은 바이트의 수로 일반적 파일의 크기를 지정한다. 만일 파일들이 디바이스거나, 그와 같은 파일로 존재한다면, 이 필드는 보통 아무런 의미가 없다.

time_t st_atime

이것은 파일의 최종 억세스 시간이다. 9. 8. 9절 [File Times] 참조.

unsigned long int st_atime_usec

이것은 파일의 최종 억세스 시간의 부수적인 부분이다. 9. 8. 9절 [File Times] 참조.

time_t st_mtime

이것은 파일의 내용이 마지막으로 갱신된 시간을 말한다. 9. 8. 9절[File Times] 참조.

unsigned long int st_mtime_usec

이것은 파일이 마지막으로 갱신된 시간의 부수적인 부분이다. 9. 8. 9절 [File Times] 참조.

time_t st_ctime

이것은 파일의 속성 자체가 마지막으로 갱신된 시간을 말한다. 9. 8. 9절 [File Times] 참조.

unsigned long int st_ctime_usec

이것은 파일 속성이 마지막으로 갱신된 시간의 부수적인 부분이다. 9. 8. 9절 [File Times] 참조.

unsigned int st_nblocks

이것은 512-바이트 블록의 단위로 계산된, 파일의 점유하고 있는 디스크 공간의 양을 말한다. 디스크 블록들의 개수는 파일이 크기와 완전히 비례하지 않는다. 두 가지 이유로. . . ; 파일 시스템이 내부적 레코드를 가지고 있기 위해서 어떤 블록들을 사용할 것이고; 그리고 파일들은 0(zeros)과 같은 "구멍들"을 가지고 있을 것이기 때문이다. 한 파일에서 st_size와 이 값을 비교하면 (대략) 한 파일의 내용이 드문드문한지 아닌지 알 수 있다. 이처럼:
(st. st_blocks * 512 < st. st_size)
이 결과는 모든 여기저기 흩어져 있는 것들이 검출이 되지 않을 수 도 있기 때문에, 완전한 결과는 아니다. 실용적인 어플리케이션에서, 이것은 문제가 아니다.

unsigned int st_blksize

쓰여진 파일을 읽기 위한 최적의 블록 크기를 말한다. 당신은 쓰여진 파일을 읽기 위한 버퍼 공간을 할당하기 위해서 이 크기를 사용할 것이다. 어떤 파일 속성들은 그들을 위해 특별히 존재하는 특별한 데이터 타입의 이름들을 가지고 있다. ( 그들은 당신이 익히 알고 있는 것들을 정수형으로 이름을 붙여놓은 것이다. ) 이들 typedef 시킨 이름들은 'sys/types. h'와 'sys/stat. h'에 정의되어 있다. 여기에 그들에 대한 설명이 있다.

데이터타입 : mode__t

이것은 현재의 파일 모드들을 나타내기 위해 사용되는 정수형 데이터 타입니다. GNU 시스템에서, 이것은 unsigned int 와 동일하다.

데이터타입 : ino__t

이것은 파일의 일련 번호들을 나타내기 위해 사용하는 산술적 데이터 타입이다. (jargon 유닉스에서는, 이들을 때때로 inode 번호라고 불려 진다. ) GNU 시스템에서는, 이 타입은 unsigned long int와 동일하다.

데이터타입 : dev__t

이것은 파일 디바이스 번호를 나타내기 위해 사용하는 산술적 데이터 타입니다. GNU 시스템에서는, 이것은 int 와 동일하다.

데이터타입 : nlink__t

이것은 파일 링크의 개수를 나타내기 위해 사용되는 산술적 데이터 타입입니다. GNU 시스템에서, 이것은 unsigned short int 와 동일하다.

 

9. 8. 2 파일 속성들 읽기

파일의 속성들을 조사하기 위해서는, stat, fstat 와 lstat 함수를 사용하라. 그들은 struct stat object에 속성정보를 리턴한다. 세 개 함수 모두는 헤더파일 'sys/stat. h'에 선언되어 있다.

함수 : int stat (const char *filename, struct stat *buf)

stat 함수는 buf가 가리키는 구조체안에 filename의 이름을 가진 파일의 속성에 대한 정보를 리턴한다. 만일 filename이 심볼릭 링크의 이름이라면, 당신이 얻게될 속성 정보는 링크가 가리키고 있는 파일에 대한 정보이다. 만일 링크가 가리키고 있는 파일이 존재하지 않는 파일이라면, stat는 실패하게 되고, 존재하지 않는 파일이라고 보고한다. 만일 오퍼레이션이 성공하면 0을 리턴하고, 실패하면 -1을 리턴한다. 파일 문법 에러들에 더하여(6. 2. 3절 [File Name Errors] 참조),

다음의 errno는 이 함수를 위해 정의된 에러 상황이다.

ENOENT : filename의 이름을 가진 파일이 존재하지 않음.

함수 : int fstat (int filedes, struct stat *buf )

fstat 함수는 파일 이름을 인수로 받아들이는 것 대신에 개방된 파일 기술자를 인수로 받아들이는 점을 제외하고는 stat와 같다. 8장 [Low_Level I/O] 참조.
stat처럼, fstat도 성공하면 0을 리턴하고, 실패하면 -1을 리턴한다.
 
다음은 errno는 함수 fstat를 위해 정의된 에러상황이다.

EBADF : filedes 인수가 유용한 파일 기술자가 아니다.

함수 : int lstat (const char *filename, struct stat *buf )

lstat 함수는 심볼릭 링크를 따르지 않는다는 점을 제외하고는 stat와 같다. 만일 filename이 심볼릭 링크의 이름이라면, lstat는 링크 그 자체에 대한 정보를 리턴한다; 그렇지 않다면 lstat는 stat처럼 동작한다. 9.4절 [Symbolic Links] 참조.

 

9. 8. 3 파일의 형식 알아보기

파일 속성의 st_mode 필드안에 저장된 파일 모드는 두 가지 정보를 담고 있다: 하나는 파일 타입 코드이고, 다른 하나는 접근 허가 비트들이다. 이 절은 그 파일이 디렉토리인지, 아니면 소켓인지, 등등을 알 수 있는 파일 타입 코드에 대한 것을 설명할 것이다. 파일 접근 권한에 대한 정보는, 9. 8. 5절 [Permission Bits] 참조.

파일 모드에서 파일 타입 부분을 검색할 수 있는 미리 정의된 두 가지 방법이 있다. 그중 한가지는, 파일 모드 값을 시험하는 매크로를 사용해서 파일이 그 타입인지, 아닌지를 참과 거짓으로 리턴하는 방법이 있다. 둘째로, 파일 모드에서 다른 값들을 제외하고( mask out을 시켜서 ) 오직 파일 타입 코드만을 얻어내는 방법이다. 당신은 이 값을 여러 가지 다양한 상수들과 비교할 수 있다. 이 절에서 설명된 모든 심볼들은 헤더파일 'sys/stat. h'에 정의되어 있다.

다음은 파일 타입을 테스트하기 위한 매크로로써, 주어진 m은 stat 함수에 의해 리턴된 st_mode 필드이다.

int S__ISDIR (mode_t m) Macro

이 매크로는 만일 그 파일이 디렉토리라면 0이 아닌 값을 리턴한다.

int S__ISCHR (mode_t m) Macro

이 매크로는 만일 그 파일이 문자 특수화일( 터미널과 같은 디바이스 )이면 0이 아닌 값을 리턴한다.

int S__ISBLK (mode_t m) Macro

이 매크로는 만일 그 파일이 블록 특수 파일 ( 디스크와 같은 디바이스 )이면 0이 아닌 값을 리턴한다.

int S__ISREG (mode_t m) Macro

이 매크로는 만일 그 파일이 보통의 파일이라면 0이 아닌 값을 리턴한다.

int S__ISFIFO (mode_t m) Macro

이 매크로는 만일 그 파일이 FIFO 특수 파일이나, pipe라면 0이 아닌 값을 리턴한다. 10장 [Pipes and FIFOs] 참조.

int S__ISLNK (mode_t m) Macro

이 매크로는 만일 파일이 심볼릭 링크라면 0이 아닌 값을 리턴한다. 9. 4절 [Symbolic Links] 참조.

int S__ISSOCK (mode_t m) Macro

이 매크로는 만일 그 파일이 소켓이라면 0이 아닌 값을 리턴한다. 11장 [Sockets] 참조. 파일 타입을 테스트하는 비-POSIX의 방법은 BSD와의 호환성을 위해서 지원된다. 그 모드는 파일 타입 코드를 추출하기 위해 S_IFMT와 비트 단위 AND연산을 시키고, 적당한 타입 코드 상수와 비교한다.
예를 들어, S_ISCHR (mode) 는 다음과 동등하다:
((mode & S_IFMT) == S-IFCHR)

int S__IFMT Macro

이것은 mode 값에서 파일 타입 코드의 부분을 추출하기 위해서 사용되는 비트마스크이다. 이들은 다른 파일 타입 코드들을 위해서 심볼화된 이름을 가지고 있다.

S_IFDIR

이 매크로는 디렉토리 파일을 위한 파일 타입 코드의 값을 나타낸다.

S_IFCHR

이 매크로는 문자-지향(character-oriented) 디바이스 파일을 위한 파일 타입 코드의 값을 나타낸다.

S_IFBLK

이 매크로는 블록-지향(block-oriented) 디바이스 파일을 위한 파일 타입 코드의 값을 나타낸다.

S_IFREG

이 매크로는 보통의 파일을 위한 파일 타입 코드의 값을 나타낸다.

S_IFLNK

이 매크로는 심볼릭 링크를 위한 파일 타입 코드의 값을 나타낸다.

S_IFSOCK

이 매크로는 소켓을 위한 파일 타입 코드의 값을 나타낸다.

S_IFIFO

이 매크로는 FIFO 나 pipe를 위한 파일 타입 코드의 값을 나타낸다.

 

9. 8. 4 파일 소유자

모든 파일은 시스템 상에 등록된 사용자 이름들중 하나인 소유자를 갖고 있다. 각 파일은 또한 정의된 그룹들중 하나인 그룹을 갖고 있다. 파일 소유자는 누가 그 파일을 편집했는지( 특별히 당신이 GNU Emacs로 편집을 할 때 ) 알기 위해서도 필요하지만, 그것의 주된 목적은 접근 권한을 제어하기 위함이다.

파일 소유자와 그룹은, 그 파일이, 파일 소유자인 사용자를 위한 접근 권한 설정, 그리고 파일의 그룹에 속한 사용자들에 적용시키기 위한 다른 설정, 그리고 모든 다른 사람들에게 적용하기 위한 세 번째 비트 설정을 가지고 있기 때문에, 접근을 결정하는 규칙에 따른다. 9. 8. 6절 [Access Permission] 를 참조하면, 이 데이터를 기초로 해서 어떻게 접근을 결정하는지에 대한 상세한 정보를 얻을 수 있다.

파일이 만들어질 때, 그 파일의 소유자는 그 파일을 만든 프로세스의 사용자 ID를 통해 설정된다( 25. 2절 [Process personal] 참조). 파일의 그룹 ID는 그 프로세스의 그룹 ID로 설정되거나, 아니면 그 파일이 포함된 디렉토리의 그룹 ID로 설정되거나 하는데, 그것은 그 파일이 저장된 시스템에 따라 다르다. 당신이 원격 파일 시스템을 억세스할 때, 당신의 프로그램이 구동되고 있는 시스템에 따르는 것이 아니라 그 파일 자신이 속한 시스템의 규칙에 따라 행동한다. 그래서 당신의 프로그램은, 당신이 어떤 시스템에서 그 프로그램을 구동시키는지에 상관없이, 당신이 부딪칠지도 모르는 모든 동작에 종류에 대해 미리 대비해야만 한다.

당신은 chown 함수를 사용해서 현존하는 파일의 사용자 와/또는 그룹 사용자를 변경할 수 있다. 이것은 chown과 chgrp 쉘 명령어에 기본적으로 포함되어 있다. 이 함수를 위한 프로토타입은 'unistd. h'에 선언되어 있다.

함수 : int chown (const char *filename, uid_t owner, gid_t group)

chown함수는 owner로 filename의 이름을 가진 파일의 소유자를 변경하고, group으로 그룹 소유자를 변경한다. 어떤 시스템 상에서 파일 소유자의 변경은 파일 접근 권한 비트들에서 set-user-ID 와 set-group-ID 비트를 클리어 한다(이것은 이 비트들이 새로운 소유자에게 적당하지 않을지도 모르기 때문이다. ). 다른 파일 접근 권한 비트들은 변경되지 않는다.

성공하면 리턴 값은 0이고, 실패하면 -1이다. 보통의 파일 문법 에러에 더하여( 6. 2. 3절 [File Name Errors] 참조),

다음의 errno는 이 함수를 위해 정의된 에러 상황이다.

EPERM

이 프로세스는 요청된 변경을 수행하기에는 접근권한이 부족하다. 오직 특권이 부여된 사용자나, 파일의 소유자만이 그 파일의 그룹을 변경할 수 있다. 대부분의 파일 시스템들은, 오직 특권이 부여된 사용자만이 그 파일의 소유자를 변경할 수 있다; 어떤 파일 시스템들은 만일 당신이 현재 그 파일의 소유자라면, 그 소유자를 변경할 수 있도록 허용한다. 당신이 원격 파일 시스템을 억세스할 때, 당신의 프로그램이 구동되고 있는 시스템에 의해서가 아니라, 그 파일을 실제로 갖고 있는 시스템에 의해서 동작이 결정되어 진다. 27. 7절 [Options for Files] 를 참조로-POSIX_CHOWN_RESTRICTED 매크로에 대한 정보를 얻어라.

EROFS : 파일이 오직 읽기 모드 파일 시스템에 존재한다.

함수 : int fchown (int filedes, int owner, int group)

이것은 개방된 파일 기술자 filedes의 파일 소유자를 변경한다는 점을 제외하고는 chown 함수와 같다. 성공하면 0을 리턴하고 실패하면 -1을 리턴한다. 다음의 errno는 이 함수를 위해 정의된 에러코드들이다.

EBADF : filedes 인수가 유용한 파일 기술자가 아니다.

EINVAL : filedes 인수가 보통의 파일이 아닌, pipe 나 소켓에 해당한다.

EPERM

이 프로세스는 요청된 변경을 수행하기 위한 파일 접근 허가가 부족하다. 좀더 상세한 것은 위의 chmod를 참조하라.

EROFS : 파일이 오직 읽기 모드 파일 시스템 상에 존재하고 있다.

9. 8. 5 파일 접근 허가 비트들

파일 속성의 st_mode 필드에 저장되어 있는 파일 모드는 두 가지의 정보를 포함하고 있다: 파일 타입 코드와 파일 접근 허가 비트들. 이 절에서는 오직, 파일을 누가 읽거나 쓸 수 있는가를 제어하는, 파일 접근 허가 비트에 대해서 다룰 것이다. 이 절에서 설명하고 있는 심볼들은 헤더파일 'sys/stat. h'에 정의되어 있다. 이들 심볼들은 파일 접근 하가를 제어하는 파일 모드 비트들을 위해 정의되어졌다.

S_IRUSR, S_IREAD

파일의 소유자를 위한 읽기 허가 비트. 많은 시스템들에서, 이 비트는 0400이다. S_IREAD는 BSD 와의 호환성을 위해 제공되고 있는 잘 안쓰이는 동의어이다.

S_IWUSR, S_IWRITE

파일의 소유자를 위한 쓰기 허가 비트. 보통 0200. S_IWRITE는 BSD와의 호환성을 위해 제공되고 있는 잘 안쓰이는 동의어이다.

S_IXUSR, S_IEXEC

파일의 소유자를 위한 실행( 보통의 파일 ) 이나 검색( 디렉토리를 위한 ) 허가 비트이다. 보통 0100. S_IEXEC는 BSD와의 호환성을 위해 제공되고 있는 잘 안쓰이는 동의어이다.

S_IRWXU : 이것은 '(S_IRUSR | S_IWUSR | S_IXUSR)'과 같다.

S_IRG : 파일의 그룹 소유자를 위한 읽기 허가 비트. 보통 040

S_IWG : 파일의 그룹 소유자를 위한 쓰기 허가 비트. 보통 020.

S_IXGP : 파일의 그룹 소유자를 위한 실행이나 검색 허가 비트. 보통 010.

S_IRWXG : 이것은 '(S_IRGRP | S_IWGRP | S_IXGRP)'와 같다.

S_IROTH : 다른 사용자를 위한 읽기 허가 비트. 보통 04.

S_IWOTH : 다른 사용자를 위한 쓰기 허가 비트. 보통 02.

S_IXOTH : 다른 사용자를 위한 실행이나 검색 허가 비트. 보통 01.

S_IRWXO : 이것은 '(S_IROTH | S_IWOTH | S_IXOTH)'와 같다.

S_ISUID

이것은 실행비트에서 set-user-ID로, 보통 04000. 25. 4절 [How Change Persona] 참조. 역자주: set-user-ID ; 수행중의 사용자 식별번호를 지정.

S_ISGID

이것은 실행비트에서 set-group-ID로, 보통 02000이다. 25. 4절 [How Change Persona] 참조. 역자주: set-user-ID : 수행중의 그룹식별번호를 지정

S_ISVTX : 이것은 스티키 비트로 보통 01000이다.

 
역자주: sticky bit ; 수행중의 텍스트 이미지를 저장.

수행중인 파일에서, 시스템의 스와핑 방법들을 변경한다. 보통은 프로그램이 종료될 때, 기억장치 안의 그 들은 즉시 해제되어 재사용 가능해진다. 그러나 만일 수행 가능한 파일에서 스티키 비트가 설정되면, 그 시스템은 마치 그 프로그램이 여전히 실행되고 있는 것처럼 기억장소에 들을 계속 유지하고 있다.

이것은 완전한 수행에 많은 시간이 소요될 것 같은 프로그램에 유용하다. 디렉토리에서, 스티키 비트는 만일 당신이 어느 파일에 쓰기 허가를 가지고 있다면 디렉토리 안의 그 파일을 제거하도록 허용한다. 보통 사용자는 디렉토리 안의 모든 파일들을 지울 수 있거나, 혹은 그들중 어느 것도 지울 수 없거나한다 ( 그 사용자가 디렉토리에 쓰기 허가를 갖고 있는지, 없는지에 기초한다. ). 스티키 비트는 개개의 파일들의 제거 권한을 제어하는 것을 가능하게 만든다.

 
역자주: 여기서 는 기억장치에 저장된 하나의 단위라고 볼 수 있겠네요.

심볼들의 실행 비트 값들은 위의 테이블에 있다. 당신은 당신의 프로그램을 디버깅할 때 파일 모드 값들은 해석할 수 있다. 이들 비트 값들은 대부분의 시스템에 정확하게 동작하지만, 그것을 완전히 보장할 수는 없다.

 
주의: 파일 접근 권한을 숫자로 명시하는 것은 좋지 않은 습관이다.

이것은 이식성이 없고, 또한 누군가 당신의 프로그램을 읽을 때, 그 비트가 무엇을 의미하는지 알 수 없기 때문이다. 프로그램을 기호(symbolic)이름들을 사용하여, 깨끗하게 만들어라.

 

9. 8. 6 어떻게 파일에 당신의 접근을 결정하는가

운영체제는 보통 유효 사용자와 프로세스의 그룹 ID들과, 그것을 보충하여 그룹 ID들과 파일의 소유자, 그룹 그리고 허가 비트들을 기초로 하여 파일에 대한 접근 하가를 결정한다. 이 원칙들은 25. 2절 [Process Persona] 에 자세히 나와있다.

 
역자주: 유효사용자ID: 실제 생성된 파일에 대한 소유권을 갖는 사용자 실제사용자ID: 프로세스를 수행시킨 사용자의 식별번호

만일 프로세스의 유효 사용자 ID가 파일의 소유 사용자 ID와 대응됐을 때, 읽기, 쓰기와 실행/검색허가는 해당되는 "사용자" (또는 "소유자") 비트에 의해 제어된다. 그와 같이, 만일 유효 그룹 ID 나 프로세스의 그룹 ID가 파일의 그룹 소유자 ID와 대응됐을 때, 파일 접근 허가는 "그룹" 비트들에 의해 제어된다. 그렇지 않다면, 파일 접근 허가는 "다른 사용자" 비트들에 의해 제어된다. '루트'처럼 특권이 부여된 사용자는 파일 접근 허가에 관계없이 어느 파일이라도 억세스할 수 있다. 특별한 경우로, 특권이 부여된 사용자를 위한 실행가능 파일을 위해서, 적어도 하나의 실행 비트들을 설정해야만 한다.

 

9. 8. 7 파일 접근 허가 정하기

파일을 만드는 원래의 함수들( 예를 들어, open이나 mkdir)은 새로이 만들어질 파일의 접근 허가를 정하기 위해서 mode인수를 취한다. 그러나 정해진 모드는 그것이 사용되기 전에, 프로세스의 파일생성마스크나 umask에 의해 갱신되어진다.

파일 생성 마스크에 설정되어진 비트들은 새로이 개방된 파일에 대한 , 그 설정에 해당하는 권한이 없다는 것을 의미한다. 예를 들어, 만일 당신이 "다른 사용자" 접근 권한 비트들을 모두 마스크에 설정하면, 새로이 생성된 파일에, "다른 사용자" 범주에 들어가는 모든 프로세스들의 접근이 불 가된다, 심지어 개방된 파일에 주어진 mode 인수가 접근가능이었다고 하더라도. 즉, 파일 생성 마스크는 당신이 원하는 만큼 파일 접근 허가를 부여하기가 용이하다.

프로그램들은 특별한 파일에 적정한 모든 허가를 포함하고 있는 mode 인수를 지정해서 파일을 개방한다. 보통의 파일은, 모든 부류의 사용자들에게 읽기와 쓰기 허가가 있다. 이들 허가들을 개별적으로 사용자 자신의 파일 생성 마스크를 통해서 제한하는 것이다.

이름이 있는 현존하는 파일의 허가를 변경하기 위해서는, chmod를 호출하라. 이 함수는 파일 생성 마스크를 무시한다; 그것은 정확히 정해진 허가 비트들을 사용한다. 보통, 파일 생성 마스크는 사용자의 로그인 쉘에서 초기화 되있고 (umask 쉘 명령어를 사용해서 ) , 그리고 모든 부프로세스들이 물려받는다. 응용 프로그램들은 보통 파일 생성 마스크에 대해서 걱정할 필요가 없다. 그것은 미리 가정된 것을 할 것이다. 당신이 파일을 개방하고, 파일 접근 허가를 위해서 umask를 사용할 때, umask를 사용하는 것보다는, 파일을 개방한 후에 fchmod를 사용하여 그 일을 하는 것이 가장 쉬운 방법이다.

실제로, umask를 통한 변경은 오직 쉘에 의해서 행해진다. 그들은 umask함수를 사용한다.

이 절의 함수들은 헤더파일 "sys/stat. h'에 선언되어 있다.

함수 : mode_t umask (mode_t mask)

umask 함수는 mask로 현재 프로세스의 파일 생성 마스크를 설정하고, 파일 생성 마스크의 전의 값을 리턴한다.
 
이곳에서는 그 값들을 변경하지 않고, umask를 사용해서 마스크를 어떻게 읽는지를 보여주고 있다.
 
mode_t
read_umask (void) {
mask = umask (0);
umask (mask);
}
 
그렇지만, 만일 당신이 마스크 값을 단지 읽기를 원한다면 getumask를 사용하는 편이 낫다, 왜냐하면 재진입 때문이다. (적어도 만일 당신이 GNU 운영체제를 사용하고 있다면 )

함수 : mode_t getumask (void)

현재의 프로세스를 위한 파일생성 마스크의 현재의 값을 리턴한다. 이 함수는 GNU확장이다.

함수 : int chmod (const char *filename, mode_t mode)

chmod 함수는 mode로 filename의 이름을 가진 파일을 위한 접근 허가비트를 설정한다. 만일 그 filename의 파일이 심볼릭 링크라면, chmod는 링크 그 자체가 아니라, 링크가 가리키고 있는 파일의 접근 허가를 변경한다. 링크의 모드를 설정할 수 있는 방법은 아무 것도 없다.
 
성공하면 0을, 실패하면 -1을 리턴한다. 보통의 파일 이름 문법 에러에 더하여 ( 6. 2. 3절 [File Name Errors] 참조),
다음의 errno는 이 함수에서 정의된 에러 상황이다.

ENOENT : 그 이름을 가진 파일이 존재하지 않는다.

EPERM

이 프로세스가 이 파일의 접근 허가를 변경할 만큼의 권한을 가지고 있지 않다. 오직 파일의 소유자( 프로세스의 유효 사용자 ID로 판단된)나 특권이 부여된 사용자만 그들을 변경할 수 있다.

EROFS : 파일이 오직 읽기 모드의 파일 시스템에 존재한다.

함수 : int fchmod (int filedes, int mode)

이것은 기술자 filedes를 거쳐서 현재 개방된 파일의 접근 허가를 변경한다는 점을 제외하고는 chmod와 같다.
 
fchmod로부터의 리턴 값은 성공하면 0이고, 실패하면 -1이다.
다음의 errno는 이 함수를 위해 정의한 상황이다.

EBADF : filedes 인수가 유용한 파일 기술자가 아니다.

EINVAL

filedes 인수가 파이프나 소켓이거나, 아니면 그것이 실제로 접근 허가를 가지지 않는 어떤 것에 해당한다.

EPERM

이 프로세스가 파일의 접근 허가를 변경하기 위한 허가를 가지고 있지 않다. 오직 파일의 소유자( 프로세스의 유효 사용자 ID로 판단된)나, 특권이 부여된 사용자만이 그들을 변경할 수 있다.

EROFS

파일이 오직 읽기 모드 파일 시스템 안에 존재한다.
 

9. 8. 8 파일을 억세스하기 위하여 파일 접근 허가를 알아보기

특권이 부여된 사용자로써 프로그램을 실행하고 있을 때, 그 사용자에게 제한없이 파일들을 억세스하도록 허용한다. 예를 들어, '/etc/passwd/'파일을 수정하기와 같은것도. 유효 사용자 ID로 항상 루트에서 실행되도록 setuid 비트를 사용하는 그런 파일들이 아니라면, 프로그램들은 보통의 사용자에 의해 실행되도록 만들어졌다. 정해진 사용자에 의하여 파일을 억세스하는 프로그램에서, 파일들이 명백한 사용자에 의해 억세스되어진다. 루트로 실행되는 프로그램에서는 정해진 사용자가 무슨 파일이든지 억세스가 허가되지만, 보통은 보통의 사용자가 검색할 수 있는 그런 파일들만 허가된다.

프로그램은 그 파일을 읽거나 쓰기전에, 사용자가 파일을 억세스할 필요가 있는지를 명백하게 체크해야만 한다. 이것을 하기 위해서, 유효 사용자 ID 보다는 프로세스의 실 사용자 ID에 의한 접근 허가를 체크하는 access함수를 사용하라. ( setuid는 그 프로그램을 실제로 실행시킨 사용자를 반영한, 실 사용자 ID를 변경하지 않는다. ) 설명하기는 쉽지만, 사용하기는 어려운 엑세스를 체크할 수 있는 다른 방법이 있다. 이것은 파일 모드 비트들을 시험하고 시스템의 엑세스조작을 흉내내는 것이다.

이 방법은 많은 시스템들이 부가적인 억세스 제어를 가지고 있기 때문에 바람직한 방법은 아니다; 당신의 프로그램이 이식성 있게 그들을 흉내낼 수도 없고, 다른 시스템들이 가진 다른 종류의 엑세스 제어를 알아내려 시도할 수도 없다. access를 사용하는 것은 간단하고, 당신이 사용하는 시스템이 무엇이든지간에 그 시스템에 적당한 동작을 자동적으로 한다.

이 절에서 설명된 심볼들은 'uinstd. h'에 선언되어 있다.

함수 : int access (const char *filename, int how )

access 함수는 how인수로 정해진 방법으로 filename의 이름을 가진 파일을 억세스하다 수 있는지를 알아보는 함수이다. how 인수는 R_OK, W_OK, X_OK, 플래그를 비트 단위 OR연산된 것이거나 존재 테스트 F_OK 플래그이다. 이 함수는 접근 허가를 체크하기위해서 유효사용자 ID를 사용하기 보다는 호출하는 프로세스의 실 사용자와 그룹 ID들을 사용한다. 그 결과로, 만일 당신이 setuid나 setgid 프로그램( 25. 4절 [How Change Persona] 참조)으로부터 함수를 사용한다면, 그것은 그 프로그램을 실제로 구동하는 사용자에 대한 정보를 알려준다.
 
억세스가 허가되면 0을 리턴하고, 그렇지 않으면 -1을 리턴한다. (즉, 하나의 함수로 취급하여, access는 요청된 접근이 거부되면 참을 리턴한다. ) 보통의 파일 이름 문법 에러에 더하여 ( 6. 2. 3절 [File Name Errors] 참조) 다음의 errno는 이 함수를 위해 정의된 에러 상황이다.

EACCES : how인수로 정해진 접근이 거부되었다.

ENOENT : 파일이 존재하지 않는다.

EROFS : 쓰기 허가가 오직 읽기 모드 파일 시스템 상에 존재하는 파일에 요청되었다.

이들 매크로들은 access 함수에 how인수로서 사용하기 위한 것으로 헤더파일 'unistd. h'에 정의되어 있다.

매크로 : int R__OK

읽기 허가를 위한 테스트를 의미하는 인수.

매크로 : int W__OK

쓰기 허가를 위한 테스트를 의미하는 인수

매크로 : int X__OK

실행/검색 허가를 위한 테스트를 의미하는 인수.

매크로 : int F__OK

파일의 존재를 위한 테스트를 의미하는 함수.

 

9. 8. 9 파일에 관련된 시간들

각 파일은 검색 시간, 갱신 시간, 속성 갱신 시간에 해당하는 세 가지 타임 스탬프들을 가지고 있다. 이것은 stat의 st_atime, st_mtime, st_ctime 멤버에 해당한다; 9. 8절 [File Attributes] 참조. 이 시간들 모두는 time_t objects 안에 표현된다. 이 데이터 타입은 'time. h'에 정의되어 있다. 시간 값들의 표현과 다루는 방법에 대한 자세한 설명은 17. 2절 [Calendar Time] 에 있다.

현존하는 파일이 개방되어 있을 때, 그 속성변경시간과 갱신 시간필드들은 갱신된다. 파일읽기는 검색 시간 속성을 갱신시키고, 파일쓰기도 또한, 갱신시간 속성을 갱신시킨다. 파일이 만들어질 때, 파일을 위한 세 가지 타임 스탬프는 현재의 시간으로 설정된다. 디렉토리의 속성 변경 시간과 갱신 시간의 필드들은 새로운 엔트리가 들어오면 갱신된다.

link함수로 원래의 파일에 새로운 이름이 더해지면, 링크되어진 파일의 속성 변경 시간(attribute change time)이 갱신되고, link를 통해 만들어진 새로운 이름을 포함하는 디렉토리의 속성 변경 시간과 갱신 시간(modifi-cation) 필드들이 갱신된다. 이와 같은 필드들은 unlink, remove, rmdir로 파일 이름이 제거될 때 영향을 받는다. rename로 파일을 재명명할 때, 파일이 포함된 부모 디렉토리들의 속성 변경 시간과 갱신시간 필드들 이 영향을 받지만, 파일에 관련된 시간속성들은 변경되지 않는다.

파일의 속성들의 변경은( 예를 들어, chmod를 통해서) 그 속성 변경시간 필드를 갱신한다. 당신은 속성 변경 시간을 제외하고, utime 함수를 사용해서 명시적으로 파일의 타임스탬프를 변경할 수 있다.

이것은 헤더파일 'utime. h'에 정의되어 있다.

데이터 타입 : struct utimbuf

utimbuf 구조체는 어떤 파일에 새로운 검색 시간과 갱신시간을 정해주기 위해서 utime 함수에서 사용된다. 이것은 다음과 같은 멤버들을 포함한다.
 
time_t actime 이것은 파일의 검색시간이다.
time_t modtime 이것은 파일의 갱신시간이다.

함수 : int utime (const char *filename, const struct utimbuf *times)

이 함수는 filename이름을 가진 파일과 연관된 시간을 변경하기 위해 사용된다. 만일 times가 널 포인터라면, 파일의 검색시간과 갱신시간들은 현재의 시간으로 설정된다. 그렇지 않다면, 그들은 times가 가리키는 구조체 utimbuf의 actime와 modtime 멤버들로부터 얻은 값으로 시간을 설정한다.
 
파일의 속성변경시간은 두 가지 경우 모두 현재의 시간으로 설정된다. ( 타임 스탬프들이 변경은, 파일 속성의 변경을 수반한다. )
 
utime 함수는 성공하면 0을 리턴하고, 실패하면 -1을 리턴한다. 보통의 파일 이름 문법 에러에 더하여( 6. 2. 3절 [File Name Errors] 참조), 다음의 errno는 이 함수를 위해서 정의된 에러 상황들이다.

EACCES

times 인수로 널 포인터를 주었을 때 허가문제가 있다. 파일의 타임 스탬프를 갱신하기 위해서는 파일의 소유자이고 파일에 쓰기 허가를 가지고 있거나, 특권이 부여된 사용자이어야 한다.

ENOENT : 파일이 존재하지 않는다.

EPERM

만일 times인수가 널 포인터라면, 당신은 파일의 소유자이거나 특권이 부여된 사용자이어야만 한다. 이 에러는 그 문제를 보고하는데 사용된다.

EROFS : 파일이 오직 읽기 모드 파일 시스템에 존재한다.

 
세 가지 타임 스탬프들은 연관된 마이크로초(microsecond) 부분을 가지고 있다. 이들 필드들은 st_atime_usec, st_mtime_usec, st_ctime_usec라고 불린다; 각각은 마이크로초 단위로 표현된 0에서 999, 999사이의 값을 가지고 있다. 그들은 timeval 구조체의 tv_usec에 해당한다; 17. 2. 2절[High-Resolution Calendar] 참조. utimes 함수는 utime와 같지만, 파일에 관련된 시간들을 아주 세밀하게 정하는 것을 허용한다.

이 함수를 위한 프로토타입은 헤더파일 'sys/time. h'이다.

함수 : int utimes (const char *filename, struct timeval tvp[2])

이 함수는 filename의 이름을 가진 파일의 검색 시간과 갱신시간을 설정하는 함수이다. 새로운 파일 검색 시간은 tvp[0]에 의해 정해지고, 새로운 갱신시간은 tvp[1]에 의해 설정된다. 이 함수는 BSD에서 왔다. 리턴 값과 error 에러 상황은 utime함수와 같다.


9. 9 특수 파일 만들기

mknod 함수는 디바이스와 같은, 특수한 파일을 만들기 위한 함수다. GNU 라이브러리는 BSD와의 호환성을 위해서 이 함수를 갖고 있다. mknod의 프로토타입은 'sys/stat. h'에 선언되어 있다.

함수 : int mknod (const char *filename, int mode, int dev)

mknod 함수는 filename의 이름을 가진 특수한 파일을 만든다. mode인수는 S_IFCHR(문자 특수 파일을 위해)나, S_IFBLK(블럭 특수 파일을 위해)와 같은 다양한 특수 파일 비트들을 가지고 파일의 모드를 지정한다. 9. 8. 3절 [Testing Fiel Type] 참조. dev 인수는 디바이스를 정한다. 그것의 정확한 해석은 만들어진 특수 파일의 종류에 의존한다.
 
리턴 값은 성공하면 0이고, 실패하면 -1이다. 보통의 파일 이름 문법 에러에 더하여 ( 6. 2. 3절 [File Name Errors] 참조), 다음의 errno는 이 함수를 위해 정의된 에러 상황이다.

EPERM : 호출한 프로세스가 특권이 없다. 특수 파일을 만들 수 있는 것은 슈퍼유저뿐이다.

ENOSPC : 새로운 파일을 갖고 있는 디렉토리나 파일 시스템이 가득 찼고, 더 이상 확장할 수 없다.

EROFS

새로운 파일을 갖고 있는 디렉토리가 오직 읽기 모드 파일 시스템 상에 있기 때문에 갱신될 수 없다.

EEXIST

filename의 이름을 가진 파일이 이미 존재한다. 만일 당신이 이 파일을 대체하기 원한다면, 당신은 먼저 그 원래의 파일을 제거해야만 한다.


9. 10 임시 파일들

만일 당신의 프로그램에서 임시파일의 사용이 필요하다면, 당신은 그것을 개방하기 위해 tmpfile함수를 사용할 수 있다. 또는 임시파일의 이름을 만들기 위해 tmpnam 함수를 사용하고 fopen을 사용해서 보통의 방법으로 그 임시파일을 개방할수 있다.

tempnam함수는 tmpnam함수와 같지만, 당신이 그 임시파일이 들어갈 디렉토리가 무엇인지, 그리고 그들의 파일이름이 무엇인지에 대한 것을 정하도록 허용한다.

함수 : FILE * tmpfile (void)

이 함수는 "wb+" 모드로 fopen을 호출함으로써, 갱신한 모드로 임시 바이너리 파일을 만든다. 그 파일은 그 프로그램이 종료할 때나 닫힐 때, 자동적으로 제거된다. ( 어떤 다른 ANSI C 시스템에서 그 프로그램이 비정상적으로 종료되면 파일의 제거도 실패할 것이다. )

함수 : char * tmpnam (char *result)

이 함수는 현재 존재하고 있는 파일 이름이 아닌 유용한 파일이름을 만들어서 리턴한다. 만일 result인수가 널 포인터라면, 리턴 값은 다음 호출로 갱신된 내부 정적 스트림의 포인터이다. 그렇지 않다면, result인수는 적어도 L_tmpnam의 배열을 가리키는 포인터이고 result는 그 배열 안에 저장된다. tmpnam의 호출에 너무 많은 시간이 소요되면 실패의 가능성이 있다. 이것은 임시파일을 정해진 개수의 파일이 있어야 하는 곳에서 이미 다른 파일들로 그 개수가 채워진 곳에 만들려했기 때문이다. tmpnam이 실패하면 널 포인터를 리턴한다.

매크로 : int L__tmpnam

이 매크로 값은 tmpnam함수로 만들어진 파일이름을 저장하기에 충분한 문자열의 최소 할당 크기를 나타내기 위한 정수 상수 표현이다.

매크로 : int TMP__MAX

매크로 TMP_MAX는 당신이 tmpnam으로 만들 수 있는 임시 파일 이름의 최소 경계이다. 당신이 너무 많은 임시 파일 이름을 만들어 실패를 경험하기 전에, 이 매크로 값에 의존할 수 있다. GNU 라이브러리에서는, 당신은 당신의 디스크의 공간이 허락하는 만큼의 많은 개수의 임시파일을 만들 수 있다. 어떤 다른 시스템에서는 임시파일의 개수를 작게 제한하고 있다. 그 제한은 25보다는 결코 작지 않다.

함수 : char * tempnam (const char *dir, const char *prefix)

이 함수는 단일한 임시 파일이름을 생성한다. 만일 prefix가 널 포인터가 아니고 이 문자열이 5개 문자 이상이면 그 prefix가 파일이름으로 사용된다. 임시파일 이름을 위한 디렉토리는 밑의 것들을 시험함으로 인해 결정한다. 디렉토리는 반드시 존재하고 기록 가능해야 한다.
환경 변수 TMPDIR이 정의되었는지. . .
dir 인수가 널 포인터인지. . .
P_tmpdir 매크로의 값.
'/tmp' 디렉토리.
이 함수는 SVID와의 호환성을 위해 정의되었다.

SVID 매크로 : char * P__tmpdir

이 매크로는 임시파일을 위한 디폴트(default) 디렉토리의 이름이다. 오래된 유닉스 시스템들은 설명된 함수들을 가지고 있지 않다. 대신에 그들은 mktemp 와 mkstemp 을 사용한다. 이들 함수들은 당신이 준 파일 이름 템플리트 문자열을 갱신하는 작업을 한다. 문자열의 마지막 6섯개 문자들은 반드시 'XXXXXX'이 되어야만 한다. 이들 6개 'X' 문자들은 단일한 파일 이름 문자열을 만든 6개의 문자들로 대체된다. 문자열은 보통 '/tmp/prefixXXXXXX'처럼 사용하고 각각의 프로그램은 단일한 prefix를 사용한다.

 

노트 : mktemp 와 mkstemp가 템플리트 문자열을 갱신하기 때문에, 당신은 그들에게 문자열 상수를 주지 않아야 한다. 문자열 상수들은 보통 오직 읽기만 가능한 기억장소에 존재하고, 그래서 당신의 프로그램은 mktemp 나 mkstemp가 문자열을 갱신하려 시도할 때 파괴되고 만다.

함수 : char * mktemp (char *template)

mktemp함수는 위에 설명된 것처럼 임시 문자열을 고쳐서 단일한 파일이름을 생성한다. 만일 성공하면, 갱신된 템플리트를 리턴한다. 만일 mktemp가 단일한 파일 이름을 발견할 수 없다면, mktemp는 빈 문자열을 만들고 그것을 리턴한다. 만일 그 템플리트에 'XXXXXX'로 끝나지 않는다면, mktemp는 널 포인터를 리턴한다.

함수 : int mkstemp (char *template)

mkstemp함수는 mktemp가 하는 것처럼 단일한 파일 이름을 생성하지만, mkstemp는 또한 open의 과정을 통해서 당신을 위해 그 파일을 개방한다 ( 8. 1절 [Opening and Closing Files] 참조). 만일 성공하면 적당한 템플리트를 갱신하고 그 파일을 읽기와 쓰기를 위해 개방해서 리턴한다. 만일 mkstemp가 단일한 이름을 가진 파일을 만들 수 없다면 그것은 빈 문자열의 템플리트를 만들고 -1을 리턴한다. 만일 템플리트가 'XXXXXX'로 끝나지 않는다면, mkstemp는 -1을 리턴하고 템플리트를 갱신하지 않는다.
 
mktemp와 달리, mkstemp는 임시 파일을 만들려 시도하고 있는 다른 프로그램과 충돌의 가능성이 없이 단일한 파일을 만드는 것을 보증한다. 그 이유는 open함수에 O_EXEL 플래그 비트를 사용함으로 인해서, 당신이 항상 새로운 파일을 만들기를 원함을 알리고, 그 파일이 이미 존재한다면 에러를 나타내도록 작업하기 때문이다.


목차 이전 : 8. 저수준 입출력 다음 : 10. 파이프와 FIFO

'Programming > Unix Programing' 카테고리의 다른 글

11. Sockets  (0) 2007.12.22
10. Pipes and FIFO  (0) 2007.12.22
9. 파일시스템 인터페이스  (0) 2007.12.22
8. 저수준 입출력  (0) 2007.12.22
7. 스트림의 입출력  (0) 2007.12.22
6. 입출력 개괄  (0) 2007.12.22
Comment 0 Trackback 0
Top

8. 저수준 입출력

목차 이전 : 7. 스트림에서의 입출력 다음 : 9. 파일 시스템 인터페이스


8 저수준 입/출력

이 장은 파일 기술자 상에서 저수준의 입/출력 명령을 행하는 함수들에 대해서 설명하고 있다. 이 함수들은 스트림과는 다른 것들을 위한 저수준의 제어 명령들을 수행하기 위한 함수로써, 7장 [I/O on Streams]안에 설명된 고 수준의 입/출력 함수들에 기본적으로 포함되어 있다.

스트림-수준 입/출력은 더 유연하고, 보통 더 편리하다; 그래서, 프로그래머들은 일반적으로 필요할 때만 기술자-수준 함수들을 사용한다. 이것에는 몇 가지 이유가 있다.

  • 큰 덩어리에서 바이너리 파일들을 읽기 위해
  • 파싱하기 전에 코어 안에서 전체의 파일을 읽기 위해
  • 오직 기술자를 가지고 수행되어지는, 데이터 참조와는 다른 명령을 수행하기 위해 (당신은 스트림과 일치하는 기술자를 얻기 위해 파일번호를 사용할 수 있다. )
  • 자식 프로세스에 기술자를 넘겨주기 위해. ( 자식은 상속받은 기술자를 사용하기 위해 자신의 스트림을 만들 수 있다, 하지만 직접적으로 스트림을 상속받을 수는 없다. )


8. 1 파일 열고 닫기

이 절은 파일 기술자를 사용하여 파일들을 열고 닫는 원시적인 것들을 설명하고 있다. open 과 creat 함수들은 헤더파일 'fnctl. h'에 선언되어 있고, close는 'unistd. h'에 선언되어 있다.

함수 : int open (const char *filename, int flags[, mode_t mode])

open 함수는 filename으로 이름지어진 파일을 위한 새로운 파일 기술자를 만들고 반환한다. 처음에, 파일을 위한 파일 위치 지시자는 파일의 시작점이다. mode 인수는 오직 파일을 만들 때 사용되어지지만, 어떤 경우에도 인수로 공급하기 위한 탈이 없다. flags 인수는 파일을 어떤 방식으로 개방할 것인지를 제어한다. 이것은 비트 마스크(bit mask) 이다; 당신은 적당한 인수를 주어 OR연산을 사용해서 값을 만든다. ( C에서는 '|' 연산자를 사용해서 ) 파일 검색 모드를 정하기 위해서 flags인수는 이들 값 중에서 하나를 반드시 포함시켜야만 한다.
 
  • O_RDONLY 읽기 전용으로 파일을 개방
  • O_WRONLY 쓰기 전용으로 파일을 개방
  • O_RDWR 읽기와 쓰기가 둘다 가능한 개방

flags인수는 아래 값들과 조합을 통해서 포함할 수 있다.

O_APPEND

이것이 설정되면, 모든 쓰기 명령들은 지금 현재의 파일 위치에 개의치 않고, 파일의 끝에 데이터를 쓴다.

O_CREAT

파일이 이미 존재하지 않으면 새롭게 만들어질 것이다.

O_EXCL

만일 O_CREAT 와 O_EXCL 이 설정되면, 파일이 이미 존재할 때 open명령은 실패한다.

O_NOCTTY

만일 filename이 터미널 디바이스 이름이면, 프로세스를 위하여 제어하는 터미널이 만들어지지 않는다. 24장 [Job Control]를 참조하여, 제어하는 터미널이 되는 것이 무엇을 의미하는지에 대한 정보를 참조하라.

O_NONBLOCK

이것은 비 블록화 모드로 설정한다. 이 옵션은 보통 FIFO와( 10장 [Pipes and FIFOs]참조) 같은 특별한 파일들과 터미널과 같은 디바이스에 사용된다. 보통, 이들 파일들을 위하여 파일이 "준비"상태일 동안 블록을 개방한다. 만일 O_NONBLOCK이 설정되면, open은 즉시 반환한다. O_NONBLOCK 비트는 또한 읽기와 쓰기에 영향을 미친다: 만약 그곳에 읽기에 유용한 입력이 없거나, 출력이 쓰여질 수 없다면, 그러한 실패의 상황을 즉각적으로 그들에게 반환하는 것을 허용한다.

O_TRUNC

만일 파일이 존재하고 쓰기 모드로 개방이 되어 있다면, 0의 길이로 그것을 자른다. 이 옵션은 디렉토리나, FIFO들과 같은 특별한 파일들이 아니라 일반적인 파일들에게 유용하다.

이 심볼 상수들에 대한 더 많은 정보는 8.10절 [File Status Flags] 을 참조하라.

open으로 부터 보통 반환되는 값은 음이 아닌 정수 파일 기술자이다. 에러가 발생한 경우에는 대신에 -1의 값을 반환한다.

보통의 파일이름 문법에러에 더하여(6.2.3절 [File Name Errors] 참조), 다음은 이 함수에서 정의하고 있는 errno와 에러상황이다.

EACCES

파일은 존재하지만, flags 인수에 의한 요청으로 읽기/쓰기가 불가능하다.

EEXIST

O_CREAT 와 O_EXCL이 모두 설정되고, 그 파일은 이미 존재한다.

EINTR

open명령은 시그널 (signal) 에 의해 인터럽트 되어졌다. 21. 5절 [Interrupted Primitives] 참조

EISDIR

flags 인수는 쓰기 모드로 지정되어 있고, 파일은 디렉토리이다.

EMFILE

프로세스가 너무 많은 파일을 개방했다.

ENFILE

전제의 시스템이나, 아마도 디렉토리를 포함한 파일시스템이 어떤 순간에 더 이상 파일의 개방을 지원하지 않는다. (이 문제는 GNU시스템에서는 발생하지 않는다. )

ENOENT

같은 이름의 파일이 존재하지 않는데, O_CREAT 플래그도 선언되지 않았다.

ENOSPC

새로운 파일을 넣어야 하는 디렉토리나 파일 시스템이 확장되어질 수 없다. 왜냐하면 그곳에는 남아있는 공간이 없기 때문이다.

ENXIO

O_NONBLOCK 와 O_WRONLY가 둘다 설정되고 filename이름을 가진 파일이 FIFO이고, 어떤 프로세스도 읽기 위해서 파일을 개방하지 않았을 때.

EROFS

파일이 오직 읽기 모드인 파일 시스템 상에 존재하고 flags 인수로 O_WRONLY, O_RDWR, O_CREAT, O_TRUNC 의 어떤 것이라도 설정되면.

이 open 함수는 스트림을 만드는 fopen과 freopen함수들을 위해 원시적인 기초가 되어진다.

시대에 뒤떨어진 함수 : int creat (const char *filename, mode_t mode)

이 함수는 시대에 뒤떨어져 있다. creat(filename, mode) 이렇게 호출하는 것은 open (filename, O_WRONLY | O_CREAT | O_TRUNC, mode) 이렇게 호출하는 것과 동등하다.

함수 : int close (int filedes)

close함수는 파일 기술자 filedes를 폐쇄한다. 파일 닫기는 다음의 결과를 갖는다. 파일 기술자가 해제되어진다. 파일에서 프로세스에 의해 소유된 어느 잠긴(lock) 레코드는 잠금해제 된다. pipe나 FIFO와 연관된 모든 파일 기술자가 닫혀질 때, unread 데이터는 버려진다. close로부터 반환되는 일반적인 값은 0이다. 실패의 경우에 -1이 반환된다.
 
다음의 errno는 이 함수에서 정의되어진 에러상황이다.

EBADF

filedes 인수가 유용한 파일 기술자가 아니다.

EINTR

close호출이 시그널에 의해 인터럽트 되었다. 21.5절 [Interrupted Primitives]를 참조. 이것은 EINTR을 어떻게 취급하는지에대한 적당한 예이다;

TEMP_FAILURE_RETRY (close (desc));

스트림을 닫기 위해서는, close로 파일 기술자를 닫으려 시도하는 대신에 fclose( 7. 4절 [Closing Stream] 참조)를 호출하라. 이것은 버퍼된 출력을 플러쉬하고, 닫혔음을 지적하기 위해 스트림 오브젝트를 갱신한다.


8. 2 기본 입력과 출력

이 절은 파일 기술자상의 기본 입력과 출력 명령을 수행하기 위한 함수들을 설명하고 있다: read, write, 그리고 lseek. 이들 함수들은 헤더파일 'unistd. h'에 선언되어 있다.

데이터 타입 : ssize__t

이 데이터 타입은 단일한 명령으로 읽혀지거나 쓰여질 수 있는 블록의 크기를 나타내기 위해 사용되어진다. 이것은 size_t 와 유사하지만 반드시 부호화된 타입이어야 한다.

함수 : ssize_t read (int filedes, void *buffer, size_t size)

read함수는 기술자 filedes의 파일로부터 size 바이트를 읽고, 그 결과를 버퍼에 저장한다. ( 이것은 문자 스트링이 필요하지 않고 그곳에는 부가된 널 종료문자가 없다. ) 반환 값은 실제로 읽은 바이트의 수이다. 이것은 size보다 적을수도 있다; 예를 들어, 만일 파일에 남겨진 바이트의 수가 적거나 즉시 유용한 바이트의 수가 적은 경우 등이 있다. 정확한 동작은 파일의 종류가 무엇인지에 따라 의존한다. size 바이트보다 덜 읽는 것은 에러가 아님을 기억하라. 0의 값은 파일의 끝을 지적한다. ( 만일 size 인수의 값이 0인 경우를 제외하고. . ) 이것은 에러로 간주하지 않는다.
 
만일 당신이 파일의 끝인 상태에서 read를 호출하면, 그것은 0을 반환하는 것 외에 아무 일도 하지 않는다. 만일 read가 적어도 한 문자를 반환한다면, 당신이 파일의 끝에 도달했는지를 알 수 있는 아무런 방법이 없다. 그러나 만일 당신이 끝에 도달해 있었다면 다음 read의 호출은 0을 반환해서 파일의 끝임을 지적해줄 것이다. 에러가 발생한 경우에, read는 -1을 반환한다.

다음의 errno는 이 함수에서 정의된 에러의 상황이다.

EAGAIN

일반적으로, 즉시 유용한 입력이 없을 때, read는 입력을 기다린다. 그러나 만일 그 파일에서 O_NONBLOCK가 설정되면( 8.10절 [File Status Flags] 참조), read는 아무런 데이터도 기다리지 않고 즉시 반환하고, 이 에러를 보고한다.
 
호환성 노트 : BSD Unix의 대부분의 버전은 이것을 위한 다른 에러코드를 사용한다: EWOULDBLOCK. GNU 라이브러리에서는, EWOULDBLOCK은 EAGAIN의 다른 이름이다. 그래서 당신이 어떤 이름을 사용해도 문제가 발생되지 않는다. 어떤 시스템들은, 특별한 문자 파일로부터 데이터의 큰 덩어리를 읽으려 할 때, 만일 커널(kernal)이 당신의 것을 담을 수 있는(to lock down the user's pages), 충분한 물리적 메모리를 얻을 수 없는 경우에 EAGAIN의 에러를 내고 실패했음을 지적한다. 디바이스가 사용자의 메모리 영역을 직접적으로 억세스 하는 것이 제한되어 있는 것은 그들은 커널내부의 분리된 버퍼를 사용하기 때문이다. 그것에 터미널들은 포함되지 않는다,

EBADF

filedes 인수에 주어진 것이 유용한 파일 기술자가 아니다.

EINTR

read가 입력을 기다리고 있는 동안 시그널에 의해 인터럽트 되어졌다. 21. 5절 [Interruped Primitives]를 참조.

EIO

많은 디바이스들, 그리고 디스크 파일들을 위하여, 이 에러는 하드웨어 에러를 지적한다. EIO는 또한 제어 중인 터미널로부터 배경 프로세스가 읽기를 시도하고, SIGTTIN의 신호가 아무런 동작도 하지 않고 보내짐에 의해 멈춘 프로세스의 일반적 동작에 대해 발생한다. 이것은 만약 신호가 블록되어지거나 무시되거나, 프로세스 그룹이 부모 프로세스를 잃어버렸다면 발생되어질 것이다. 24장 [Job Control]를 참조해서 job control에 대한 더 많은 정보를 얻고, 21장 [Signal Handling] 를 참조해서, 신호에 대한 정보를 참조하라.
 
---- 역자주 : blocked--> 블록된 이라고 이곳에서 해석하였다. 좀더 자세히 설명하자면 블록이란 보통 하나의 입/출력 단위로 표현되는 것이 일반적이지만 이곳에서 쓰인 것의 의미는 아마도(?) 유닉스처럼 다중 프로그래밍 시스템에서 하나의 프로세서가 자원을 획득하지 못하여 아무런 작업도 수행할 수 없는 상태에 처한 것. 이러한 상태를 블록된 상태라고 하는 것 같다. ( 크. . . 자신할 수 없어서 죄송. . 하지만 거의 맞을 듯~)

read 함수는 fgetc처럼 스트림으로부터 읽는 동작을 하는 모든 함수들에 기본적으로 포함되어 있다.

    함수 : ssize_t write (int filedes, const void *buffer, size_t size)

    write함수는 기술자 filedes 파일에 버퍼에 있는 size 바이트의 데이터를 쓰는 함수이다. 버퍼에 있는 데이터는 문자 스트링과 널 문자가 필요하지 않다. 반환 값은 실제로 쓰여진 바이트들의 개수이다. 이것은 보통은 size와 같지만, 더 적을수도 있다( 예를 들어, 만일 물리적 매체가 채워져 있는 경우 ). 에러가 발생하면 write는 -1을 반환한다.

    다음의 errno는 이 함수에서 정의한 에러상황이다.

    EAGAIN

    일반적으로 write 명령하에서 블록 쓰기 동작은 완벽하다. 그러나 만일 그 파일에서 O_NONBLOCK 플래그가 설정되어 있다면, 그것은 어떤 데이터도 쓰지 않고 곧바로 반환하고, 에러를 발생한다. 그 상황에 대한 하나의 예는 프로세스가 출력하려는 블록을 STOP 문자를 받아들임으로 인해 출력이 일시 중단되고, 흐름제어를 지원하는 터미널 디바이스에 쓰기를 시도할 때 발생한다.
     
    호환성 노트 : BSD Unix의 대부분의 버전은 이것을 위한 다른 에러코드를 사용한다: EWOULDBLOCK. GNU 라이브러리에서는, EWOULDBLOCK은 EAGAIN의 다른 이름이다. 그래서 당신이 어떤 이름을 사용해도 문제가 발생되지 않는다. 어떤 시스템들은, 특별한 문자 파일로부터 데이터의 큰 덩어리를 쓰려 할 때, 만일 커널(kernal)이 당신의 것을 담을 수 있는( to lock down the user's pages ), 충분한 물리적 메모리를 얻을 수 없는 경우에 EAGAIN의 에러를 내고 실패했음을 지적한다. 디바이스가 사용자의 메모리 영역을 직접적으로 억세스 하는 것이 제한되어 있는 것은 그들은 커널내부의 분리된 버퍼를 사용하기 때문이다. 그것에 터미널들은 포함되지 않는다,

    EBADF

    filedes 인수는 유용한 파일 기술자가 아니다.

    EFBIG

    파일의 크기가 그 실행에서 지원할 수 있는 것보다 크다.

    EINTR

    write 오퍼레이션은 명령이 완전히 수행될 때까지 기다리는 동안 신호에 의해 인터럽트 되어졌다. 21.5절 [Interrupted Primitives] 참조.

    EIO

    많은 디바이스들, 그리고 디스크 파일들을 위하여, 이 에러는 하드웨어 에러를 지적한다. EIO는 또한 제어 중인 터미널로부터 배경 프로세스가 읽기를 시도하고, SIGTTIN의 신호가 아무런 동작도 하지 않고 보내짐에 의해 멈춘 프로세스의 일반적 동작에 대해 발생한다. 이것은 만약 신호가 블록되어지거나 무시되거나, 프로세스 그룹이 부모 프로세스를 잃어버렸다면, 발생되어질 것이다. 24장 [Job Control]를 참조해서 job control에 대한 더 많은 정보를 얻고, 21장 [Signal Handling] 를 참조해서, 신호에 대한 정보를 참조하라.

    ENOSPC

    디바이스가 차 있다.

    EPIPE

    이 에러는 어느 프로세스에 의해서 읽기 위해 개방되지 않는 파이프나 FIFO에 쓰려 시도할 때 반환된다. 이것이 발생될 때, SIGPIPE 신호를 프로세스에 보낸다; 21장 [Signal Handling] 참조.

    당신이 EINTR 실패를 방지하기 위해 조정하지 않았다면, 당신은 실패한 write의 호출에 대해서 errno를 체크해야할 것이다. 그리고 만일 errno가 EINTR 이라면, 그냥 간단하게 다시 호출해주면 된다. 21. 5절 [Interrupted Primitives] 참조. 이것을 하는 쉬운 방법으로 매크로 TEMP_FAILURE_RETRY 가 있다. 다음처럼:

    nbytes = TEMP_FAILURE_RETRY (write (desc, buffer, ount));

    write 함수는 fputc처럼 스트림에 쓰는 모든 함수들에 기본적으로 포함되어 있다.


8. 3 기술자의 파일 위치 설정하기

당신이 fseek로 스트림의 파일 위치를 설정할 수 있는 것처럼, 당신은 lseek를 통해서 기술자의 파일 위치를 설정할 수 있다. 이것은 다음에 읽거나 쓸 명령을 위해 파일의 위치를 정한다. 파일의 위치와 그것이 어떤 의미를 갖는지에 대한 정보를 7. 15절 [File Position] 을 참조하라. 기술자로부터 현재 파일 위치의 값을 읽으려면, lseek(desc, 0, SEEK_CUR)을 사용하라.

함수 : off_t lseek (int filedes, off_t offset, int whence)

lseek 함수는 기술자 filedes 파일의 파일 위치를 변경하기 위해 사용된다. whence 인수는 fseek와 같은 방법으로 사용되어 offset을 어떻게 해석되어야 하는지를 정하고, 심볼 상수 SEEK_SET, SEEK_CUR, SEEK_END중에 하나가 될 수 있다.
 

SEEK_SET 이것은 파일의 시작점을 기준으로 문자들의 수를 셈한다.  

SEEK_CUR 현재의 파일 위치를 기준으로 문자들의 수를 셈한다. 여기서 문자들의 수(count)는 양이나 음의 값이 되어진다.  

SEEK_END 파일의 끝을 기준으로 문자들의 수를 셈한다. 음의 값은 현재의 파일안의 영역으로 파일의 위치를 정한다; 양의 값은 현재보다 앞의 영역으로 파일 위치를 정한다. 파일 위치를 앞의 영역으로 정하고, 실제로 데이터를 쓰면, 원래의 파일의 끝점에서 현재의 파일위치 사이의 빈 공간은 0으로 채워지고, 결국 파일은 확장되는 것이다.  

lseek를 통한 반환 값은 보통 파일의 시작점부터 바이트의 수를 계산한 파일의 위치이다. 당신은 현재의 파일 위치를 읽기 위해 SEEK_CUR와 함께 lseek를 사용할 수 있다. 또한 파일의 현재의 끝점을 넘어선 곳으로 파일의 위치를 설정할 수 있는데, 이것은 스스로 파일의 길이를 길게 만드는 것이 아니다; lseek는 결코 파일을 변화시키지 않는다. 그러나 그 위치에서 그후에 일어나는 출력은 파일의 크기를 확장할 것이다.

 
역자주 : 즉. . . 파일의 끝을 넘어선 곳으로 파일의 위치를 설정하고 다음에 그 위치에서 그 파일에 무언가를 쓴다면 원래의 파일의 끝점과 금방 파일에 쓴 데이터 사이의 빈 공간은 0으로 채워지고 파일은 확장된 결과에 이른다. . . 그말인 듯.

만일 파일의 위치를 변경할 수 없거나, 그 명령이 유용하지 못한 상황이라면, lseek는 -1의 값을 반환한다.

다음의 errno는 이 함수에서 정의한 에러의 상황이다.

    EBADF

    filedes는 유용한 파일 기술자가 아니다.

    EINVAL

    whence인수가 유용하지 않거나, 결과로 얻은 파일 offset이 유용하지 않은 것이다.

    ESPIPE

    filedes가 위치를 설정할 수 없는 파이프나, FIFO에 해당한다. ( 파일의 위치를 설정할 수 없는 다른 파일의 종류도 있겠지만, 그것들의 경우는 정해져 있지 않다. )

lseek 함수는 파일 기술자 대신에 스트림에서 명령을 수행하는 fseek, ftell과 rewind함수를 위해 기본적으로 포함되어져 있다.

당신이 만일 여러 번 같은 파일을 개방하거나, dup을 통해서 기술자를 중복시킨다면 같은 파일에 다중의 기술자를 가질 수 있다. 분리된 open의 호출을 통해서 얻은 기술자는 독립적인 파일 위치를 가진다; 한 기술자에 lseek를 사용하는 것은 다른 것에 아무런 영향이 없다.

    예를 들어,

{
int d1, d2;
char buf[4];
d1 = open ("foo", O_RDONLY);
d2 = open ("foo", O_RDONLY);
lseek (d1, 1024, SEEK_SET);
read (d2, buf, 4);
}

이것은 'foo'파일의 처음 네 개의 문자들을 읽을 것이다. ( 실제의 프로그램에서는 필요한 에러 검색 코드가 여기서는 간결함을 이유로 생략되어져 있다. )

비교해서, dup로 만들어진 중복된 기술자는 보통의 파일 위치도 중복해서 사용한다. 중복된 파일 기술자중 하나가 읽거나 쓰는 동작이 포함된, 파일 위치를 변경하는 어느 동작을 하면, 그들 모두에게 영향이 미친다.

    그래서 예를 들면,

{
int d1, d2, d3;
char buf1[4], buf2[4];
d1 = open ("foo", O_RDONLY);
d2 = dup (d1);
d3 = dup (d2);
lseek (d3, 1024, SEEK_SET);
read (d1, buf1, 4);
read (d2, buf2, 4);
}

이것은 파일 'foo'의 1024번째 문자를 시작으로 해서 네 개의 문자를 읽고, 1028번째 문자를 시작으로 다시 네 개의 문자를 읽는다.

데이터 타입 : off__t

이것은 파일 위치를 나타내기 위한 수치적 데이터 타입니다. GNU시스템에서는 이것은 fpos_t나 long int와 동일하다.

다음 'SEEK_. . . '를 위한 세 개는 오래된 버전의 BSD 시스템들과의 호환을 목적으로 존재한다. 그들은 두 개의 다른 헤더파일에 정의되어 있다: 'fnctl. h'와 'sys'file. h'

L_SET : SEEK_SET의 다른 이름.

L_INCR : SEEK_CUR의 다른 이름.

L_XTND : SEEK_END의 다른 이름.


8. 4 기술자와 스트림

open을 통해 주어진 파일 기술자에, 당신은 fdopen함수를 가지고 그 기술자를 위한 스트림을 만들 수 있다. 당신은 fileno함수를 가지고 현존하는 스트림의 파일 기술자를 얻을 수 있다. 이 함수들은 헤더파일 'stdio. h'에 선언되어 있다.

함수 : FILE * fdopen (int filedes, const char *opentype)

fdopen함수는 파일 기술자 filedes를 위한 새로운 스트림을 반환한다. opentype 인수는 'b'옵션이 허용되지 않는 것을 제외하고는, fopen함수 와 같은 방법으로 사용된다( 7. 3절 [Opening Streams] 참조). 'b' 옵션이 허용되지 않는 것은 GNU 시스템이 텍스트와 바이너리 파일의 차이를 구분하지 않기 때문이다.
 
또한 "w"와 "w+"가 파일을 잘라내는 효과를 내지못한다; 그것은 그 옵션들이 파일을 개방할 때만 오직 영향을 미치는데, 이 경우에는 파일들이 이미 개방되었던 것이기 때문이다. 당신은 opentype인수를 개방한 파일 기술자의 실제 모드와 일치시켜야만 한다.
 
반환 값은 새로운 스트림이다. 만일 스트림이 만들어지지 못한다면, ( 예를 들어, 파일 기술자에 의해 정해진 파일의 모드가 opentype 인수에서 지정한 억세스를 허용하지 않을 때 ), 널 포인터를 반환한다. fdopen함수를 사용하는 예는 10.1절 [Creating a Pipe] 를 참조.

함수 : int fileno (FILE *stream)

이 함수는 스트림 stream과 연관된 파일 기술자를 반환한다. 만일 에러가 검출되거나( 예를 들어, 만일 그 스트림이 유용하지 않다면 ), 만일 스트림이 파일에 입/출력을 할 수 없다면, fileno는 -1을 반환한다. 표준 스트림 stdin, stdout 과 stderr에 속한 파일 기술자를 위한 심볼 상수가 'unistd. h'에 정의되어 있다. ; 7. 2절 [Standare Streams] 참조.

STDIN_FILENO

이 매크로는 표준 입력을 위한 파일 기술자로 0의 값을 가진다.

STDOUT_FILENO

이 매크로는 표준 출력을 위한 파일 기술자로 1의 값을 가진다.

STDERR_FILENO

이 매크로는 표준 에러출력을 위한 파일 기술자로 2의 값을 가진다.


8. 5 스트림과 기술자 혼용의 위험

당신은 동일한 파일과 연결된 다중의 파일 기술자와 스트림을 가질 수 있다( 짧게 스트림과 기술자를 "채널" 이라 부르자), 그러나 당신은 채널들 사이의 혼란을 피하도록 주의해야만 한다. 고려해야 하는 두 가지 경우가 있다: 단일한 파일 위치를 갖고 있는 연결된 채널과, 그들 자신의 파일 위치를 갖고 있는 독립적 채널

모든 억세스가 입력을 위한것임을 제외하고는, 어느 주어진 파일에서 실제의 데이터를 참조하기 위해서는 당신의 프로그램 안에 오직 하나의 채널을 사용하는 것이 좋다. 예를 들어 만일 당신이 파이프를 개방하여, ( 당신이 파일 기술자 수준에서 할 수 있는 어떤 것 ), 기술자를 가지고 입/출력을 하거나, 또는 fdopen으로 기술자로부터 스트림을 구성하고 스트림에 모든 입/출력을 한다면.

8. 5. 1 연결된 채널들

단 한 번 개방된 것으로부터 온 채널들은 동일한 파일 위치를 점유한다; 우리는 그들을 연결된 채널이라 부른다. 연결된 채널은, 당신이 fdopen을 사용하여 기술자로부터 스트림을 만들 때, fileno를 사용해서 스트림으로부터 기술자를 얻을 때, 그리고 dup나 dup2를 사용해서 기술자를 복제할 때 결과로 얻게된다. 터미널과 파이프처럼 랜덤 억세스(임의 접근)가 제공되지 않는 파일들에서 모든 채널들은 실제로 연결되어 있다. 임의 접근이 가능한 파일들에서는 연결된-형태의 출력 스트림들 모두가 서로 연결되어 있다.

만일 당신이 입/출력을 위해 한 스트림을 사용해 오면서, 그것과 연결된 다른 채널(스트림이나 기술자)을 사용해서 입/출력하기 원한다면, 당신은 첫째로 당신이 사용하고 있던 그 스트림을 정리해야만 한다. 8. 5. 3절 [Cleaning Streams] 참조.

프로세스가 종료하거나, 프로세스에 새로운 프로그램을 실행하면 그 프로세스안에 있던 모든 스트림들은 파괴된다. 만일 이 스트림과 연결된 기술자가 다른 프로세스안에 살아남아 있다면, 그들 파일의 위치는 정의되지 않은 상태로 되고 만다. 이것을 방지하기 위해서, 당신은 그들을 파괴하기 전에 그 스트림들을 정리해야만 한다.

8. 5. 2 독립적 채널들

분리되어 탐색 가능한 채널들( 스트림이나 기술자)을 개방할 때, 각 채널들은 자신의 파일 위치를 갖는다. 이들을 독립적 채널이라고 부른다.

시스템은 독립적으로 각 채널을 취급한다. 거의 대부분, 이것은 꽤 예측가능하고, 자연스럽다(특히 입력의 경우): 각 채널은 파일안의 자신의 위치에서 순차적으로 읽거나, 쓸 수 있다. 그렇지만, 만일 어떤 채널들이 스트림이라면, 당신은 이런 것들을 주의해야한다.

당신은 파일의 동일한 부분으로부터 읽거나 쓰는 어떤 것을 하기 전에 사용할 출력 스트림을 깨끗이 해야한다.

독립적인 채널을 사용해서 변경되었을 데이터를 읽기 전에는 입력 스트림을 깨끗이 해야한다. 그렇지 않으면 당신은 스트림의 버퍼에 존재하고 있는 쓸모 없는 데이터(obsolete data)를 읽게 될 것이다.

만일 당신이 파일의 끝에 있는 채널에 출력하려 한다면, 이 채널은 출력을 하기 전에 어느 곳인지에 있을 다른 독립적 채널들을 확실히 무시할 것이다. 만일 당신이 끝에 출력하기 원한다면, 당신은 첫째로, 파일의 끝으로 그들 파일 위치를 설정해야 한다. ( 이것은 연결된-형태의 기술자나 스트림에서는 필요치 않다; 그들은 항상 파일의 끝에서 출력한다. ) 파일의 끝 위치를 정확하게 만들려면, 당신은 만일 당신이 사용하고 있는 것이 스트림이라면 출력 채널을 깨끗이 해야한다. (이것은 심지어 다음에 연결된-형태의 채널을 사용하려 계획할 때도 필요하다. )

파일이 랜덤 억세스를 지원하지 않는다면 파일에서 분리된 파일 위치를 갖고있는 두 개의 채널을 갖는 것은 불가능하다. 그러므로, 파일처럼 읽거나 쓰기 위한 채널들은 항상 연결되어있고, 결코 독립적이지 않다. 연결된-형태의 채널들은 항상 연결되어 있다. 이와 같은 채널들을 위해 연결된 채널들을 위한 규칙을 따라야한다; 8.5.1 [Linked Channels] 참조.

8. 5. 3 채널 깨끗이 하기

GNU 시스템에서, 당신은 fclean을 사용해서 어느 스트림을 정리할 수 있다.

함수 : int fclean (FILE *stream)

버퍼를 비우기 위해 스트림 stream을 깨끗이 한다. 만일 스트림이 출력을 하고 있다면, 강제로 그것을 출력한다. 만일 스트림이 입력을 하고 있다면, 버퍼에 있는 데이터는 그것을 다시 읽도록 조정하는 시스템에 되돌려준다. 다른 시스템들에서는 대부분의 경우 스트림을 깨끗이 하기 위해서 fflush를 사용할 수 있다.
 
만일 당신이 이미 그 스트림이 깨끗하다란 것을 안다면 fclean이나 fflush를 건너뛸 수 있다. 스트림은 버퍼가 비어있을때는 깨끗하다. 예를 들어 비버퍼화된 스트림은 항상 깨끗하다. 그리고, 파일의 끝에서 입력 스트림은 깨끗하다. 라인 버퍼화된 스트림은 마지막 문자출력이 새줄 문자였을 때 깨끗하다.
 
스트림을 깨끗하게 하는 것이 대부분의 시스템에서 불가능한 한가지 경우가 있다. 이것은 스트림이 랜덤 억세스가 불가능한 파일로부터 입력을 하고 있을 때이다. 그와 같은 스트림은 이미 읽은 검색 데이터를 되돌려줄 아무런 방법이 없다. 입력 스트림이 랜덤 억세스 파일로부터 입력할 때, fflush로 스트림을 깨끗이 하지 않으면 파일 포인터가 예측 불가능한 영역에 남겨진고 만다. 당신은 입/출력을 하기 전에 먼저 파일 포인터를 설정하라. GNU 시스템에서는 이 문제들의 양쪽을 피하게 한다.
 
오직 출력만 가능한 스트림을 닫을 때도 fflush를 사용하는데, 이것은 출력 스트림을 깨끗이 하는데 유용한 방법이다. GNU 시스템에서는, 입력 스트림을 닫을 때 fclean을 사용한다.
 
당신은 터미널 모드를 설정하는 것과 같은 제어 명령을 위해 기술자를 사용하기 전에는 스트림을 깨끗이 할 필요가 없다; 이들 명령들은 파일 위치에 영향을 받지 않고, 그것에 영향을 미치지 않는다. 당신은 이 명령들은 위해서는 어느 기술자라도 사용할 수 있고, 모든 채널들은 동시에 영향을 받는다. 그렇지만, 스트림에 의해 새로운 터미널 모드로 설정되어질 스트림이 여전히 "출력"을 갖고 버퍼화된 상태라면 플러쉬 되어진다. "앞으로"의 출력을 확실히 하기 위해 터미널모드의 설정이 동시에 영향을 받도록 되어졌고, 그 모드를 설정하기 전에 터미널의 모든 출력 스트림들을 플러쉬 한다. 12. 4절[Terminal Modes] 참조.


8. 6 입력이나 출력을 위한 기다림

때때로 프로그램들은 입력이 도착할 때마다 다중 입력 채널들로부터 입력을 받아들일 필요가 있다. 예를 들어, 어떤 워크스테이션들은 보통의 비동기적 직렬 인터페이스를 경유하여 연결된, 디지타이징 태블릿 (역자주: 컴퓨터에 좌표 위치를 입력하는 장치), 함수 버튼 박스 (역자주: 버튼들이 모여있는 박스로, 버튼 하나가 한가지 함수를 수행하는 기능을 하는 것. . 일걸요?), 혹은 대화박스(역자주 : 대화상자)와 같은 디바이스들을 갖고 있을 것이다; 좋은 유저 인터페이스의 스타일은 어느 디바이스 상에서 들어온 입력에 대한 즉각적인 대응이 필요하다. 다른 예로써, 파이프나 소켓들을 경유한 여러 가지 다른 프로세스들에게 서버로서 동작하는 프로그램이 있다.

당신은 이러한 목적으로는 read를 사용할 수 없는데, 왜냐하면 프로그램은 어떤 특정한 파일 기술자 상에서 유용한 입력이 있을 때까지 블록 되어지기 때문이다. (역자주 : 여기서 블록의 개념은 입력을 얻지 못해서 아무런 작업도 수행할 수 없는 상태에 처한 것을 말함. ) 다른 채널들에서도 입력을 발생하지 않을 것이다. 당신은 비블럭화 모드로 설정하고 차례로 각 기술자들을 돌아볼 수 있지만, 이것은 매우 비능률적이다.

여기에 대한 훌륭한 해결책으로는 select함수를 사용하는 것이다. 이것은 설정된 기술자들이 입력이나 출력 준비상태가 될 때까지, 또는 설정된 시간이 끝날 때까지 등 이것 중 어떤 것이라도 먼저 될 때까지 프로그램을 블록 시킨다. 이 도구는 헤더파일 'sys/types. h'에 있다.

select 함수를 위해서 파일 기술자를 설정하는 것은 fd_set 오브젝트를 통해서 이루어진다. 다음은 이들 오브젝트들을 다루기 위한 데이터 타입과 매크로 들을 설명한다.

데이터타입 : fd__set

fd_set 데이터 타입은 select함수를 위해서 설정하는 파일 기술자를 나타낸다. 이것은 실제로 비트 배열이다.

매크로 : int FD__SETSIZE

이 매크로의 값은 fd_set 오브젝트가 파일 기술자에 대한 정보를 가질 수 있는 최대의 수이다. 고정된 최대 수를 가진 시스템들에서는, FD_SETSIZE는 적어도 그 개수이다. GNU 시스템을 포함한 다른 시스템에서는 개방하는 기술자의 수에 대한 절대적인 제한이 없지만, 이 매크로는 fd_set의 비트에 개방하는 기술자의 개수를 제어하기 위한 상수 값을 가지고 있다.

매크로 : void FD__ZERO (fd_set *set)

이 매크로는 파일 기술자에 대한 정보를 가지고 있는 set을 빈 공간이 되도록 초기화한다.

매크로 : void FD__SET (int filedes, fd_set *set)

이 매크로는 파일 기술자 정보를 갖고 있는 set에 기술자 filedes를 더한다.

매크로 : void FD__CLR (int filedes, fd_set *set)

이 매크로는 파일 기술자 정보를 갖고 있는 set에서 기술자 filedes를 제거한다.

매크로 : int FD__ISSET (int filedes, fo_set *set)

이 매크로는 만일 filedes가 파일 기술자에 대한 정보를 갖고 있는 set의 멤버라면 0이 아닌 값을 반환하고, 그렇지 않으면 0을 반환한다.

다음은 select 함수에 대한 설명이다.

함수 : int select (int nfds, fd_set *read_fds, fd_set *write_fds, fd_set *except_fds, struct timeval *timeout)

select함수는 정해진 파일 기술자들 중에서 어느 것이 어떤 동작을 하거나, 설정된 시간이 끝날 때까지 호출된 프로세스를 블록 시킨다.
 
read_fds 인수에 의해 정해진 파일 기술자들은 그들이 읽기 위한 준비가 되어있는지 체크되어진다. write_fds 인수로 정해진 파일 기술자들은 그들이 쓰기 위한 준비가 되었는지 체크되어진다. 그리고 except_fds 로 정해진 파일 기술자들은 예외적 상황을 위해 체크되어진다. 만일 당신이 위에 설명된 상황 중에 체크하지 않아도 되는 것에 대한 인수는 널 포인터로 주면 된다. "예외적 상황"은 이미 실행되어지고, 파일 기술자의 상황을 구성하지 않는 잘못된 시스템호출이 있을 때 즉시 보고되어지는 errors_errors를 의미하지 않는다. 오히려, 그들은 소켓에 긴급한 메시지가 나타난 그런 상황을 말한다. ( 11장 [Socket] 에서 긴급한 메시지들에 대한 정보를 참조 )

select 함수는 오직 첫 번째 nfds 파일 기술자를 체크하는데, 보통 이 인수의 값으로 FD_SETSIZE가 주어진다. timeout는 기다리기 위한 최대의 시간을 정한다. 만일 당신이 이 인수로 널 포인터를 준다면, 하나의 파일 기술자라도 준비될 때까지 무기한 블록 시킴을 의미한다. 그렇지 않다면, 당신은 struct timeval 형식 안에 시간의 값을 주어야한다; 17. 2. 2. 절 [High Resolution Calendar] 참조. 시간의 값으로 0을 정하면(모두 0의 값인 struct timeval) 이미 준비된 기술자를 찾거나, 만일 준비된 기술자가 없다면 기다리지 않음을 의미한다.

보통 select 함수로부터의 반환 값은 set안에 지정된 기술자 중에서 준비된 기술자의 총수이다. 인수 set들의 각각은 연관된 명령을 위해서 준비된 기술자에 대한 정보로 갱신된다. 만일 입력을 가진 특별한 기술자 desc를 보려면, select 함수가 반환한 후에 FD_ISSET (desc, read`fds)를 사용하라. 만일 시간 설정 값이 끝났다면 select는 0의 값을 반환한다. 어느 신호는 select가 기다리지 않고 즉시 반환해버리는 원인이 된다. 그래서 만일 당신의 프로그램이 신호들을 사용한다면, 당신은 select가 정해진 완전한 시간동안 기다림을 유지한다고 신뢰할 수 없다. 만일 당신이 어느 정해진 시간동안 확실히 기다림을 원한다면, 당신은 반드시 EINTR을 체크하고 현재의 시간에 기초한 새로이 설정된 시간(timeout)으로 select 을 다시 호출하라. 아래에 있는 예를 보라. 21.5절 [Interrupted Primitives] 참조. 만일 에러가 발생하면, select는 -1을 반환하고, 파일 기술자 셋(sets) 인수를 갱신하지 않는다.

다음 errno는 이 함수에서 정의한 에러 상황이다.

    EBADF

    파일 기술자중에 하나가 유용하지 않은 파일 기술자이다.

    EINTR

    명령이 신호에 의해 인터럽트 되어졌다. 21. 5절 [Interrupted Primitives] 참조.

    EINVAL

    timeout 인수가 유용하지 않다; 구성요소중 하나가 음의 값이거나, 너무 길다.
 
이식성 노트 : select 함수는 BSD Unix 용이다.

아래의 예는 파일 기술자로부터 읽기 위해 timeout시간을 설정하고 어떻게 select를 사용하는지에 대한 것을 보여주고 있다. input_timeout함수는 파일 기술자 상에 유용한 입력이 있을 때까지, 또는 정해진 timeout시간이 끝날 때까지 호출한 프로세스를 블록 시킨다.

 
#include <stdio. h>
#include <unistd. h>
#include <sys/types. h>
#include <sys/time. h>
 
int input_timeout (int filedes, unsigned int seconds)
{
fd_set set;
struct timeval timeout;
/* 파일 기술자 셋을 초기화 */ <--- 일단 파일 기술자 셋을 공백으로 초기화 한 다음 filedes기술자를 더했네요. */
FD_ZERO (&set);
FD_SET (filedes, &set);
 
/* timeout 데이터 구조를 초기화 */
timeout. tv_sec = seconds;
timeout. tv_usec = 0;
 
/* select는 정해진 시간이 끝나면 0을 반환하고, 입력이 있으면 1을 반환하고, 에러가 발생하면 -1을 반환한다. */
return TEMP_FAILURE_RETRY (select (FD_SETSIZE, &set, NULL, NULL,
&timeout));
--> 세 번째, 네 번째 인수가 널이므로 읽기 위한 기술자에서 정해진 시간동안에 입력이 있는지 체크하겠네요.
}
 
int main (void)
{
fprintf (stderr, "select returned %d. \n", input_timeout (STDIN_FILENO, 5));
return 0;
}

다중 소켓으로부터 다중 입력을 위해서는 select를 어떻게 사용하는지에 대한 예제는 11. 8. 7절 [Server Example] 참조.


8. 7 파일에서의 제어 명령들

이 절은 파일 기술자 상에서, 파일 기술자의 상황을 나타내고 있는 플래그를 세팅하거나, 레코드의 락(locks)을 다루는 것과 같은 다양한 명령들을 어떻게 수행할 수 있는지를 설명하고 있다. 이들 명령들은 모두 fcntl함수를 통해 이루어진다.

fcntl함수의 두 번째 인수는 수행하려는 명령으로 지정된 명령문이다. 그 함수와 매크로와 함께 사용되는 다양한 플래그들은 헤더파일 'fcntl. h' 에 선언되어 있다. ( 이들 플래그들의 대부분은 open함수에서 사용되어 지던 것이다; 8. 1절 [Opening and Closing Files] 참조)

함수 : int fcntl (int filedes, int command, . . . )

fcntl 함수는 파일 기술자 filedes 상에서 command에 의해 정해진 명령을 수행한다. 어떤 command들은 공급해야할 부가적 인수를 필요로 한다. 이곳에 각각의 명령들에 따른 부가적 인수들과 반환 값과 에러 상황 각각에 대한 상세한 설명이 있다.

간단히, 아래는 다양한 명령들에 대한 리스트이다.

F_DUPFD

파일 기술자를 복제하라(동일한 개방된 파일을 가리키는 다른 파일 기술자를 반환). 8. 8절 [Duplicating Descriptors] 참조.

F_GETFD

파일기술자와 연관된 플래그들을 얻어라. 8. 9절 [Descriptor Flags] 참조.

F_SETFD

파일 기술자와 연관된 플래그들을 설정하라. 8. 9절 [Descriptor Flags] 참조.

F_GETFL

개방한 파일과 연관된 플래그들을 얻어라. 8. 10절 [File Status Flags4] 참조.

F_SETFL

개방한 파일과 연관된 플래그들을 설정하라. 8. 10절 [File Status Flags] 참조.

F_GETLK

파일 록을 얻어라. 8.11절 [File Locks] 참조.

F_SETLK

파일 록을 설정하거나, 지워라. 8. 11절 [File Locks] 참조.

F_SETLKW

F_SETLK와 같지만, 완전하기까지 기다린다. 8. 11절 [File Locks] 참조.

F_GETOWN

SIGIO 신호들을 받기 위해서 프로세스나 프로세스 그룹 아이디를 얻어라. 8. 12절 [Interrupt Input] 참조.

F_SETOWN

SIGIO 신호들을 받기 위해서 프로세스나 프로세스 그룹 아이디를 설정하라. 8. 12절 [Interrupt Input] 참조.


8. 8 기술자 복제하기

당신은 파일 기술자를 복제하거나, 동일한 파일 기술자를 참조하기 위한 다른 파일기술자를 할당할 수 있다. 복제한 기술자는 하나의 파일 위치를 점유하고, 하나의 파일 상황 플래그들은 갖는다. ( 8. 10절 [File Status Flags] 참조. ), 그러나, 파일 기술자 플래그는 자신의 것을 소유한다. ( 8. 9절 [Descriptor Flags] 참조)

복제한 파일 기술자의 주된 사용처는 입력이나 출력의 리다이렉션(redirection)을 하기 위한 것이다. 그것은 특정한 파일 기술자에 해당하는 파일이나 파이프를 변화시킨다.

당신은 F_DUPFD 명령으로 fcntl 함수를 사용해서 이 동작을 수행할 수 있지만, 복제 기술자를 만들기 위해서는 dup와 dup2라는 편리한 함수가 있다. fcntl 함수와 플래그들은 'fcntl. h'에 선언되어 있고, dup와 dup2는 헤더파일 'unistd. h'에 있다.

함수 : int dup (int old)

이 함수는 이미 있는 기술자 old를 첫 번째 유용한 파일기술자 번호로 복제한다(첫 번째 번호는 현재 개방되지 않았다. ) 이것은 fcntl(old. F_DUPFD, 0)과 동일하다.

함수 : int dup2 (int old, int new)

이 함수는 기술자 번호 new로 old 기술자를 카피한다. 만일 old기술자가 유용하지 못한 기술자라면, dup2는 아무 것도 하지 않는다; new를 폐쇠하지 않는다. 그렇지 않다면, new가 old로 부터 복제되기 전에 이미 전에 존재하고 있던 기술자라면, new를 일단 먼저 폐쇄시켜야 한다. 만일 old와 new가 서로 다른 번호이고, old가 유용한 기술자라면, dup2는 다음과 동일하다:
 
close (new);
fcntl (old, F_DUPFD, new)
 
그렇지만, dup2는 이것을 자동적으로 한다. new가 폐쇄되어 있고, 아직 old가 복제되지 않았을 때, 호출된 dup2의 도중에는 아무런 걸림돌이 없다.

매크로 : int F__DUPFD

이 매크로는 첫 번째 인수로서 주어진 파일 기술자를 카피하기 위해 , fcntl에게 주는 명령어 인수로서 사용된다. 이 경우 호출 형식은 다음과 같다:

fcntl (old, F_DUPFD, next_filedes)

next_filedes 인수는 int 형이고, 반환될 다음에 유용한 기술자를 정하는데 사용되고, 그 기술자는 이 값보다 하나 크거나, 같은 것이다. 이 명령을 사용한 fcntl로 부터의 반환 값은 보통 new 파일 기술자의 값이다. -1의 반환 값은 에러가 발생했음을 나타낸다.
 
다음의 errono는 이 명령을 위하여 정의된 에러 상황이다.

EBADF

old 인수가 유용하지 않다.

EINVAL

next_filedes 인수가 유용하지 않다.

EMFILE

당신의 프로그램이 이미 최대한 사용하고 있기 때문에 더 이상 유용한 파일 기술자가 없다.

ENFILE

dup2가 파일의 개방으로 new를 만드는 것이 아니기 때문에 dup2에서는 발생할 수 있는 에러코드가 아니다; 복제한 기술자는 ENFILE이 지적한 제한을 미리 체크하지 않는다. EMFILE는 한 프로세스에서 사용중인 별개의 기술자 번호에 대한 제한을 참조하기 때문에 가능하다.

다음의 예는 리다이렉션을 하기 위해 dup2를 어떻게 사용하는지에 대한 것이다. 특별히, 표준 스트림(stdin 처럼)의 리다이렉션은 자식 프로세스안에서 새로운 프로그램을 수행하기 위해서 수행 함수들의 ( 23. 5절 [Executing a File] 참조 ) 수행 함수들중 하나를 호출하기 전에 쉘이나 쉘과 같은 프로그램에 의해 행해진다. 새로운 프로그램이 수행되어질 때, 그 프로그램의 메인 함수가 수행되기 전에 연관된 파일 기술자들을 가리키는 표준 스트림을 초기화한다. 그래서 파일로 표준 입력을 리다이렉트 하는 것은 쉘이 다음과 같은 어떤 것을 하는 것이다.

pid = fork ();
if (pid == 0)
{
char *filename;
char *program;
int file;
. . .
file = TEMP_FAILURE_RETRY (open (filename, O_RDONLY));
dup2 (file, STDIN_FILENO);
TEMP_FAILURE_RETRY (close (file));
execv (program, NULL);
}

24. 6. 3절 [Launching Jobs] 에 프로세스의 파이프라인에 어떻게 리다이렉션을 하는지에 대한 상세한 많은 예제가 있다.


8. 9 파일 기술자 플래그

파일 기술자 플래그는 파일 기술자의 잡다한 속성들이다. 이 플래그들은 특별한 파일 기술자와 연관되어 있기 때문에, 만일 당신이 한 번 개방한 파일로부터 복제해서 파일 기술자를 만들었다면, 각 기술자는 자신의 플래그 셋(set)을 갖는다.

현재 단지 하나의 파일 기술자 플래그가 있다: FD_CLOEXEC, 이것은 만일 당신이 exec. . . 함수(23. 5절 [Executing a File])들 중 어느 하나를 사용한다면 기술자가 닫히게 되는 결과를 가져온다.

이 절에서 설명하고 있는 것은 헤더파일 'fcntl. h'에 정의되어 있다.

매크로 : int F__GETFD

이 매크로는 fcntl의 command 인수로 사용되어, filedes 인수와 연관된 파일 기술자 플래그를 반환하도록 한다. 이 명령을 사용한 fcntl의 반환 값은 보통 음이 아닌 수로써, 각각의 플래그( 현재 그곳에서 사용하기 위한 오직 하나의 플래그를 제외하고)들을 비트별 OR 연산을 통해 해석 가능하도록 추출된 값이다. 에러가 발생한 경우에, fcntl은 -1을 반환한다.
다음 errno는 이 명령에서 정의한 에러의 상황이다.

EBADF

filedes인수가 유용하지 않다.

매크로 : int F__SETFD

이 매크로는 filedes 인수와 연관된 파일 기술자 플래그들을 설정함을 지시하기 위해 fcntl함수의 command인수로 사용되어진다. 이것은 새로운 플래그들을 정하기 위해서 세 번째 int 형 인수가 필요하므로, 다음과 같은 형식으로 호출한다:
 
fcntl (filedes, F_SETFD, new_flags)
이 명령과 함께 사용된 fcntl로 부터의 반환 값은 보통 정의되어있지 않지만, 에러가 발생한 경우에는 -1을 반환하여 에러임을 지적한다. 플래그들과 에러발생 상황은 F_GETFD 명령과 같다. 다음 매크로는 fcntl함수와 함께 파일 기술자 플래그로써 사용하기 위해 정의되었다. 이 값은 비트 마스크 값으로써 사용할 수 있는 정수 상수 값이다.

매크로 : int FD__CLOEXEC

이 플래그는 exec 함수가 불리워질 때 닫혀질 파일 기술자를 정한다; 23. 5절 [Executing a File] 를 참조하라. 파일 기술자가 할당되었을 때( open이나 dup를 통해서), 이 비트가 새로운 파일 기술자 상에서 클리어 되면 다른 새로운 프로그램에서 그 기술자를 사용할 수 있다.
 

만일 당신이 파일 기술자 플래그들을 갱신하기 원한다면, 당신은 F_GETFD를 통해서 현재의 플래그들은 얻고, 그 값을 갱신하라. 이곳에서 보여준 플래그들이 각각의 플래그 하나만 사용된다고 생각하지 말라; 당신의 프로그램은 지금으로부터 수년간 실행되어질 것이고 그러면 더 많은 플래그들이 존재하게 될 것이다. 예를 들어, 이곳에 있는 함수는 다른 플래그들을 변경하지 않고 FD_CLOEXEC 플래그만 클리어 하거나 설정하는 함수이다.

/* 만일 value가 0이 아닌 값이면 desc의 FD_CLOEXEC 플래그를 설정하고, value가 0이면 클리어 하라. 성공하면 0을, 실패하면 -1을 반환한다. */

int set_cloexec_flag (int desc, int value)
{
int oldflags = fcntl (desc, F_GETFD, 0);
/* 만일 플래그를 읽는 것이 실패하면, 에러임을 지적하라. */
if (oldflags < 0)
return oldflags; /* 우리가 원하는 값으로 플래그를 설정하라. */
if (value != 0)
oldflags |= FD_CLOEXEC;
else
oldflags &= ~FD_CLOEXEC; /* 기술자에 갱신된 플래그 워드를 저장하라 */
return fcntl (desc, F_SETFD, oldflags);
}


8. 10 파일 상황 플래그들

파일 상황 플래그들은 개방한 파일의 속성을 정하기 위해 사용되어진다. 8. 9절 [Descriptor Flags] 에서 논의된 파일 기술자 플래그들과 달리, 파일 상황 플래그들은 개방된 파일로부터 복제된 파일 기술자에 의해 공유되어진다.

파일 상황 플래그들은 open 함수의 flags인수를 통해 초기화되어진다. 플래그중 어떤 것은 개방할 때 의미가 있고 계속해서 기억되지 않는다; 나머지 대대 분은 그후에 변화되지 않아서, 당신은 파일 상황 플래그들을 시험함으로 인해서 그들의 값을 읽을 수 있다. 몇몇 파일 상황 플래그들은 fcntl 함수를 사용해서 변화시킬 수 있다. 그들에 O_APPEND 와 O_NONBLOCK 가 포함된다. 이 절에 있는 심볼들은 헤더파일 'fcntl. h'에 있다.

매크로 : int F__GETFL

이 매크로는 기술자 filedes로 개방한 파일의 파일 상황 플래그들은 읽기 위해 fcntl함수의 command 인수로써 사용된다. 이 명령과 함께 사용된 fcntl을 통한 반환 값은 보통 각각의 플래그들을 비트별 OR연산을 통해서 해석 가능한 값으로 추출된 음이 아닌 값이다. 그 플래그들은 open(8. 1절 [Opening and Closing Files] 참조) 함수의 flags 인수처럼 부호화 되었지만, 오직 파일 검색 모드와 O_APPEND 와 O_NONBLOCK 플래그들만 이곳에서 의미가 있다. 파일 억세스 모드들이 단일한-비트 값들이 아니기 때문에, 당신은 그들과 비교하기 위해서 O_ACCMODE을 사용하여 반환된 플래그들에서 다른 비트들을 마스크 시켜서 원하는 비트를 추출할 수 있다. 에러가 발생하면
 
fcntl은 -1을 반환한다. 다음의 errno는 이 명령에서 정의한 에러상황이다.

매크로 : EBADF

filedes 인수가 부적합하다.

매크로 : int F__SETFL

이 매크로는 filedes 인수와 연관된 개방파일의 파일 상황 플래그를 설정하기 위해서, fcntl 에서 command 인수로써 사용되어진다. 이 명령은 새로운 플래그를 설정하기 위해서 세 번째에 int 형 인수를 필요로 한다. 그래서 이것은 다음처럼 호출한다:
fcntl (filedes, F_SETFL, new_flags)
당신은 이 방법으로 파일 엑세스 모드를 변경할 수 있다; 읽거나 쓰기 위하여 개방되어진 파일 기술자든지 간에. 당신은 오직 O_APPEND와 O_NONBLOCK 플래그들을 변경할 수 있다. 이 명령을 사용한 fcntl로부터 반환된 값은 지정된 값이 아니지만, 에러가 발생한 경우에 -1을 반환한다. 에러의 발생 상황은 F_GETFL 명령과 같다.

다음 매크로들은 파일 상황 플래그 값들을 분석하고 구성하기 위해 정의되어졌다.

O_APPEND

파일 덧붙이기가 가능한 모드로 만들기 위한 비트이다. 만일 이 비트가 설정되면, 모든 쓰기(write) 명령들은 파일의 끝에서 쓰기가 이루어지고, 현재 파일의 위치가 무시된다.

O_NONBLOCK

파일을 비블록화 모드로 만들기 위한 비트이다. 만일 이 비트가 설정되면, 파일에서 읽기(read) 요청은 즉시 유용한 입력이 없다면 기다리지 않고 즉시 반환한다. 마찬가지로, 쓰기(write) 요청 또한 즉시 쓰기 가능한 출력이 없다면 즉시 반환하여 쓰기 요청은 실패한다.

O_NDELAY

이것은 O_NONBLOCK의 유사어로써, BSD와의 호환성을 위해 제공된다.

매크로 : int O__ACCMODE

이 매크로는 파일 검색모드를 나타내는 값을 알아내기 위해 파일 상황플래그와 비트별 AND를 시키는 마스크이다. 모드에는 O_RDONLY, O_WRONLY, 또 O_RDWR이 있다.

O_RDONLY

읽기 모드로 파일을 개방.

O_WRONLY

쓰기 모드로 파일을 개방.

O_RDWR

읽기 쓰기 모두 가능하게 파일을 개방.

만일 당신이 파일 상황 플래그를 갱신하기 원한다면, 당신은 F_GETFL을 사용해서 현재의 플래그들은 얻어서 그 값을 갱신하라. 이곳에서 보여준 플래그들이 오직 단독으로 존재한다고 가정하지 말아라; 당신의 프로그램은 지금부터 수년간 실행되어질 것이고 그러면 더 많은 플래그들은 존재할 것이다. 예를 들어, 이곳에 다른 플래그들을 변경함이 없이 오직 O_NONBLOCK 플래그만 설정하고, 클리어 하는 함수가 있다.

/* 만일 value 값이 0이 아닌 값이면 desc의 O_NONBLOCK 플래그를 설정하고, value의 값이 0이면 그 플래그를 클리어 하라. 성공하면 0을 반환하고, 에러가 발생하면 -1을 반환하라 */

int
set_nonblock_flag (int desc, int value)
{
int oldflags = fcntl (desc, F_GETFL, 0);
/* 만일 읽기 플래그들이 실패하면, 즉시 에러임을 지적하라. */
if (oldflags < 0)
return oldflags; /* 우리가 원하는 플래그를 설정하라 */
if (value != 0)
oldflags |= O_NONBLOCK;
else
oldflags &= ~O_NONBLOCK; /* 기술자에 갱신된 플래그 워드를 저장하라 */
return fcntl (desc, F_SETFL, oldflags);
}


8. 11 파일 록

위에서 설명한 것 외에 fcntl 에서 쓸 수 있는 명령은 레코드 락킹(locking)을 지원하기 위해 사용되어진다. 레코드 락킹은 다중 사용자 프로그램에서 동시에 파일의 같은 부분을 억세스 하는 것을 방지하기 위한 것이다.

배타적이거나 쓰기(write) 락(lock)은 프로세스가 파일의 지정한 부분에서 배타적인 동작으로 쓰기 위해서 주어진다. 쓰기 락이 있는 곳에서는 다른 프로세스들은 파일의 그 부분에 락을 걸 수가 없다.

분배하거나 읽기(read) 락(lock)은 파일의 지정된 부분을 읽고 있을 때 다른 프로세스로부터의 쓰기 락(write lock) 요청을 거부한다. 그렇지만 다른 프로세스가 읽기 락(read lock)을 요청하는 것은 가능하다.

read와 write 함수들은 실제로 그 위치에 락(lock)이 걸린 건지를 체크하지 않는다. 만일 당신이 다중 프로세스에 의해 점유된 파일을 위해 락킹(locking) 프로토콜(protocol)을 사용하기 원한다면, 당신의 어플리케이션은 적당한 지점에서 락(lock)을 요청하거나 클리어 하기 위해서 fcntl 함수를 호출해야만 한다.

락들은 프로세스들과 연관되어 있다. 한 프로세스는 주어진 파일의 각 바이트에 한 종류만의 락(lock) 설정을 할 수 있다. 파일기술자가 프로세스에 의해 닫혀질 때, 그 파일에서 프로세스가 갖고 있던 모든 락들은 해제된다, 심지어 현재 개방 상태인 다른 기술자를 사용해서 만들어진 락일지라도. . . 마찬가지로, 프로세스가 존재하고 있을 때 해제된 락들은, fork(23. 4절 [Creating a Process] 참조)를 사용해서 만들어진 자식 프로세스에게 물려줄 수 없다.

락을 만들 때, 락이 어떤 종류이고 현재 어디에 있는지를 알리기 위해 구조체 struct flock을 사용한다. 이 데이터 타입과 fcntl과 연관된 매크로는 헤더파일 'fcntl. h'에 선언되어 있다.

데이터타입 : struct flock

이 구조체는 파일락을 설명하기 위해 fcntl함수에서 사용되어진다. 이들 멤버는 다음과 같다.

short int l_type

락의 타입을 정한다. F_RDLCK, FWRLCK, 또는 F_UNLCK 중에 하나.

short int l_whence

이것은 fseek 나 lseek 함수의 인수인 whence 와 일치하고, 그것을 기점으로 offset을 정한다. SEEK_SET, SEEK_CUR, SEEK_END 중 하나.

off_t l_start

이것은 락이 적용되는 지역의 시작 offset을 지정하고, l_whence 멤버에 연관한 지점이 바이트로 주어진다.

off_t l_len

이것은 락이 설정된 지역의 길이를 지정한다. 0의 값은 특별히 취급된다; 그것은 파일의 끝에서 락의 범위를 확장하는걸 의미한다.

pid_t l_pid

이 필드는 락을 갖고 있는 프로세스의 프로세스 ID (23. 2절 [Process Creation Concepts] 참조)이다. F_GETLK 명령을 사용해서 fcntl 함수를 호출하면 이 필드가 채워지지만, 락을 만들 때는 무시된다.

매크로 : int F__GETLK

이 매크로는 락에 대한 정보를 얻고자 함을 알리기 위해, fcntl에게 command 인수로써 사용된다. 이 명령은 fcntl에게 주기 위해 struct flock * 타입의 세 번째 인수가 필요하다. 그래서 다음과 같은 형식으로 호출한다:

fcntl (filedes, F_GETLK, lockp)

만일 lockp 인수에 의해 묘사된 락이 설정된 블록의 영역에 이미 락이 존재한다면, 그 락에 대한 정보는 *lockp에 다시 쓰여 진다. 이미 존재하고 있는 락이 새로 만들고 있는 락과 호환성이 있다면 보고되지 않는다. 그래서, 만일 당신이 읽기와 쓰기 락들에 대해서 찾기를 원한다면 F_WRLK의 타입으로 락을 지정하거나, 만일 당신이 쓰기 락에 대한 것을 찾고 싶다면 F_RDLCK의 타입으로 락을 지정해야 한다.
 
lockp 인수에 의해 지정된 지역에는 한 개보다 더 많은 락들이 있겠지만, fcntl은 오직 그들중 하나에 대한 정보를 반환한다. lockp 구조체중 l_whence 멤버는 SEEK_SET으로 설정되고 l_start 와 l_len 필드들은 락이 걸린 지역과 동일하게 설정된다. 만일 아무 것도 락이 걸리지 않았다면, lockp 구조체에 유일하게 가해지는 변화는 F_UNLCK의 값으로 l_type이 갱신되는 것이다. 이 명령을 사용한 fcntl 으로부터의 반환 값은 보통 정해지지 않았고, 에러가 발생한 경우에는 -1을 반환한다.
 
다음 errno는 이 명령에서 정의한 에러상황이다.

EBADF filedes 인수가 유용하지 않다.

EINVAL lockp

인수가 유용한 락 정보를 가지고 있지 않거나, 파일이 락이 지원되지 않는 filedes와 연관이 있다.

매크로 : int F__SETLK

이 매크로는 락을 설정하거나, 클리어 할 것임을 지정하기 위해 fcntl 함수에서 command 인수로 사용되어진다. 이 명령은 fcntl 함수에 주기 위한 세 번째 인수로써 struct flock * 형의 인수를 필요로 한다. 그래서 다음과 같이 호출한다.

fcntl (filedes, F_SETLK, lockp)

만일 프로세스가 락을 설정할 지역에서 이미 어느 한 부분이 락을 가지고 있다면, 이미 있던 락은 새로운 락으로 대체된다. 당신은 F_UNLCK의 타입으로 락을 지정하여서 락을 제거할 수도 있다. 만일 락이 설정되지 않았다면, fcntl은 -1의 값을 즉시 반환한다. 이 함수는 락을 해제하기 위해 다른 프로세스들을 기다리는 블록을 하지 않는다. 만일 fcntl 이 성공하면, -1이 아닌 다른 값을 반환한다.
 
다음 errno는 이 함수에서 정의한 에러 상황이다.

EACCES

EAGAIN 파일에 존재하고 있는 락으로 블록 되어졌기 때문에 그 락을 설정할 수 없다. 어떤 시스템에서 이 경우에 EAGAIN을 사용하고, 다른 시스템에서는 EACCES를 사용한다; 당신의 프로그램은 F_SETLK 후 처럼 그들을 취급할 것이다.

EBADF

filedes 인수가 유용하지 않거나; 당신이 읽기 락을 요청했지만 filedes는 읽기 모드로 개방되지 않았거나, 또는 쓰기 락을 요청했는데, 그 filedes 는 쓰기 모드로 개방되지 않았거나; 이 둘 중에 하나이다.

EINVAL

lockp인수가 유용한 락 정보를 지정하지 않았거나, 또는 filedes와 연관된 파일이 락을 지원하지 않거나이다.

ENOLCK

시스템의 파일 락 자원들이 바닥났다; 이미 너무 많은 파일 락들이 그곳에 존재하고 있다. 잘-만들어진 파일시스템들은 결코 이 에러를 발생시키지 않는데, 그 이유는 그들이 락의 수에 대한 아무런 제한을 가지고 있지 않기 때문이다. 그렇지만, 다른 컴퓨터(machine)의 파일시스템을 네트웍으로부터 검색하는 것에서 발생할 수 있는 결과와 같이, 당신은 이처럼 이 에러가 발생할 가능성으로 인해서 여전히 락의 개수를 세어야만 한다.

매크로 : int F__SETLKW

이 매크로는 락을 설정하거나, 설정된 락을 지울 것임을 지정하기 위해서, fcntl 함수에 command 인수로서 사용되어진다. 이것은 F_SETLK 명령과 같지만, 그 요청이 받아들여 질 때까지 프로세스를 블록(또는 기다림)하게 한다. 이 명령은 F_SETLK 명령처럼 세 번째 인수로서 struct flock * 타입의 인수를 필요로 한다. fcntl의 반환 값과 에러들은 F_SETLK 명령을 위한 것과 동일하지만, 이것들에 더해진 에러 상황들이 밑에 설명되어 있다.

EINTR

이 함수는 기다리는 동안 신호에 의해 인터럽트 되어졌다. 21. 5절 [Interrrpted Primitives] 참조.

EDEADLK

교착상태(역자주: 다중 프로그래밍 시스템에서 발견될 수 있는 상황으로 프로세스들이 서로 작업을 진행하지 못하고 영원히 대기 상태로 빠지게 되는 현상을 교착 상태라 하는데, 보통 프로세스 사이에 할당된 자원의 충돌로 인하여 발생한다. )가 검출되어졌다. 이것은 만일 이미 자신이 제어하고 있는 락된 지역(locked region)을 갖고 있는 두 개의 프로세스가 다른 프로세스에 의해 락된 동일한 지역을 요청했을 때 발생할 수 있다.

다음 매크로는 flock 구조체의 1_type 멤버를 위한 값들로 사용되어지기 위해서 정의되어졌다. 이 값들은 정수 상수이다.

    F_RDLCK 이 매크로는 읽기(또는 분배) 락을 지정하기 위해 사용된다.

    F_WRLCK 이 매크로는 쓰기(또는 배타적) 락을 지정하기 위해 사용된다.

    F_UNLCK

      이 매크로는 그 지역에 락이 걸리지 않도록 지정하기 위해 사용되어진다. 파일 락킹의 쓰임새에 대한 하나의 예로써, 만일 동시에 여러 다른 사용자들에 의해 실행되어질 수 프로그램을 생각해보라. 그와 같은 프로그램은 높은 점수를 기록하기 위해 한 파일을 사용하는 게임프로그램이 될 수 있고. 또 다른 예는 계산의 목적을 위한 정보로 사용하는 레코드를 갖는 프로그램이 되어질 것이다.

파일에 쓰기 위하여 동시에 여러 개의 프로그램 복제본을 갖는다면, 그 파일의 내용은 나중에 엉망진창이 되어질 것이다. 그러나 당신은 파일에 쓰기가 실제로 발생하기 전에 그 파일에 쓰기락을 설정해서 그와 같은 문제들의 발생을 예방할 수 있다.

만일 프로그램이 그 파일을 읽을 필요가 있고 그 파일의 내용이 확실하기를 원한다면, 그때 읽기 락을 사용할 수 있다. 읽기 락이 설정된 동안 어떤 다른 프로세스도 쓰기 위해서 그 부분에 락을 설정하려면 기다려야 한다.

파일 락은 파일을 억세스 하는 것을 제어하기 위한 자발적 프로토콜임을 기억하라. 그래서 락 프로토콜을 사용하지 않는 프로그램에 의해 그 파일을 억세스할 가능성은 여전히 있다.


8. 12 인터럽트로 조종된 입력

만일 당신이 파일 기술자상의 FASYNC 상황 플래그를 설정하면( 8. 10절[File Status Flags] 참조 ), SIGIO 신호는 파일 기술자 상에서 입력이나 출력이 가능하게 될 때마다 보내어진다. 그 신호를 받을 프로세스나 프로세스 그룹은 fcntl 함수에 F_SETOWN 명령을 사용해서 선택할 수 있다. 만일 그 파일 기술자가 소켓이라면, 이것은 소켓에 도착한 범위 밖의 데이터가 있을 때, 그때 배달된 SIGURG 신호를 받을 수령자를 선택한다; 11. 8. 8 [Out-of-Band Data] 참조.

만일 그 파일 기술자가 터미널 디바이스와 교신한다면, SIGIO 신호는 터미널의 전면(foreground) 프로세스 그룹에 보내어진다. 24장 [Job Control] 참조.

이 절안의 심볼들은 헤더파일 'fcntl. h'에 정의되어 있다.

매크로 : int F__GETOWN

이 매크로는 SIGIO 신호를 보낸 프로세스나 프로세스 그룹에 대한 정보를 얻음을 정하기 위해 fcntl 함수의 command 인수로서 사용되어진다. (터미널에서, 이것은 실제로는 전면(foreground) 프로세스 그룹 아이디(ID)를 tcgetpgrp을 사용해서 얻을 수 있다; 24. 7. 3절 [Terminal Access Functions] 참조. ) 반환 값은 프로세스 ID로 인터럽트 되어지고 만일 음의 값이면, 그 절대값이 프로세스 그룹 ID 이다.
 
다음의 errno는 이 명령에서 정의한 에러 상황이다.
EBADF : filedes 인수가 유용하지 않다.

매크로 : int F__SETOWN

이 매크로는 SIGIO 신호를 보내기 위한 프로세스나 프로세스 그룹 ID를 설정함을 정하기 위해 fcntl 함수의 command 인수로서 사용되어진다. 이 명령은 fcntl 함수의 세 번째 인수로 pid_t 타입의 인수를 필요로 한다, 그래서 다음과 같은 형식으로 호출된다:

fcntl (filedes, F_SETOWN, pid)

pid 인수는 프로세스 그룹 ID이다. 당신이 그 값을 음의 값으로 주더라도 그 절대값이 프로세스 그룹 ID가 되어진다. 이 명령을 사용한 fcntl함수로부터의 반환 값은 에러가 발생된 경우에 -1이고, 성공하면 어떤 다른값이 된다.
 
다음의 errno는 이 명령에서 정의한 에러상황이다.
EBADF : filedes 인수가 유용하지 않다.
ESRCH : pid에 해당하는 아무런 프로세스나 프로세스 그룹이 없다.


목차 이전 : 7. 스트림에서의 입출력 다음 : 9. 파일 시스템 인터페이스

'Programming > Unix Programing' 카테고리의 다른 글

10. Pipes and FIFO  (0) 2007.12.22
9. 파일시스템 인터페이스  (0) 2007.12.22
8. 저수준 입출력  (0) 2007.12.22
7. 스트림의 입출력  (0) 2007.12.22
6. 입출력 개괄  (0) 2007.12.22
5. 문자열과 배열 유틸리티  (0) 2007.12.22
Comment 0 Trackback 0
Top

7. 스트림의 입출력

목차 이전 : 6. 입출력 개요 다음 : 8. 저수준 입출력


7 스트림에서의 입/출력

이 장은 스트림을 만들기 위한 함수와 그리고 그들에게 입력과 출력을 수행하기 위한 함수를 설명하고 있다. 6장[I/O Overview] 에서 논의했던 것처럼 스트림은 꽤 추상적이고, 파일, 디바이스, 또는 프로세스의 통신채널을 나타내는 고 수준의 개념이다.


7. 1 스트림

역사적인 이유로, 스트림으로 표현되는 C의 자료구조 타입은 "스트림"보다는 FILE로 불려졌다. FILE * 타입으로 object들을 다루는 라이브러리 함수들의 대부분은 term file pointer를 스트림의 의미로 또한 사용하고 있다. 이 전문어를 넘어선 적절하지 못한 혼란은 많은 C관련 책들이 선도하고 있다. 하지만 이 매뉴얼은 "file"과 "stream"의 사용에 주의를 기울일 것이다. 오직 기술적 분별 안에서. . ( 여기서 term file pointer는 어떤 단위를( 한줄, 혹은 한 레코드, 한 문자, 한 단어 등등. . ) 포인팅 하고 있는 포인터를 말하는 듯. . . )

FILE 타입은 헤더파일 'stdio. h'에 선언되어 있다.

데이터타입: FILE

이것은 스트림 object들을 나타내기 위해 사용되는 데이터형이다. FILE object는 파일 위치를 지적하고, 정보를 버퍼링 하는 것과 같은 일들을 포함한, 파일과 연관된 모든 내부적 정보를 갖고 있다. 각 스트림은 또한 ferror와 feof를 사용해서 검사할 수 있도록 에러와 파일끝에 대한 정보를 갖고 있다. 7. 13절 [EOF and Errors] 참조.
 
FILE object들은 입/출력 라이브러리 함수들에 의해 내부적으로 할당되고, 관리된다. 당신이 FILE이라는 형으로 object를 창조하려고 시도하지 마라. ; 그것을 라이브러리가 하도록 내버려 두라. 당신의 프로그램들은 오직 FILE objects를 직접 다루기보다는 포인터를 이용하여 다루도록 해야한다. ( 그것은 FILE * 과 같다. )


7. 2 표준 스트림

당신의 프로그램의 메인 함수가 불려질 때, 그곳에는 이미 열기도 하고 사용도 할 수 있는, 미리 선언된 3개의 스트림을 갖게된다. 이것은 프로세스를 위해 만들어놓은 "표준"의 입/출력을 표현하기 위함이다.

이 스트림들은 헤더파일 'stdio. h'에 선언되어 있다.

변수 : FILE * stdin

이 표준 입력 스트림은 프로그램의 입력을 위한 표준의 자원(source)이다.

변수 : FILE * stdout

이 표준 출력 스트림은 표준의 입력을 위해 사용된다.

변수 : FILE *stderr

이 표준 에러 스트림은 프로그램의 에러 발생에 대한 메시지와 진단에 대한 조언을 위해 사용된다.

GNU 시스템에서 당신이 정한 어떤 파일이나 프로세스도 쉘에서 제공하는 파이프나 리다이렉션을 사용해서 이 스트림들과(표준 입/출력, 에러 스트림) 통할 수 있다. ( 이 기능들을 도구로 사용하는 원시적 쉘은 9장[File System Interface] 에 설명되어 있다. ) 대부분의 다른 운영체제는 이와 비슷한 메커니즘을 제공하지만 이들을 어떻게 사용하는 지에 대한 세밀한 부분에서는 많은 차이가 있을 수 있다. GNU C 라이브러리에서는 stdin, dtdout과 stderr은 다른 어느 변수들처럼 당신이 정할 수 있는 보통의 변수이다. 예를 들어 표준출력에서 파일로 리다이렉션 하기 위해서는 다음과 같이 할 수 있다.

fclose (stdout);
stdout = fopen ("standard-output-file", "w");

뿐만 아니라, 다른 시스템에서는 stdin, stdout과 stderr은 보통의 방법으로 지정할 수 없는 매크로란 걸 기억하라. 그러나 당신은 하나를 닫고, 다시 여는 효과를 얻기 위해 freopen을 사용할 수 있다. 그것은 7. 3절 [Opening Streams] 참조.


7. 3 스트림 열기

하나의 파일을 fopen함수를 사용하여 여는 것은 새로운 스트림을 만들고 파일과 스트림사이의 연결을 만드는 것이다. 이것은 새로운 파일을 만드는 것도 포함된다.

이 절에서 설명된 모든 것은 헤더파일 'stdio. h'에 선언되어 있다.

함수: FILE * fopen (const char *filename, const char *opentype)

fopen함수는 입출력을 위해 파일이름으로 파일을 열고 스트림에게 포인터를 반환한다. 여기서 opentype 인수는 문자열로서 어떻게 열려진 파일을 제어하고 결과물로 얻어진 스트림의 속성을 지정하는 역할을 한다. 그것은 반드시 아래와 같은 문자들 중 하나로 시작해야한 만다.

'r'

오직 읽기 모드로 존재하는 파일을 열 때 사용

'w'

오직 쓰기 모드로 파일을 열 때, 만약 이미 그 파일이 존재한다면, 이미 존재하고 있는 것을 없애고 새로운 파일을 만드는 것과 같은 효과를 낸다.

'a'

덧붙임을 위해 파일을 열 때 사용하라. 그럴 때 오직 파일의 끝에만 쓰여 진다. 만약 파일이 이미 존재한다면 그 안에 이미 존재하고 있는 내용은 변하지 않고 스트림이 그 파일의 끝에 덧분여진 것 과 같은 결과를 낳는다. 그러나 파일이 없을 때는 새로운 파일이 만들어진다.

'r+'

이미 존재하는 파일을 읽거나 쓰는 작업을 동시에 하고자 할 때 사용한다. 그 파일 안에 이미 존재하고 있는 내용은 변하지 않고 파일 포인터는 파일의 처음에 존재하여 처음부터 파일을 읽고, 쓸 수 있도록 한다.

'w+'

쓰기와 읽기를 동시에 하고자 할 때 사용한다. 하지만 만약 파일이 이미 존재한다면 그 안에 있는 내용은 무시되고, 존재하는 파일이 없다면 새로운 파일을 만든다.

'a+'

읽고, 덧붙이는 일을 동시에 하는 파일을 열거나 만들 때 사용한다. 만약 그 파일이 이미 존재한다면 이미 있는 내용은 변화되지 않고, 존재하는 파일이 없으면 새로운 파일이 만들어진다. 읽기 위해 사용 할 때 파일의 처음 위치는 파일의 시작점이지만 출력은 항상 파일의 뒤에 덧붙여진다.

'+'기호

입력과 출력을 동시에 할 수 있도록 스트림에게 요구할 때 사용한다. ANSI에서는 그와 같은 스트림을 사용하여 읽기에서 쓰기 모드로 변할 때, 혹은 그 반대의 경우일 때, 반드시 fflush함수를 호출하거나, fseek처럼 파일의 위치를 정하는 함수를 사용하기를 권장한다. 왜냐하면 내부적인 버퍼가 아마도 비어있지 않을 것이므로. GNU C 라이브러리는 이러한 제한을 가지고 있지 않는다; 당신은 그 스트림에게 언제든지 쓰기나 읽기 명령을 마음대로 할 수 있다. (fflush에 대한 것은 7. 17절 [Stream Buffering]를 참조. fseek에 대한 것은 7. 15절 [File Positioning]를 참조하라. )

GNU C 라이브러리에서는 읽기 위한 모드로 하나를 더 선언해 놓고 있다. 그것은 문자'x'로써 이것은 만약 이미 존재하는 파일이 있다면 그 파일을 그냥 여는 것이 아니라 아예 fopen함수가 실패하는 것으로 끝남으로써 새로운 파일을 연다는 사실을 강조할 때 사용한다. 이것은 open 함수를 사용할 때 쓰이는 O_EXCL 옵션과 동일한 동작을 한다.

열기모드중의 하나인 문자 'b'는 표준의 c안에 있고, 텍스트 형태의 스트림이 아니라 바이너리 형태의 스트림을 요구할 때 사용한다. 그러나 이것은 POSIX 시스템들에게는 아무런 차이가 없다( GNU시스템을 포함하여 ) 만약 '+'와 'b'를 둘다 쓰면 어느 한쪽의 명령으로 나올 수 있다. 7. 14절 [Binary Streams] 참조.

어느 열기 모드안의 문자들을 간단히 무시된다. 그들을 다른 시스템에서만 의미가 있을 수 있다. 만약 열기가 실패한다면 fopen 함수는 널 포인터를 반환한다.

당신은 같은 파일상에 동시에 여러 개의 스트림( 혹은 파일기술자)을 정할 수 있다. 만일 당신이 오직 입력(읽기라고 하면 더 쉽겠네요. . )에만 이일을 한다면 이것은 올바르게 동작하지만 출력(역시 쓰기. . )에 이 동작을 가할 때는 주의를 기울여야 한다. 8. 5절 [Stream/Descriptor Precautions] 참조. 이것은 하나의 프로그램( 보통이 아닌)이나 여러 개의 프로그램( 쉽게 일어날 수 있다 )이던지 간에 실제로 사실로 나타난다. 그래서 동시에 억세스 하는 것을 피하기 위해 파일 잠금 도구를 사용하는 것이 유익할 것이다.

 

매크로 : int FOPNE__MAX

이 매크로의 값은 정수 상수 표현으로, 동시에 오픈할 수 있음을 보증하는 스트림의 최소개수를 나타낸다. 이 상수 값은 세 개의 표준 스트림, stdin, stdout, stderr에서는 적어도 8개이다.

함수 : FILE * freopen (const char *filename, cnost char *opentype, FILE *stream)

이 함수는 fclose와 fopen의 혼합과 같다. 이 함수는 첫째로 스트림에 의해 참조된 스트림을 닫는데, 이때 이 프로세스에서 검출된 어떤 에러도 무시한다. ( 에러가 무시되는 이유는 당신이 그 스트림을 사용하여 어느 출력을 했었다면 그 출력 스트림에 freopen을 사용할 수 없게되기 때문이다. ) 그러면 지정된 파일이름을 가진 파일이 fopen과 같은 파일 열기 모드를 가지고 열려진다.


7. 4 스트림 닫기

fclose함수를 사용하여 스트림을 닫을 때, 스트림과 파일사이의 연결은 취소된다. 당신이 스트림을 닫은 후에, 그것에 어떤 부가적인 명령을 수행하게 할 수 없다.

함수 : int fclose (FILE *stream)

이 함수는 스트림과 그것과 대응하는 파일과의 연결을 깨는데 사용된다. 출력버퍼안에 있는 내용은 쓰여지고, 입력버퍼안에 있는 내용은 버려진다. fclose함수는 성공적으로 파일이 닫혀진 경우에는 0을 반환하고, 에러가 검출된 경우에는 EOF를 반환한다.

당신이 fclose함수를 호출하여 출력 스트림을 닫을 때, 실제로 언제든지 에러들이 검출될 수 있기 때문에 에러를 체크하는 것은 중요하다. 예를 들어, fclose함수를 사용하여 버퍼에 남아있는 내용을 쓸 때에 혹시, 디스크가 다 찬 경우가 발생하여 그것으로 에러가 발생할 수 있을지 모른다. 심지어 당신이 버퍼가 비어있는 것을 안 경우라도, 만일 당신이 NFS(Network File System)을 사용하여 파일을 닫을 때 여전히 에러가 발생할 수 있다.

fclose함수는 'stdio. h'에 선언되어 있다.

만약 메인 함수가 당신의 프로그램에 반환하거나, 혹은 exit함수가 호출된다면 ( 22. 3. 1절[Normal Tremination] 참조), 모든 열려진 스트림 자동적으로 닫혀진다. 그러나 프로그램이 정상적이 아닌 다른 방법으로, 죽 abort함수를 호출하거나( 22. 3. 4절 [Aborting a Program] 참조), 어떤 심각한 신호( 21장 [Signal Handling] 참조) 가 있어서 프로그램이 종료된다면 열려진 스트림들은 완전히 닫혀지지 않을 것이다. 버퍼 안에 있는 내용들도 비워지지 않고, 파일들은 불완전해질 것이다. 좀더 많은 스트림의 버퍼화에 대한 정보를 보려면 7. 17절 [Stream Buffering] 를 참조하라.


7. 5 문자들이나 라인의 간단한 출력

이 절은 문자나 라인 단위의 출력을 수행하기 위한 함수들을 설명하고 있다.

이 함수들은 헤더파일 'stdio. h'에 선언되어 있다.

함수 : int fputc (int c, FILE *stream)

이 fputc함수는 문자 c 을 unsigned char형으로 변화시키고 그것을 스트림에 출력한다. 만약 에러가 발생하면 EOF를 반환하고, 그렇지 않으면 문자 c 가 출력된다.

함수 : int putc (int c, FILE *stream)

이 함수는 대부분의 시스템에서 속도를 빠르게 하기 위해 매크로로 실행된다는 것을 제외하고는 fputc와 같다. 여러 개의 인수보다는 하나의 인수로 이것을 평가할 때 그런 결과에 이른다. 그래서 putc는 보통 단일 문자를 출력하는데 사용하기에는 가장 좋은 함수이다.

함수 : int putchar (int c)

putchar 함수는 putc함수가 스트림의 인수로 stdout을 사용할 때는 동등하다.

함수 : int fputs( const char *s, FILE *stream)

이 함수는 파일 스트림에 문자열 s를 출력한다. 종료문자로 null문자가 쓰여지지 않는다. 이 함수는 newline character도 더하지 않고, 오직 문자열 안에 있는 문자들만 출력한다.
이 함수는 에러가 발생하면 EOF를 반환하고 그렇지 않으면 음이 아닌 값을 반환한다.
 
예를 들어:
fputs ("Are ", stdout);
fputs ("you ", stdout);
fputs ("hungry?\n", stdout);
이렇게 하면 출력은 새로운 줄이 하나 따르는 'Are you hungry?'가 된다.

함수 : int puts (const char *s)

puts함수는 표준스트림 stdout에 newline문자가 붙어있는 문자열 s를 출력한다. 스트링의 종료 널 문자는 출력되지 않는다. puts함수는 간단한 메시지를 출력하는데 가장 편리한 함수이다. 예를 들어:
puts ("This is a message. ");

함수 : int putw (int w, FILE *stream)

이 함수는 워드 w(워드란 바이트의 배가되는 개념이니 형으로 따지면 int가 되죠. . )를 스트림에 출력한다. 이 함수는 SVID와의 호환성을 위해서 제공되지만 우리는 대신에 fwrite를 사용한다. (7. 12절 [Block Input/Output] 참조)


7. 6 문자 입력

이 절은 문자와 라인 단위의 입력을 수행하기 위한 함수를 설명한다. 이 함수들은 헤더파일 'stdio. h'에 선언되어 있다.

함수 : int fgetc (FILE *stream)

이 함수는 스트림인 스트림에서 unsigned char 형으로 다음 문자를 읽고 그 값을 int 형으로 변화시켜 반환한다. 만약 파일에 끝인 이르거나 읽기 에러가 나면 EOF를 반환한다.

함수 : int getc (FILE *stream)

이 함수는 여러 개의 인수가 아닌 한 개의 인수에 적용할 때 더 빠르게 동작하도록 매크로로 동작하는걸 제외하고는 fgetc와 같다. getc함수는 종종 아주 최대한 활용되어서, 보통 단일 문자를 읽을 때 사용하면 가장 좋은 함수다.

함수 : int getchar (void)

getchar 함수는 getc함수가 stream의 인수 값으로 stdin을 사용하면 두 함수는 동일한 것이 된다.
 
이곳에 fgetc를 사용하여 입력을 행하는 함수를 보여주는 예가 있다. fgetc(stdin) 대신에 getchar()을 사용하거나 대신에 getc를 사용해서 같은 결과를 내고 있다.
 
int y_or_n_p (const char *question)
{
fputs (question, stdout);
while (1) {
int c, answer;
c = tolower (fgetc (stdin));
/* 줄의 첫 번째 문자를 읽어라. 이것은 원하는 문자일수도 있고, 아닐 수도 있다. */
answer = c;
while (c != '\n') /* 입력 라인의 나머지를 버려라 */
c = fgetc (stdin);
 
/* 만일 그것이 유용한 것이라면 그 답에 복종하라 */
if (answer == 'y')
return 1;
if (answer == 'n')
return 0;/
fputs ("Please answer y or n:", stdout);
/* 답이 쓸모 없는 것이라면: 유용한 답을 위해서 물어보아라 *
}
}

함수 : int getw (FILE *stream)

이 함수는 스트림으로 한 워드를( 아까. . 위에서 얘기했는데. . . 다시 찾아보셔요. . . ) 읽는다. 이것은 SVID와 호환성으로 위해 제공된다. 우리는 대신에 fread함수를 이용한다. ( 7. 12절 [Block Input/Output] 참조 )


7. 7 라인 단위 입력

많은 프로그램들이 라인을 기본으로 해서 입력을 받아들이므로 스트림으로부터 한 라인의 텍스트를 읽는 함수를 사용하기에 편리하다.

표준 C 는 이와 같은 일을 하는 함수를 가지고 있지만 그들의 사용에는 많은 위험이 도사리고 있다. 가령 널 문자와 심지어 (gets일 경우) 긴 라인을 혼동한다. 그래서 GNU 라이브러리는 신용할 수 있도록 라인을 읽기 위해 쉽게 만들었지만, 일반적이지 않은 getline 함수를 제공한다.

다른 GNU 확장은 getdelim으로 getline을 일반화한 것이다. 그 함수는 정해진 한계문자가 나타나기까지의 모든 것을 읽는, 즉 한계가 있는 레코드를 읽는다. 이 함수들 모두는 'stdio. h'에 선언되어 있다.

함수 : ssize_t getline (char **lineptr, size_t *n, FILE *stream)

이 함수는 스트림으로부터 전체의 라인을 읽어서, 버퍼 안에 텍스트를 저장하고( 새줄과 널 종료문자가 포함된) *lintptr안에 버퍼의 주소를 저장한다.
 
getline를 호출하기 전에, 당신은 malloc로 할당된 *n 바이트의 길이를 가진 버퍼의 주소를 *lineptr에 주어야한다. 만약 이 버퍼가 라인을 저장하기에 충분히 길면, getline은 이 버퍼에 라인을 저장한다. 그렇지 않으면, getline은 realloc을 사용해서 버퍼를 크게 만든 다음, *lineptr에 새로운 버퍼의 주소를 저장하고, *n안의 크기가 증가된다. 3. 3[Unconstrained Allocation] 를 참조.
 
만일 당신이 getline을 호출하기 전에 *lineptr을 널 포인터로 놓고, *n을 zero라고 하면 getline은 malloc을 호출하여 버퍼를 할당한다. 다른 경우, getline은 반환할 때 *lineptr은 라인 텍스트의 위치를 가리키고 있는 char * 이다. getline이 성공하면 읽은 문자들의 수를 반환한다( newline문자는 포함되지만, 널 종료문자는 포함되지 않은 ). 이 값은 종료를 표시하기 위해 삽입된 널 문자와 라인의 일부분의 하나인 널 문자들과는 구별하는 것이 가능하다. 이것은 GNU 확장이지만 스트림으로부터 라인을 읽는 방법으로 권장되고 있다. 표준 함수들은 믿을만 하지 않기 때문이다. 만약 에러가 발생하거나 파일의 끝에 도달하면 getline은 -1을 반환한다.

함수 : ssize_t getdelim (char **lineptr, size_t *n, int delimiter, FILE *stream)

이 함수는 줄의 끝임을 나타내는 newline 문자가 필요한 대신에 문자로 (그 문자가 나타날 때까지 모든걸 읽는) 종료를 나타내는걸 제외하고는 getline과 같다. 여기서 인수 delimiter에는 한계 문자를 정한다. getdelim은 정해진 그 문자가 보일 때까지 읽기를 지속한다. (아니면 파일의 끝까지) 텍스트는 lineptr안에 저장되고, 그 안에는 delimiter(한계) 문자와 종료 널 문자가 표현된다. getline처럼 getdelim은 만약 lineptr이 충분히 크지 않으면 그것을 크게 만든다.
 
getline은 getdelim을 이용하여 다음처럼 구현할 수 있다.
ssize_t getline (char **lineptr, size_t *n, FILE *stream)
{
return getdelim (lineptr, n, '\n', stream);
}
/* getlim의 delimiter인수를 newline 문자인 '\n'으로 주면 getline과 같게 되는 거죠. */

함수 : char * fgets (char *s, int count, FILE *stream)

fgets함수는 newline문자가 나올 때까지 스트림을 읽고, 스트링 s에 그들을 저장시키는데, 그때 스트링의 끝을 표시하기 위해 널 문자를 더한다. 당신은 문자열 s의 길이가 될 문자들의 개수를 알려줘야 하지만 읽혀지는 문자들의 수는 대부분 count-1이다. 나머지 하나의 문자는 스트링의 끝을 표시하기 위한 널 종료문자를 저장하는데 쓰인다. 이미 파일의 끝까지 읽혀졌는데 다시 당신이 fgets를 호출하면 문자열 s의 내용은 변하지 않고, 널 포인터가 반환된다. 에러가 발생됐을 때도 널 포인터는 반환된다. 그렇지 않으면 반환 값은 포인터 s가 된다.
주의 : 만약 입력 데이터가 널 문자라면, 당신은 아무 것도 할 수 없다. 그러므로 당신이 데이터에 널 이 포함되지 않았음을 확신할 수 없다면 fgets를 사용하지 말라. 사용자에 의해 편집된 파일을 읽을 때, fgets를 사용하는 것을 하지 말라고 하는 이유는 만약 사용자가 널 문자를 포함 시켰다면 당신이 에러 메시지를 프린트 해주거나, 아니면 그것이 가능하도록 처리를 해주어야 하기 때문이다. 우리는 fgets대신에 getline을 사용하도록 권장한다.

비난받는 함수: char * gets (char *s)

gets함수는 표준 스트림 stdin으로 부터 다음 newline문자가 나올 때까지 문자들을 읽고, 그들을 문자열 s에 저장한다. gets함수는 앞에서 보여준 fgets와는 다르게(fgets함수는 문자열에 newline문자를 복사한다. ) newline문자를 취하지 않고 버린다. 만약 gets함수는 에러가 발생하거나 파일의 끝에 이르면 널 포인터를 반환하고, 그렇지 않은 경우에는 문자열 s를 반환한다. gets함수는 문자열 s 가 오버플로우를 일으키는 경우에 대비한 아무런 보호책을 제공하지 않기 때문에 매우 위험한 함수이다. GNU 라이브러리 단지 호환성이라는 문제 때문에 이 함수를 제공하고 있을 뿐이다. 그러므로 당신은 gets대신에 fgets나 getline을 사용하라. 당시에게 이것을 상기시키기 위해 링커는( 당신이 GNU ld를 사용한다면 ) 당신이 gets를 사용할 때마다 경고를 내보낼 것이다.


7. 8 읽지 않기

구문분석 프로그램에서 스트림으로부터 문자를 지우지 않고 입력 스트림 안에서 다음 문자를 시험하기에 종종 유용하게 쓰인다. 당신의 프로그램에서 이것을 읽을 때 그냥 흘깃 보는 것과 같아서 이것을 "peeking ahead : 미리 엿보기"라고 부른다.

I/O 스트림을 사용할 때 당신은 문자를 일단 읽은 다음, 그것을 다시 읽지 않는 동작을 통해 입력을 미리 엿볼 수 있다. ( 또한 스트림에서 뒤로 밀기라고도 불린다. ) 문자 읽지 않기( Unreading a character)는 fgetc나 다른 입력 함수를 사용할 때, 스트림으로부터 전에 입력받았던 문자를 다시 입력받을 때 사용하기 유용한 것이다.

뭔 소린지 잘 모르시겠다구요. . . . ?

음. . 다음에 Unreading이 무슨 의미인지 나옵니다. 키키키. . .

 

7. 8. 1 Unreading이란 무슨 의미인가

이해를 돕기 위해 여기 그림 설명이 있다. 당신이 파일에서 읽어서 6개의 문자를 가진 한 개의 스트림을 갖고 있다고 가정하라. 그 문자는 'foobar'이다. 당신이 이미 3개의 문자를 읽었다고 가정하면 상황은 다음과 같다. :

f o o b a r
^
그러므로 다음 입력 문자는 'b'가 될 것이다.
만일 'b'를 읽는 대신에 'o'를 unread시키면 상황은 다음과 같다.
 
f o o b a r
|
o--
^
그래서 다음 입력 문자들은 'o'와 'b'가 될 것이다.
만일 당신이 'o'대신에 '9'를 unread 시키면, 상황은 다음과 같이 변한다.
 
f o o b a r
|
9--
^
그러면 다음 입력 문자들은 '9'와 'b'가 된다.

 

7. 8. 2 Unreading 하기 위해 ungetc를 이용하기

문자를 unread하기 위한 함수를 ungetc라고 부르는데, 그 이유는 getc의 동작을 되돌리기 때문이다.

함수 : int ungetc (int c, FILE *stream)

ungetc함수는 입력 스트림 상에서 문자 c의 뒤로 밀어놓는다. 그래서 다음 입력은 다른 어느 문자를 읽기 전에 c 가 읽혀질 것이다. 만일 c가 EOF이면, ungetc는 아무 일도 하지 않고 단지 EOF를 반환한다. 이것은 getc로 부터 에러를 체크하지 않고 getc의 반환 값으로 ungetc를 호출하도록 한다. unread할 문자들이 스트림으로부터 읽은 마지막 문자와 꼭 같을 필요는 없다. 실제로 ungetc로 그들을 unread하기 전에 스트림으로부터 실제로 읽은 어느 문자일 필요가 없다는 것이다. ( 아까 그림 설명에서 문자'9'를 unread한 것과 같은 것이죠. . . )그러나 이것은 프로그램을 출력을 위한 것치고는 좀 이상한 방법이다. ; 보통 ungetc는 동일한 스트림으로부터 읽었던 문자를 unread하는데 사용한다.

GNU C 라이브러리는 어떤 스트림에 unread할 때 오직 한 문자만을 지원하고, 새로운 입력을 받음이 없이 두 번 ungetc를 호출하는 것을 금한다. 다른 시스템들은 여러 개의 문자들을 unread하는 것이 허용된다. ; 그러면 역순으로 문자들을 읽을 수 있다는 것이 된다.

문자들을 뒤로 밀기( unread라 함이 더 이해하기 쉬운 것 같은데. . . )는 파일의 내용을 변화시키지 않는다. ; 오직 스트림을 위한 내부적 버퍼에 영향을 미친다. 만약 파일내부의 위치를 정하는( fssek나 rewind와 같은 7. 15절 [File Positioning] 를 참조) 것이 호출되었다면, 아직 쓰여지지 않은 pushed_back 문자들은 버려진다.

파일의 끝에 있는 스트림에서 문자를 unread 하는 것은 스트림의 파일의 끝임을 나타내는 지시 단어를 지우는데, 왜냐하면 그것을 유용한 입력 문자로 만들기 때문이다. 당신이 그 문자를 읽은 후에 다시 읽으려고 시도하면 파일의 끝과 다시 만날 수 있다.

여기에 공백문자를( whitespace characters) 건너뛰기 위해 getc와 ungetc를 사용하는 예제가 있다. 이 함수가 공백이 아닌 문자에 도달하면 그 문자를 unread하여서 다음 읽기 동작에서 다시 그 문자가 보여지도록 한다.

#include <stdio. h>
#include <ctype. h>
 
void skip_whitespace (FILE *stream)
{
int c;
do
/* EOF를 체크할 필요가 없다. 왜냐하면 isspace는 EOF를 무시하지 않고 ungetc는 EOF를 무시하기 때문이다. */
c = getc (stream);
while (isspace (c));
ungetc (c, stream);
}


7. 9 형식화된 출력

이 절에 기술된 함수들은(printf와 관련함수들) 형식화된 출력을 수행하는 편리한 방법을 제공한다. printf를 호출할 때는 나머지 인수들의 값을 형식화하는 방법을 지정하는 형식 문자열이나 템플리트 문자열을 사용한다.

당신의 프로그램이 행단위 또는 문자 단위의 처리만을 특별히 수행하는 필터가 아닌 이상, 이 절에 기술되어 있는 printf와 관련 함수들을 사용하는 것이 출력을 수행하는 가장 쉽고 상세한 방법이 될 것이다. 이 함수들은 에러 메시지, 데이터 테이블 등을 프린트하는 데에 특히 유용하다.

 

7. 9. 1 형식화된 출력의 기본사항

printf 함수는 여러 개의 인수를 프린트하는 데에 사용될 수 있다. 당신이 호출할 때에 지정한 템플리트 문자열 인수는 부가적인 인수의 개수에 대한 정보뿐만 아니라 인수들의 형태와 인수들을 프린트하는 데에 사용될 양식에 관한 정보도 제공한다.

템플리트 문자열 내의 통상적인 문자들은 단순히 출력 스트림 그대로 적으면 된다. 반면에 템플리트 내에서 '%'문자에 의해 소개되는 변환 지정자들은 후속 인수들이 출력 스트림으로 형식화되어서 쓰여지도록 만든다. 예를 들면,

int pct = 37;
char filename[] = "foo. txt";
printf (" `%s'의 처리는 %d%% 완료됨. \n ", filename, pct);
위 프로그램의 출력은 이렇다.
'foot. txt'의 처리는 37% 완료됨.

이 예는 '%d' 변환자의 사용은 int 인수가 십진수로 프린트되도록 지정하며, '%s' 변환자는 문자열 인수의 프린트를 지정하며, '%%' 지정자는 리터럴 '%' 문자의 프린트를 지정함을 보여준다. 정수 인수를 8진, 10진, 16진의 무부호 값으로 (각각 '%o', '%u', '%x') 프린트 되도록 하는 변환자도 있고, 문자 값('%c')으로 프린트되도록 하는 변환자도 있다.

부동소수점수는 '%f' 변환자를 사용하면 정상적인 고정소수점수로 프린트될 수 있으며, '%e'변환자를 사용하면 지수 표기로 프린트될 수 있다. '%g'변환자는 '%e'나 '%f' 형식을 사용하는데, 특정 숫자의 크기에 어떤 것이 더 적절한가에 달려있다.

당신은 '%'와 적용할 변환자를 나타내는 문자 사이에 수식어를 써넣음으로써 형식을 더 상세하게 통제할 수 있다. 이들은 변환자의 일상적인 행위를 쉽게 변경할 수 있다. 예를 들어, 대부분의 변환자 지정은 당신이 최소한의 필드 폭을 규정할 수 있도록 허용하며, 또한 필드내에서 그 결과를 왼편으로 정렬 또는 오른편으로 정렬할 것인가를 나타내는 플래그를 지정할 수 있도록 허용한다.

허용된 특정 플래그들, 수식어들과 그 해석은 특정 변환자에 따라 다르다. 이 점은 모두 다음 장에 상세하게 기술된다. 이것이 처음에 너무 복잡하게 보인다고 근심할 필요는 없다; 당신은 전혀 수식어들을 사용하지 않고서도 항상 자유로운 형식의 출력을 상당히 얻을 수 있다. 수식어들은 대체로 출력이 테이블에서 "더 예쁘게" 보이도록 하려고 사용된다.

 

7. 9. 2 출력 변환 문장

이 절은 printf 템플리트 문자열에서 표현될 수 있는 변환 지정의 상세한 문장에 관한 세부사항을 제공한다. 변환자 지정의 일부분이 아닌 템플리트 문자열 내의 문자들은 출력 스트림에 그대로 프린트된다. 수바이트의 문자 연속물들이(18장 [확장된 문자들] 참조) 템플리트 문자열 내에서 허용된다.

printf 템플리트 문자열 내에서의 변환자 지정은 일반적인 형태를 갖는다:

% flags width [ . precision ] type conversion

예를 들어, 변환지정자 '%-10. 8ld'에서는, '-'는 플래그이고, '10'은 필드 폭을 규정하며, 상세지정은 '8'이고, 문자 'l'은 형태변환자이며, 'd'는 변환자 양식을 규정한다. (이 특수한 형태지정자는 long int 인수를 십진수로 프린트함을 나타내며, 최소 10문자 넓이의 필드내에서 최소한 8개의 수를 왼편으로 정렬함을 나타낸다. )

더 상세히 설명하자면, 출력 변환 지정은 앞문자 '%'문자와 후속의 문자로 구성된다:

변환지정의 정상적인 행위를 변경하는 제로 또는 그 이상의 플래그 문자들.

최소한의 필드 폭을 지정하는 십진 정수 옵션. 정상적인 변환이 이 숫자보다 적은 문자를 생성하게 되면, 필드는 지정된 폭이 될 때까지 공백으로 채워진다. 이것은 최소 값이다; 정상적인 변환이 이 숫자보다 많은 문자를 생성하게 되면, 필드는 잘라지지 않는다. 정상적으로는, 출력이 필드내에서 오른편으로 정렬한다. 당신은 '*'의 필드 폭을 지정할 수 있다. 이것은 인수 목록내의 다음 인수(프린트되는 실제값 이전)가 필드 폭으로 사용됨을 의미한다. 그 값은 int이여야 한다. 만약 그 값이 음수이면, 이것은 '-'플래그(아래를 보라)를 설정해서 그 절대값을 필드 폭으로 사용함을 의미한다.

숫자 변환에 쓰이는 숫자의 개수를 지정하는 상세지정 옵션. 만약 상세지정이 지정되면, 그것은 십진정수(빠뜨리게 되면 제로로 내정된다)가 뒤따르는 하나의 마침표('. ')로 이루어진다. 당신은 '*'의 상세지정을 할 수 있다. 이것은 인수 목록내의 다음 인수(프린트되는 실제값 이전)가 상세지정으로 사용됨을 의미한다. 그 값은 int여야 하며, 만약 그 값이 음수이면 무시된다. 만약 당신이 필드폭과 상세규정 모두를 '*'로 지정하게 되면, 필드 폭 인수가 상세규정 인수에 앞선다. 다른 C 라이브러리 변환자들은 이 문장을 인식하지 못할 것이다.

형태 변경자 문자 옵션, 이것은 데이터 형태가 내정 형태와 다를 경우에 상응하는 인수의 데이터 형태를 지정하기 위하여 사용된다. (예를 들면, 정수 변환자는 int 형태를 가정하지만, 당신은 다른 정수 형태를 위해 'h', 'l'이나 'L'을 지정할 수도 있다. )

적용될 변환자를 지정하는 문자. 허용되는 정확한 옵션과 그것들이 해석되는 방식은 변환 지정자가 달라짐에 따라 변한다. 각각의 변환자가 사용하는 특정 옵션에 관한 정보는 각 변환자의 설명을 참조하라.

 

7. 9. 3 출력 변환자 테이블

각기 다른 변환자들이 하는 일을 요약해보자.

`%d', `%I'

정수를 부호 있는 십진수로 프린트한다. 7. 9. 4 [정수 변환자] 참조, 상세설명. '%d'와 '%i'는 출력에서는 동의어이지만, 입력에서 scanf를 사용할 때는 다르다. (7. 11. 3 [입력 변환자 테이블] 참조)

'%o' : 정수를 부호 없는 8진수로 프린트한다. 7. 9. 4 [정수 변환자] 상세설명 참조.

'%u' : 정수를 부호 없는 10진수로 프린트한다. 7. 9. 4[정수 변환자] 상세설명 참조.

'%Z'

정수를 부호 없는 십진수로 프린트하되, 정수가 size_t 형태로 전달된 것으로 가정한다.
7. 9. 4 [정수 변환자] 상세설명 참조. 이것은 GNU 확장이다.

`%x', `%X'

정수를 16진수로 프린트한다. '%x'는 소문자를 사용하고 '%X'는 대문자를 사용한다.
7. 9. 4[정수 변환자] 상세설명 참조.

'%f'

부동소수점수를 정상적인(고정소수점) 표기로 프린트한다.
7. 9. 5 [부동소수점 변환자] 상세설명 참조.

'%e', '%E'

부동소수점수를 지수 표기로 프린트한다. '%e'는 소문자를 사용하고 '%E'는 대문자를 사용한다.
7. 9. 5 [부동소수점 변환자] 상세설명 참조.

'%g', '%G'

부동소수점수를 정상적인 또는 지수 표기로 프린트하는데, 그 크기에 따라 적절한 것을 선택한다. '%g'는 소문자를 사용하고 '%G'는 대문자를 사용한다.
7. 9. 5 [부동소수점 변환자] 상세설명 참조.

'%c' : 단일문자를 프린트한다. 7. 9. 6 [여타의 출력 변환자] 참조.

'%s' : 문자열을 프린트한다. 7. 9. 6 [여타의 출력 변환자] 참조.

'%p' : 포인터의 값을 프린트한다. 7. 9. 6 [여타의 출력 변환자] 참조.

'%n'

프린트될 문자의 개수. 7. 9. 6 [여타의 출력 변환자] 참조. 이 변환자 지정은 어떤 출력도 생성할 수 없음에 주의하라.

'%m'

errno 값에 일치하는 문자열을 프린트한다. (이것은 GNU 확장이다. )
7. 9. 6 [여타의 출력 변환자] 참조.

'%%'

리터럴 '%' 문자를 프린트한다. 7. 9. 6 [여타의 출력 변환자] 참조. 변환자 지정 문장이 유효하지 않다면, 예상치 못한 결과가 일어날 것이다. 그러므로 그렇게 하지 말아야 한다. 만약 템플리트 문자열 내의 모든 변환자 지정을 위한 값들을 공급하는 함수의 인수들이 충분치 못하거나, 또는 인수들의 형태가 제대로 되지 못하였다면, 그 결과는 예측할 수 없다. 만약 당신이 변환자 지정보다도 많은 인수들을 주게 되면, 남는 인수 값들은 그대로 무시된다. 이것은 가끔 유용하다.

 

7. 9. 4 정수 변환자

이 절에서는 `%d', `%i', `%o', `%u', `%x', `%X' 와 `%Z' 변환자 지정을 위한 옵션에 대해 기술한다. 이들 변환자들은 정수들을 다양한 형식으로 프린트한다.

'%d'와 '%i' 변환자 지정은 모두 int 인수를 부호 있는 십진수로 프린트한다; 반면에 '%o', '%u'와 '%x'는 인수를 부호 없는 8진, 10진, 16진수로 프린트한다(각각). '%X'변환자 지정은 문자 'abcdef' 대신에 숫자로서 문자'ABCDEF'를 사용하는 점을 제외하고는 '%x'와 같다. '%Z'는 '%u'와 같으나, size_t 형태의 인수를 기대한다.

다음의 플래그들이 유의미하다:

'-' : 필드내에서(정상적인 오른편 정렬 대신에) 결과를 왼편 정렬한다.

'+' : 부호 있는 '%d'와 '%i' 변환자에서, 만약 그 값이 양수이면 플러스 부호를 프린트한다.

'` '

부호 있는 '%d'와 '%i' 변환자에서, 만약 그 결과치가 플러스 또는 마이너스 부호로 시작하지 않는다면, 그 대신에 공백문자로써 후미를 채운다. '+' 플래그는 그 결과가 부호를 포함하는 것을 보증하기 때문에, 만약 당신이 두 부호를 모두 공급하게 되면 이 플래그는 무시된다.

'#'

'%o' 변환자에서, 이것은 마치 상세지정을 증가시키는 것처럼 앞의 숫자를 '0'으로 채운다.
'%x' 나 '%X'에서는 이것이 결과 값에 앞의 숫자 '0x'나 '0X'를(각각) 붙여준다. 이것은 '%d', '%I'나 '%u' 변환자에 대해서는 아무런 소용도 없다. 이 플래그를 사용하게 되면 strtoul함수(14. 7. 1 [정수의 해부] 참조)와 '%i' 변환자를 갖는 scanf 함수(7. 11. 4 [수치 입력 변환자] 참조)에 의해 해부될 수 있는 출력이 생성된다.

'0'

필드를 공백 대신에 영(零)으로 채운다. 영은 어떤 부호나 진수의 지정 뒤에 위치한다. 이 플래그는 만약 '-' 플래그가 지정되거나 상세지정이 지정되면 무시된다.
만약 상세지정이 주어지면, 그것은 나타날 숫자의 최소개수를 지정한다; 앞에 오는 영들은 필요하면 생겨난다. 만약 당신이 상세지정을 하지 않는다면, 수치는 필요한 만큼의 숫자로 프린트된다. 만약 당신이 영의 명시적인 상세지정으로 영의 값을 변환시키게 되면, 어떤 문자도 만들어지지 않는다. 형태 변경자가 없을 때에는 상응하는 인수는 int (부호 있는 변환자 '%i'와 '%d')로 취급되거나 unsigned int(부호 없는 변환자 '%o', '%u', '%x'와 '%X')로 취급된다. printf와 관련함수들은 variadic해서 어떤 char와 짧은 인수들은 내정 인수 처리에 의해 자동적으로 int로 변환됨을 기억하자. 다른 정수 형태의 인수에 대해서 당신은 이들 변경자들을 사용할 수 있다.

'h'

인수가 short int 나 unsinged short int에 적합하도록 지정한다. 짧은 인수는 내정 인수 처리에 의해 어떻게든 int 나 unsigned int로 변환되지만, 'h' 변경자는 그것을 다시 짧은 정수로 변환한다.

'l'  : 인수가 long int나 unsigned long int에 적합하도록 지정한다.

'L'

인수가 long long int이도록 지정한다. (이 형태는 GNU C 컴파일러에 의해 지원되는 확장이다. extra-long 정수들을 지원하지 않는 체제에서는 이것은 long int와 같다. )

인수 형태 변경자는 '%Z'에는 적용할 수 없다. 왜냐하면, '%Z'의 유일한 목적은 데이터 형태 size_t를 지정하는 것이기 때문이다.

예를 들어 보자. 템플리트 문자열

"|%5d|%-5d|%+5d|%+-5d|% 5d|%05d|%5. 0d|%5. 2d|%d|\n"

을 사용하면, '%d'변환자에 대한 다른 옵션들을 사용하여 수치를 프린트해보면 다음과 같은 결과를 얻는다:

특히, 마지막의 경우는 수치가 지정된 최소 필드폭에 비해 너무 크기 때문에 발생하였음을 주의하라.

 

7. 9. 5 부동소수점 변환자

이 절에서는 부동소수점수에 대한 변환자 지정에 대해 논의한다:

`%f', `%e', `%E', `%g'와 `%G' 변환자.

'%f' 변환자는 그 인수를 고정소수점 표기로 프린트하며, [-]ddd. ddd 형태로 출력을 생성하는데, 여기에서 소수점 뒤에 따르는 숫자의 개수는 당신이 지정한 상세지정에 의해 조절된다.
 
'%e' 변환자는 그 인수를 지수 표기로 프린트하며, [-]d. ddde[+|-]dd 형태로 출력을 생성한다. 또한, 소수점 뒤에 따르는 숫자의 개수는 상세지정에 의해 조절된다. 지수는 최소한 두개의 숫자를 담게 된다. '%E' 변환자는 유사하지만 지수를 'e' 대신에 문자 'E'로 표기한다는 점만 다르다.
 
'%g''%G' 변환자는 만약 지수가 -4이하이거나 상세지정보다 같거나 크면 인수를 '%e'나 '%E' (각각) 형태로 프린트한다; 그렇지 않을 경우에는 이들은 '%f' 형태로 사용한다. 뒤에 붙는 영들은 결과치의 소수부분에서 제거되며, 소수점 문자는 숫자가 뒤따를 때만 나타난다.

다음 플래그들은 그 행위를 변경하는데 사용될 수 있다:

'-'  : 필드에서 결과를 왼편에 정렬한다. 정상적으로는 결과치를 오른편에 정렬한다.
 
'+'  : 결과치 에서 항상 플러스나 마이너스를 포함한다.
 
'` ' : 만약 결과치가 플러스나 마이너스 부호로 시작되지 않는다면, 그 대신에 공백으로 후미를 채운다. '+' 플래그는 결과치가 부호를 포함하도록 보증하기 때문에 만약 당신이 두 부호를 함께 공급하면 이 플래그는 무시된다.
 
'#' : 결과치가 비록 뒤따르는 숫자를 갖지 않더라도 항상 소수점을 포함하도록 지정한다. '%g'와 '%G' : 변환자에 대해서 이것은 소수점 뒤의 영을 다른 경우에는 제거되는 지점의 왼편에 두도록 강제한다.
 
'0'  : 공백대신에 0으로 필드를 채운다; 0은 어떤 부호 다음에 놓인다. 만약 '-' 플래그가 지정되면 이 플래그는 무시된다.

상세지정은 '%f', '%e'와 '%E' 변환자에서 소수점다음에 몇 개의 숫자가 올 것인가를 지정한다. 이들 변환자에 대해서 내정 상세지정은 6이다. 만약 상세지정이 명시적으로 0이면, 이것은 소수점 문자를 완전히 배제한다. '%g'와 '%G' 변환자에 대해서 상세지정은 몇 개의 유효숫자를 프린트할 것인가를 지정한다. 유효숫자는 소수점 앞의 처음 숫자와 소수점 다음의 모든 숫자다. 만약 '%g'나 '%G'에 대해 상세지정이 0이든가 지정되지 않으면, 상세지정은 1의 값을 갖는 것으로 간주된다. 만약 프린트될 값이 숫자의 지정된 개수에서 상세하게 나타낼 수 없는 경우라면, 값은 적당한 근사값으로 처리된다. 형태 변경자가 없으면, 부동소수점 변환자는 double 형태의 인수를 사용한다. (내정 인수 처리에 의해 어떠한 float 인수이든 간에 자동적으로 double로 변환된다. ) 다음 형태 변경자가 지원된다.

'L' : 대문자 'L'은 인수가 long double일 것을 지정한다.
다양한 부동소수점 변환자를 사용하여 어떤 수치들을 프린트할 수 있는가를 보여주는 몇 개의 예들이 있다. 이 수치들 모두는 다음의 템플리트 문자열을 사용하여 프린트되었다:
 
"|%12. 4f|%12. 4e|%12. 4g|\n"
출력은 이렇다:
헤더파일쓰여진
'%g' 변환자가 뒤따르는 0을 떨어뜨리는 법에 유의하라.

 

7. 9. 6 여타의 출력 변환자

이 절에서는 printf에 대한 각종의 변환자에 대해 기술한다.

'%c' 변환자

단일한 문자를 프린트한다. int 인수는 처음에 unsigned char로 변환된다. '-' 플래그는 필드내에서의 왼편 정렬을 지정하기 위해 사용될 수 있지만, 다른 플래그는 정의되지 않으며, 상세지정이나 형태 변경자도 주어질 수 없다.
 
예를 들어:
printf ("%c%c%c%c%c", 'h', 'e', 'l', 'l', 'o');
는 'hello'를 프린트한다.

'%s' 변환자

문자열을 프린트한다. 상응하는 인수는 char * (또는 const char *) 형태여야 한다. 상세지정은 기록할 문자의 최대 개수를 나타내기 위하여 지정될 수 있다; 그렇지 않을 경우에는 널 문자를 포함하지 않을 동안의 모든 문자가 출력 스트림에 기록된다. '-'플래그는 필드내에서의 왼편 정렬을 지정하기 위해 사용될 수 있지만, 다른 플래그나 형태 변경자는 이 변환자에 대해서 주어질 수 없다.
 
예를 들어:
printf ("%3s%-6s", "no", "where");
는 ' nowhere '를 프린트한다.
 
만약 당신이 우연히 '%s' 변환자에 대한 인수로서 널 포인터를 전달하게 되면, GNU 라이브러리는 그것을'(null)'로 프린트한다. 이것은 실패보다는 낫다고 생각할 수 있다. 그러나 의도적으로 널 인수를 전달하는 것은 좋은 습관이 아니다.

'%m' 변환자

errno내의 에러코드에 해당하는 문자열을 프린트한다. 2. 3 [에러 메시지] 참조.
즉,
fprintf(stderr, " `%s'를 열 수 없음: %m\n", filename); 은
fprintf(stderr, " `%s'를 열 수 없음: %s\n", filename, strerror (errno)); 과 같다.
 
'%m' 변환자는 GNU C 라이브러리 확장이다.

'%p' 변환자

포인터 값을 프린트한다. 상응하는 인수는 void * 형태여야 한다. 사실상, 당신은 어떠한 형태의 포인터도 사용할 수 있다.
GNU C 체제에서는, 널이 아닌 포인터들은 마치 '%#x' 변환자가 사용된 것처럼 무부호 정수로 프린트된다. 널 포인터들은 '(nil)'로 프린트된다. (포인터들은 체제마다 다르게 프린트될 것이다. )
 
예를 들어:
printf ( "%p", "연습" );
은 16진수가 뒤따르는 '0x'를 프린트한다_문자열 상수 "연습"의 주소. 그것은 '연습'이란 단어를 프린트하는 것이 아니다.
 
당신은 '%p' 변환자에서 왼편 정렬을 지정하기 위하여 '-' 플래그를 공급할 수는 있지만, 다른 플래그, 상세지정, 또는 형태 변경자를 정의할 수 없다.

'%n' 변환자

여타의 출력 변환자와는 다르다. 이것은 인수를 사용하는데, 그 인수는 int에 대한 포인트여야만 한다. 그러나 이 변환자는 어떤 것을 프린트하는 대신에 그 위치에서 호출되었을 때 프린트되는 문자의 개수를 저장한다. 'h'와 'l' 형태 변경자는 인수가 int * 대신에 short int *이든지 long int * 인가를 지정하도록 허용되어 있으며, 플래그, 필드 폭, 상세지정은 허용되지 않는다.
 
예를 들면,
int nchar;
printf ("%d %s%n\n", 3, "bears", &nchar);
는 3 bears를 프린트하고, '3 bears'는 7문자이기 때문에 nchar에 7을 넣는다.

'%%' 변환자

리터럴 '%' 문자를 프린트한 이 변환자는 인수를 사용하지 않으며 플래그, 필드 폭, 상세지정, 또는 형태 변경자가 허용되지 않는다.

 

7. 9. 7 형식화된 출력 함수

이 절에서는 printf와 관련함수들을 호출하는 방법을 기술한다. 이 함수들의 원형은 헤더파일 'stdio. h'에 있다. 이 함수들은 인수의 개수를 가변적으로 갖고있기 때문에, 당신은 그것들을 사용하기 전에 원형을 선언해야 한다. 물론 당신이 올바른 원형을 갖게 되는 확실하고 손쉬운 방법은 곧바로 'stdio. h'를 포함하는 것이다.

함수 int printf (const char *template, . . . )

printf 함수는 템플리트 문자열 template의 통제하에 선택적 인수들을 스트림 stdout로 프린트한다. 이 함수는 프린트되는 문자들의 개수를 반환하거나 만약 출력에러가 있으면 음수 값을 반환한다.

함수 int fprintf(FILE *stream, const char *template, . . . )

이 함수는 print 함수와 꼭 같으나, 출력이 stdout 대신에 스트림 stream에 쓰여 진다는 점만 다르다.
<역자주> 스트림 stream이 아니고 파일 stream이 아닌가?
** stream stream ---> FILE stream ????

함수 int sprintf (char *s, const char *template, . . . )

이것은 printf와 같으나, 출력이 스트림에 쓰여지는 대신에 문자 배열 s에 저장된다는 점만 다르다. 널 문자는 문자열의 끝을 표시하기 위해 쓰여 진다.
 
sprintf 함수는 배열 s에 저장된 문자의 개수를 반환하지만 종료 널 문자는 포함하지 않는다. 이 함수의 행위는 만약에 복사가 중첩되는 대상물사이에서 일어난다면__예를 들어, 만약에 s가 '%s' 변환자의 통제하에서 프린트되는 인수로서도 주어진다면__정의되지 않는다. 5. 4 [복사와 연결] 참조.
 
경고: sprintf 함수는 위험할 수가 있다, 왜냐하면그것은 잠재적으로 문자열 s의 할당크기에 적합 가능한 것보다 더 많은 수의 문자를 출력할 가능성이 있기 때문이다. 변환자 지정에서 주어진 필드폭은 최소값일 뿐임을 기억하라.
 
이 문제를 피하기 위하여, 당신은 다음에 기술되는 snprintf나 asprintf를 사용할 수 있다.

함수 int snprintf (char *s, size_t size, const char *template, . . . )

snprintf 함수는 sprintf 함수와 유사하지만, size 인수가 만들어낼 문자들의 최대개수를 지정한다는 점만 다르다. 따라붙는 널 문자는 이 한계치에 포함되어 계산되므로, 당신은 문자열 s에 대해 최소한 size 문자를 할당하여야만 한다. 반환 값은 저장된 문자의 개수인데, 종료 널을 포함하지 않는다. 만약 이 값이 size-1 이라면, s에는 모든 출력을 위한 공간이 불충분한 것이다. 당신은 더 큰 출력 문자열을 갖고서 다시 해야 한다.
 
이렇게 하는 예를 들어 보자:
/* 명칭이 name이고 값이 value인 변수의 값을 기술하는 메시지 만들기. */
char * make_message (char *name, char *value)
{
/* 정확히 100 char의 공간만 필요히디고 가정. */
int size = 100;
char *buffer = (char *) xmalloc (size);
while (1) {
/* 할당된 공간에 프린트 시도. */
int nchars = snprintf (buffer, size, "value of %s is %s", name, value);
/* 작동하면 문자열 반환함. */
if (nchars < size)
return buffer;
/* 그렇지 않을 경우 2배의 공간으로 다시 시도. */
size *= 2;
buffer = (char *) xrealloc (size, buffer);
}
}
사실상, 다음에 있는 asprintf를 바로 사용하는 편이 더 쉬울 때가 많다.

 

7. 9. 8 동적으로 할당하는 형식화된 출력

이 절에 있는 함수들은 출력물을 형식화시키고, 그 결과를 동적으로 할당된 메모리에 넣는 일을 한다.

함수 : int asprintf (char **ptr, const char *template, . . . )

이 함수는 sprintf함수가 미리 할당된 버퍼에 output을 저장하는 대신에 동적으로 할당된 곳에 저장한다는 점을 제외하고는 sprintf함수와 유사하다. ( malloc을 사용한다. 3. 3절 [Unconstrained Allocation] 참조) 여기서 ptr 인수는 char * object의 주소가 되어지고, asprintf는 그 위치에 새롭게 할당된 문자열을 가리키는 포인터를 저장한다.
 
여기에 snprintf와 같은 결과를 얻기 위해 asprintf를 사용하는 법을 보여주고 있다. 더 쉬운 방법이다.
 
/* 변수의 값, 즉, 이름은 뭐고, 값은 뭐라는 식으로 알리기 위해 구조화된 출력문이 필요하다. */
char *make_message (char *name, char *value)
{
char *result;
asprintf (&result, "value of %s is %s", name, value);
return result;
}

함수 : int obstack__printf (struct obstack *obstack, const char *template, . . . )

이 함수는 공간을 할당하기 위해서 obstack을 사용하는 것을 제외하면 asprintf 함수와 유사하다. 3. 4절 [Obstacks] 를 참조. 현재 object의 끝에 쓰여있는 문자들, 그들을 얻기 위하여, 당신은 obstack_finish로 object를 끝내야만한다. ( 3. 4. 6절 [Growing Objects] 참조)

 

7. 9. 9 다양한 인수들을 출력하는 함수들

vprintf 함수와 그 유사한 함수들은 printf 함수를 가지고 형식화된 출력을 할 수 있도록 내부적으로 사용되는 다양한 형식을 당신 자신의 것으로 정의할 수 있도록 해준다.

이런 함수들을 정의하는 가장 좋은 방법은 언어를 사용하여 "printf 함수를 호출하고, 첫 번째 다섯 개 후의 인수모두를 더한 템플릿을 넘겨라" 라고 말하는 것이다. 그러나 C로는 이렇게 할 수 있는 방법이 없다, 그리고 제공되는 방법은 매우 사용하기 어렵다. 그리고 C언어 수준에서는 당신의 함수에 많은 인수들이 어떻게 받아들여진건지 알 수 있는 방법이 없다.

그 방법이 불가능했기 때문에, 우리는 "첫 번째 다섯후의 모든 인수들"도 표현하여 va_list로 넘기도록 한 vprintf 시리즈인 선택함수를 제공한다.

vprintf나 이 절에도 보여주고 있는 다른 함수들은 호출하기 전에 당신은 먼저 va_start를 호출하여 다양한 인수들로 포인터를 초기화해줘야만 한다. ( A. 2절 Variadic Functions] 참조) 그리고나서 당신은 당신이 다루기 원하는 인수들을 가져오기 위해 va_arg를 호출할 수 있다.

일단 당신의 va_list 포인터가 당신이 선택한 인수를 가리키고 있으면 당신은 vprintf를 호출할 준비가 되어있는 것이다. 그 인수와 모든 부속된 인수들은 구분되게 정하여진 template와 함께 vprintf에 의해 사용되어진 당신의 함수에 넘겨진다.

어떤 다른 시스템에서는, va_list 포인터는 vprintf를 호출한 후에 유용하게 되어지는데, 당신은 vprintf를 호출한 후에 va_arg를 사용할 수는 없다. 대신에, 당신은 서비스로부터 포인터를 없애기 위하여 va_end를 호출해야한다. 그래야만 당신이 다른 포인터 변수에 va_start를 호출하고, 그 포인터를 통해 인수 추출을 시작하는것들을 안전하게 할 수 있다. vprintf를 호출하는 것은 당신의 함수의 인수 리스트를 파괴하지 않는다, 단지, 그것에게 넘겨진 포인터이다.

GNU C에서는 그와 같은 제한을 가지고 있지 않다. 당신은 vprintf에 그것을 넘겨준 이후에도 va_list 포인터로부터 인수들을 추출하는 것을 안전하게 계속할 수 있고 va_end is a no-op.

 
(참고 : 그렇지만 va_arg호출 부속 인수들은 미리 vprintf를 사용하여 같은 인수들을 추출할 것이다. )

이 함수들을 위한 프로토타입은 'stdio. h'에 선언되어 있다.

함수 : int vprintf (const char *template, va_list ap)

이 함수는 직접적으로 인수로써 한 변수의 수를 취하는 대신에 인수 리스트 포인터 ap를 취한다는 점을 제외하고는 printf와 유사하다.

함수 : int vfprintf (FILE *stream, const char *template, va_list ap)

이 함수는 vprintf로 직접적으로 정해진 변수 인수 리스트를 사용한 fprintf와 동등하다.

함수 : int vsprintf (char *s, const char *template, va_list ap)

이 함수는 변수 인수리스트를 인수로 사용한 sprintf와 동등하다.

함수 : int vsnprintf (char *s, size_t size, const char *template, va_list ap)

이 함수는 직접적으로 선정된 변수 인수리스트를 사용한 snprintf 함수와 동일하다.

함수 : int vasprintf (char **ptr, const char *template, va_list ap)

vasprintf함수는 직접적으로 선정된 변수 인수리스트를 사용한 asprintf와 동등하다.

함수 : int obstack__vprintf (struct obstack *obstack, const char *template, va_list ap)

obstack_vprintf 함수는 vprintf로 직접적으로 정해진 변수인수 리스트를 사용하는 obstack_printf와 동등하다.

여기에 vfprintf를 어떻게 사용했는지를 보여주는 예가 있다. 이것은 표준 스트림 sterr에 프로그램의 이름을 지적하는 접두사와 함께, 에러메시지를 프린트하는 함수이다.

(program_invocation_short_name의 기술을 위해서는 2. 3절 [Error Messages] 참조.
#include <stdio. h>
#include <stdarg. h>
 
void eprintf (char *template, . . . )
{
va_list ap;
extern char *program_invocation_short_name;
 
fprintf (stderr, "%s: ", program_invocation_short_name);
va_start (ap, count);
vfprintf (stderr, template, ap);
va_end (ap);
}
 
당신은 이처럼 eprintf를 호출할 수 있다.
eprintf ("file '%s' does not exist\n", filename);

 

7. 9. 10 템플릿 스트링 파싱

당신은 주어진 템플릿 스트링으로 예상되는 인수들의 타입과, 개수에 대한 정보를 얻기 위하여 parse_printf_format 함수를 사용할 수 있다. 이 함수는 파괴의 원인이 될 수도 있는, 사용자의 프로그램으로부터 유용하지 못한 인수들로 파싱하는 것을 피하기 위해 printf에게 인터페이스를 제공하는 즉각적 해석을 허용한다.

이 절에서 설명하고 있는 모든 심볼들은 헤더파일 'printf. h'에 선언되어 있다.

함수 : size_t parse__printf__format (const char *template, size_t n, int *argtypes)

이 함수는 printf 스트링 템플릿에 의해 예상되는 인수들의 개수와 타입에 대한 정보를 반환한다. 그 정보는 배열 argtypes에 저장되어 있다. ; 이 배열의 각 요소들은 한 개의 인수를 묘사하고 있다. 이 정보는 밑에 리스트된 다양한 'PA_'매크로를 사용해서 기호화되어졌다. n 인수는 배열 argtypes안에 요소들의 개수를 정한다. 이것은 parse_printf_format이 쓸려고(write) 시도하는 대부분의 요소들이다. parse_printf_format는 템플릿에 의해 요구되는 인수들의 총수를 반환한다. 만일 이 숫자가 n 보다 크다면 오직 첫 번째 인수인 n을 설명하여 정보를 반환한다. 만약 당신이 많은 인수들에게서 보다 더 많은 정보를 원한다면, 배열을 크게 할당하고 parse_printf_format를 다시 호출하라.

인수들의 타입은 기본 타입을 조합하고, 플래그 비트들을 수정하여 기호화 되었다.

매크로 : int PA__FLAG__MASK

이 매크로는 플래그 비트를 원하는 형태로 수정하기 위한 비트마스크이다. 당신은 인수를 위해 플래그 비트를 추출하거나 (argtypes[i] & PA_FLAG_MASK) 기본 타입 코드를 추출하기 위해(argtypes[i] & ~PA_FLAG_MASK) 그 표현을 쓸 수가 있다.

여기에 기본적 타입을 나타내기 위한 심볼 상수들이 있다. 그들은 정수 값들을 위한 표준이다.

 

PA_INT : 기본 타입인 int를 지정한다.

PA_CHAR : 기본 타입인 int형을 char로 캐스트 함을 지정한다.

PA_STRING : 이것은 널 종료문자를 갖는 스트링을 지정하는 기본 타입인 char*를 지정한다.

PA_POINTER : 이것은 형에 관계없는 포인터형인 void *를 지정한다.

PA_FLOAT : 이것은 float형 기본 형태를 지정한다.

PA_DOUBLE : 이것은 double형 기본 형태를 지정한다.

PA_LAST

당신은 PA_LAST로부터 offsets로 당신 자신의 프로그램에 부가적인 기본적인 타입들을 정의할 수 있다. 예를 들면, 만일 당신이 그들 자신의 특별 화된 printf 변환으로 'foo'와 'bar'의 데이터 타입들은 갖고 있다면, 당신은 이 타입들을 위해 다음과 같이 정의할 수 있다:
 
#define PA_FOO PA_LAST
#define PA_BAR (PA_LAST + 1)
 

여기엔 기본적인 타입들을 수정하기 위한 플래그 비트들이 있다. 그들은inclusive-or를 사용하여 기본적인 타입들과 조합되어진다.

PA_FLAG_PTR

만일 이 비트가 세트되면, 그것은 기본적인 타입을 가리키는 포인터를 기호화한 타입을 지정한다. 예를 들어, 'PA_INT|PA_FLAG_PTR' 이것은 'int *'를 나타낸다.

PA_FLAG_SHORT

만일 이 비트가 세트되면, short로 수정된 기본적인 타입을 지정한다.
( 이것은 타입 수정자 'h'에 해당한다)

PA_FLAG_LONG

만일 이 비트가 세트되면, long형으로 수정된 기본적인 타입을 지정한다.
( 이것은 타입수정자 'l'에 해당한다. )

PA_FLAG_LONG_LONG

만일 이 비트가 세트되면, long long으로 수정된 기본타입을 지정한다.
( 이것은 타입수정자 'L'에 해당한다. )

PA_FLAG_LONG_DOUBLE

이것은 PA_FLAG_LONG_LONG의 동의어로써, long double형을 지정하기 위해 PA_DOUBLE의 기본적인 타입과 함께 협약으로 사용됐다.

 

7. 9. 11 템플릿 스트링 파싱의 예

여기에 포맷 스트링을 가지고 인수 타입들을 해석하는 예가 있다. 우리는 이것을 NUMBER, CHAR, STRING과 STRUCTURE의 타입 인수들을 포함한 해석기의 일부분이라고 가정한다.

/* 벡터 args안에 정해진 objects인 nargs가 포맷 스트링 format으로 유용한지 테스트하라. 만약 그렇다면 1일 반환하고, 그렇지 않다면 0을 반환한 후 에러메시지를 출력한다. */

 
int validate_args (char *format, int nargs, OBJECT *args)
{
int *argtypes;
int nwanted;
/* 그 인수들에 대한 각 변환 지정은 적어도 두 개의 문자가 길어야 하고, 그것보다 더 길게 할 수는 없다. */
 
argtypes = (int *) alloca (strlen (format) / 2 * sizeof (int));
nwanted = parse_printf_format (string, nelts, argtypes);
/* 인수들의 수를 체크하라. */
if (nwanted > nargs) {
error ("too few arguments (at least %d required)", nwanted);
return 0;
}
/* 각 인수들의 타입을 체크하고, 그 objects들이 적당하게 주어졌는지 보라 */
for (i = 0; i < nwanted; I++) {
int wanted;
 
if (argtypes[i] & PA_FLAG_PTR) {
wanted = STRUCTURE;
} else {
switch (argtypes[i] & ~PA_FLAG_MASK)
{
case PA_INT:
case PA_FLOAT:
case PA_DOUBLE:
wanted = NUMBER;
break;
case PA_CHAR:
wanted = CHAR;
break;
case PA_STRING:
wanted = STRING;
break;
case PA_POINTER:
wanted = STRUCTURE;
break;
}
}
if (TYPE (args[i]) != wanted) {
error ("type mismatch for arg number %d", i);
return 0;
}
} /* for 의 끝 */
return 1;
} /* 함수의 끝 */


7. 10 printf 주문하기

GNU C라이브러리는 당신의 프로그램의 중요한 데이터 구조를 프린트하기 위해 printf에게 가르쳐주는 영리한 방법으로 printf 템플릿 스트링을 지정하여 당신이 주문한 방법으로 변환을 행할 수 있도록 정의하는 것이 허용된다.

당신이 이처럼 하는 방법은 register_printf_function함수로 그 변환을 등록하는 것이다. ; 7. 10. 1절 [Registering New Conbersions] 를 참조. 당신이 이 함수에 넘겨줄 인수증 하나는 출력을 만들어내는 핸들러 함수를 가리키는 포인터이다. 7. 10. 3절 [Defining the Output Handler] 를 참조하여 이 함수를 어떻게 쓰는지 정보를 얻어라. 당신은 변환 지시자에 의해 예상된 인수들의 수와 타입에 대한 정보를 반환하는 함수를 인스톨할 수 있다. 이것에 대한 정보는 7. 9. 10절 [Parsing a Template String] 를 참조하라.

이 절에서 설명하고 있는 도구들은 헤더파일 'printf. h'에 선언되어 있다.

 
호환성 노트: printf 템플릿 스트링의 구문을 연장하는 능력은 GNU확장이다. ANSI 표준 C는 이와 유사한 것이 아무 것도 없다.

 

7. 10. 1 새로운 변환 등록하기

register_printf_function은 새로운 출력 변환을 등록하는 함수로 'printf. h'에 선언되어 있다.

함수 : int register__printf__function (int spec, printf_function handler_function, printf_arginfo_function arginfo_function)

이 함수는 문자 spec 변환 지시자를 정의한다. 그러므로 만일 spec이 'q'라면 그것은 '%q'로 변환되어 정의된다. handler_function은 템플릿 스트링 안에 이러한 변환이 나타날 때 printf와 그 유사 함수들에 의해 호출되는 함수이다. 이것을 인수로 넘기기 위한 함수를 어떻게 정의하는가에 대한 정보는 7. 10. 3절 [Defining the Output Handler] 참조하라. 만일 당신이 널 포인터를 지정하면 spec을 위해 존재한 어느 handler function이 제거된다.

arginfo_function은 이 변환이 템플릿 스트링 안에 나타날 때 parse_printf_format 에 의해 호출되는 함수이다. 이것에 대한 정보는 7. 9. 10절[Parsing a Template String]를 참조하라. 일반적으로 당신은 변환을 위해서 두 개의 함수를 동시에 인스톨 해야하지만 만약 당신이 결코 parse_printf_foramt를 호출하지 않는다면, 당신은 arginfo함수를 정의할 필요가 없다. 성공하면 반환 값은 0이고, 실패하면 -1이다. (만약 spec이 범위를 벗어나는 경우)당신은 기본 출력 변환을 재정의 할 수도 있지만, 이것은 혼란의 가능성이 많기 때문에 좋은 생각이 아니다. 당신이 재정의 했다면 다른 사람에 의해 쓰여진 라이브러리 루틴은 파괴될 수 있다.

 

7. 10. 2 변환 지시자 옵션들

만일 당신이 '%q'의 의미를 정의했다면, 그것이 포함된 '%+23q'나 '%-#q'는 무슨 의미인가? 이 의미를 분별하기 위한 도구로 핸들러가 호출되는데 그것은 템플릿의 옵션의 명세를 얻도록 도와준다.

register_printf_function에게 주어지는 handler_function과 arginfo_function 인수는 변환 지시자의 사례 안에 나타나는 옵션들에 대한 정보를 포함하는 구조체 printf_info의 타입을 인수로써 받아들인다. 이 데이터 타입은 헤더파일 'printf. h'에 선언되어 있다.

타입 : struct printf__info

이 구조체는 printf 템플릿 스트링 안에 있는 변환 지시자의 인스탄스에 나타난 옵션에 대한 정보를 넘기기 위해 사용된다. 그것은 다음과 같다.

int prec

이것은 배정도에 대한 지시자이다. 이 값은 만약 아무 것도 지시되지 않으면 -1이다. 만약 배정도가 '*'로 주어졌다면, 핸들러 함수에 주어진 printf_info 구조체는 인수 리스트로부터 복구시킬 실제의 값을 포함하고 있다. 그러나 그 구조체가 INT_MIN의 값을 가지고 arginfo함수에 주어진다면 그 실제의 값은 알 수 없다.

int width

이것은 최소 필드 너비를 지정한다. 그 값이 0이면 아무 너비도 정해지지 않은 것이다. 만일 그 필드의 너비가 '*'로 주어지면, 핸들러 함수에 넘겨진 printf_info구조체는 인수 리스트로부터 복구될 실제의 값을 포함하고 있다. 그러나 arginfo함수에 주어진 그 구조체가 INI-MIN을 포함하고 있으면 그 실제의 값이 무엇인지 알 수 없다.

char spec

이것은 특별 화된 문자변환 지시자이다. 여러 개의 문자들을 위해 같은 핸들러 함수에 등록시킬 수 있는 구조체에 저장되어 있지만 여전히 핸들러 함수에 호출되어졌을 때 그들에게 말해줄 수 있는 방법을 가지고 있다.

unsigned int is_long_double

이것은 만약 'L'형의 수정자가 지정되면 참인 논리형이다.

unsigned int is_double

이것은 만약 'h'형의 수정자가 지정되면 참인 논리형이다.

unsigned int is_long

이것은 만약 'l'형의 수정자가 지정되면 참인 논리형이다.

unsigned int alt

이것은 만일 '#' 플래그가 지정되면 참인 논리형이다.

unsigned int space

이것은 만일 ' '플래그가 지정되면 참인 논리형이다.

unsigned it left

이것은 만일 '-' 플래그가 지정되면 참인 논리형이다.

unsigned int showsign

이것은 만일 '+' 플래그가 지정되면 참인 논리형이다.

char pad

이것은 최소 필드 너비의 출력에 덧붙여질 때 사용하는 문자이다. 그 플래그가 '0'이면 값이 '0'이지만 ' '이면 다르다.

 

7. 10. 3 출력 핸들러 정의하기

이제 register_printf_function에게 인수로써 주어지게 될 핸들러와 arginfo 함수들을 어떻게 정의하는지 살펴보자

당신은 프로토타입과 함께 당신의 핸들러 함수를 정의할 수 있다.

int function (FILE *stream, const struct printf_info *info, va_list *ap_pointer)

여기의 핸들러 함수에 넘겨진 stream 인수는 출력으로 쓰기 위한 스트림이다. 여기의 info 인수는 템플릿 스트링안의 변환을 포함하고 있는 다양한 옵션에 대한 정보를 포함하고 있는 구조체를 가리키는 포인터이다. 당신은 당신의 핸들러 함수 내부에서 이 구조체를 수정할 수는 없다. 이 데이터 구조를 나타내는 것은 7. 10. 2절 [Conversion Specifier Options]를 참조하라.

여기서 ap_pointer인수는 당신의 핸들러에 프린트되어질 값을 포함하고 있는 변수 인수 리스트의 꼬리를 넘기기 위해 사용되어진다. 다른 함수들이 명백하게 변수 인수 리스트를 넘겨질 수 있는 것과는 달리 여기서는 va_list 그 자체가 아니라 va_list를 가리키는 포인터이다. 그래서 당신은 va_arg의 방법으로 인수들을 추출할 수 있을 것이다. (포인터 넘기기가 당신의 핸들러 프로세스들인 인수들을 세기 위해서 자신의 va_list 변수를 갱신하기 위한 자신의 핸들러 함수를 호출하는 함수에 허용된다. A. 2절 [Variadic Functions] 참조. )

당신의 핸들러 함수는 단지 printf처럼 값을 반환시킬 것이다: 그것은 출력된 문자들의 수를 반환하거나 에러가 나면 음의 값을 반환할 것이다.

데이터 타입 : printf_function

이것은 핸들러 함수가 가질 수 있는 데이터 타입이다. 만일 당신이 당신의 응용 프로그램 안에 parse_printf_format를 사용하려하면, 당신은 당신이 register_printf_function으로 인스톨한 새로운 각각의 변환을 위해서 arginfo_function을 인수로 넘겨주기 위한 함수를 정의해야한다.
당신은 다음과 같이 프로토타입과 함께 이들 함수를 정의할 수 있다.
 
int function (const suruct printf_info *info, size_t n, int *argtypes)
 
그 함수로부터 반환되는 값은 변환 예상되는 인수들의 수가 되어질 것이다. 그 함수는 또한 이 인수들 각각의 타입에 대한 정보로써 argtypes 배열의 n개의 요소를 채운다. 이 정보는 다양한 'PA_' 매크로를 사용하여 기호화된다. ( 당신은 이것이 parse_printf_format에서 사용되는 동일한 호출 협약임을 기억할 것이다. )

데이터 타입 : printf_arginfo_function

이 타입은 변환 지시자에 의해 사용된 인수들의 개수와 타입에 대한 정보를 반환하는 함수를 설명하기 위해 사용되어진다.

 

7. 10. 4 printf 확장 예제

여기에 printf 핸들러 함수를 어떻게 정의하는지 보여주는 예제가 있다. 이 프로그램은 Widget이라고 불리는 데이터 구조를 정의하고 데이터 구조안에 포인터 값과 이름을 포함하고 있는 Widget * 인수들에 대해서는 '%W'라고 변환되도록 printf 정보를 정의한다. '%W'변환은 최소 필드 너비와 왼쪽 정렬 옵션을 지원하지만 그 외 모든 것은 거부된다.

#include <stdio. h>
#include <printf. h>
#include <stdarg. h>
 
typedef struct {
char *name;
} Widget;
 
int print_widget (FILE *stream, const struct printf_info *info, va_list *app)
{
Widget *w;
char *buffer;
int len;
/* 문자열 안에 출력을 포맷하라 */
w = va_arg (*app, Widget *);
len = asprintf (&buffer, "<Widget %p: %s>", w, w->name);
if (len == -1)
return -1;
/* 최소 필드 너비에 덧붙여서, 스트림을 프린트하라. */
len = fprintf (stream, "%*s", (info->left ? - info->width : info->width), buffer);
/* 지우고 반환하라 */
free (buffer);
return len;
}
 
int main (void)
{
/* 프린트하기 위하여 widget를 만들어라 */
Widget mywidget;
mywidget. name = "mywidget";
 
/* widgets를 print 함수에 등록하라. */
register_printf_function ('W', print_widget, NULL);
/* arginfo가 아니다. 이제 widget를 프린트하라. */
printf ("|%W|\n", &mywidget);
printf ("|%35W|\n", &mywidget);
printf ("|%-35W|\n", &mywidget);
 
return 0;
}
 
이 프로그램의 출력은 다음과 같다.
|<Widget 0xffeffb7c: mywidget> |
| <Widget 0xffeffb7c: mywidget>|
|<Widget 0xffeffb7c: mywidget> |


7. 11 형식화된 입력

이 절에 서술된 함수들(scanf와 관련함수들)은 형식화된 출력도구들과 유사한 형식화된 입력을 위한 도구들을 제공한다. 이 함수들은 포맷 문자열과 템플리트 문자열의 통제하에 임의의 값을 읽어오는 메커니즘을 제공한다.

 

7. 11. 1 형식화된 입력의 기초

scanf 함수에 대한 호출은 임의의 인수들이 템플리트 문자열의 통제하에서 읽힌다는 점에서 피상적으로는 printf 함수에 대한 호출과 유사하다. 템플리트에서의 변환자 지정 문장이 printf에서의 그것과 매우 유사한 반면에, 그 템플리트의 해석은 고정필드 형식화라기 보다는 자유형식 입력과 단순형식 어울리기라 볼 수 있다. 예를 들어, 대부분의 scanf 변환자들은 얼마만큼이든 "공백"(공백, 탭, 개행을 포함)을 뛰어넘으며, 해당 출력 변환자에 대한 상세지정의 개념은 있었던 반면에 수치입력 변환자에 대한 상세지정의 개념이 전혀 없다. 보통, 템플리트 내에서의 공백이 아닌 문자들은 출력 스트림 내의 문자들과 정확하게 일치한다. 그러나, 일치시키기 실패는 스트림 상에서의 입력 에러와는 다르다.

scanf가 printf와 다른 점의 또다른 하나는 scanf에 대한 선택적 인수로서는 직접적인 값 대신에 포인터를 공급해야 한다는 점이다; 읽혀지는 값은 포인터가 가리키는 대상물에 저장된다. 노련한 프로그래머들 조차도 종종 이 점을 잊어버리는 수가 있다. 그러므로 만약에 당신의 프로그램이 scanf와 관련된 것처럼 보이는 에러를 만날 때는 당신은 이것을 다시 체크하라. 일치 실패 현상이 일어나면 scanf는 첫 번째의 불일치 문자를 제쳐두고 스트림에서 읽히는 다음 문자를 반환한다.

scanf가 되돌리는 정상적인 반환 값은 할당된 값들의 개수이다. 그러므로 당신은 모든 기대값이 읽혀지기 전에 일치시키기 에러가 발생하였는지를 알아보기 위해 이것을 사용할 수 있다. scanf 함수는 전형적으로 테이블 내용 읽기와 같은 일들을 하기 위해 사용된다. 예를 들어, double형의 배열을 초기화하기 위해 scanf를 사용하는 함수가 있다.

void readarray (double *array, int n)
{
int i;
for (i=0; i<n; I++) {
if (scanf (" %lf", &(array[i])) != 1)
invalid_input_error ();
}
/*각 배열요소에 1개씩의 숫자만 들어 있으면, 별일이 없겠으나, 숫자가 안 들어있든지 2개(?)가 들어 있으면 에러보고??? */
}

형식화된 입력은 형식화된 출력만큼 자주 쓰이지는 않는다. 어느 면에서는 이것은 형식화된 입력을 적절히 사용하는 데에 주의를 요하기 때문이기도 하다. 또 다른 이유는 일치시키기 에러에서 벗어나기 어렵기 때문이다.

만약 당신이 단일한, 고정적인 형식에 들어맞지 않는 입력을 읽고자 한다면, 당신은 scanf를 사용하기 보다는 어휘 스캐너를 발생시키는 Flex라든가 분석자를 발생시키는 Bison과 같은 툴을 사용하는 편이 좋다. 이에 대한 상세한 정보는 Flex에 있는 "Flex"절을 참조하라: 어휘 스캐너 발생기와 Bison 레퍼런스 안내서에 있는 "Bison"절을 참조할 것.

 

7. 11. 2 입력 변환자 문장

scanf 템플리트 문자열은 '%'로 시작되는 변환 지정자들이 중간중간에 박혀있는 통상적인 수바이트 문자를 담고 있는 문자열이다. 템플리트에서의 공백문자(isspace 함수에 의해 정의된 것들; 4. 1 [문자 분류] 참조. )는 입력 스트림내에서 몇 개의 공백문자가 읽히거나 포기되게 하는 현상을 야기한다. 일치하는 공백문자들이 반드시 템플리트에서 나타나는 공백문자들과 꼭 같을 필요는 없다. 예를 들어, 콤마를 인식시키기 위해서 앞뒤에 선택적인 공백을 추가하여 템플리트 내에 ' , '라고 쓰는 경우. 템플리트 내에서 변환자 지정의 일부분이 아닌 다른 문자들은 입력스트림내의 문자들과 정확하게 일치한다; 만약 이러하지 못하다면, 일치 실패가 일어난다.

scanf 템플리트 문자열 내의 변환자 지정은 일반적 형식을 갖는다:

% 플래그 폭 형태 변환자

더 자세히 살펴보면, 입력 변환자 지정은 맨앞의 '%' 문자와 후속의 다음 문자들로 구성된다:

옵션 플래그 '*'는 이 지정에 대한 것으로 읽히는 본문을 무시하도록 한다. scanf가 이 플래그를 사용하는 변환 지정자 발견하였을 때는 나머지의 다른 변환 지정자가 가리키는 대로 입력을 읽어들인다. 그러나, 이 입력은 버려지며, 포인터 인수를 사용하지도 않을 뿐 아니라, 성공한 할당을 헤아리는 숫자도 증가하지 않는다.

옵션 플래그 문자 'a'(문자열 변환에서만 유효함)는 문자열을 담아두기에 충분한 버퍼의 할당을 요구한다. (이것은 GNU 확장이다. ) 7.11.6 [동적인 문자열 입력] 참조.

필드폭의 최대치를 규정하는 옵션 십진수. 입력 스트림에서의 문자 읽기가 중단되는 시점은 최대치에 도달하였을 때이거나 불일치 문자가 발견되었을 때인데, 어느 편이 먼저 발생하든지 간에 그렇게 된다. 대부분의 변환자는 맨앞의 공백문자들 ( 명시적으로 제공되지 않은 문자들 )을 버리게 되며, 이처럼 버려진 문자들은 최대 필드폭에 삽입되지 않는다. 문자열 입력 변환자들은 입력의 끝 부분을 표시하기 위하여 널 문자를 저장한다. 최대 필드폭은 이 종료자를 포함하지 않는다.

옵션 형태 변경자 문자. 예를 들어, 당신은 인수가 int에 대한 포인터이기 보다는 long int에 대한 포인터임을 지정하기 위하여 '%d'와 같은 정수 변환자에 형태 변경자 'l'을 지정할 수 있다.

적용될 변환자를 지정하는 문자. 허용되는 정확한 옵션들과 그것들이 해석되는 방식은 변환자 지정자들이 달라짐에 따라 변한다. 변환자 지정자들이 허용하는 특정 옵션들에 관한 정보를 얻으려면 개별적인 변환자에 대한 기술을 참조하라.

 

7. 11. 3 입력 변환자 테이블

다양한 변환자 지정을 요약해 놓은 테이블을 보기로 하자:

'%d' : 십진수로 된 선택적 부호 정수를 일치시킨다. 7.11.4 [수치 입력 변환자] 참조.

'%I'

C언어가 정수 상수를 지정하기 위해 정의하는 어떠한 형식의 선택적 부호 정수도 일치시킨다. 7.11.4 [수치 입력 변환자] 참조.

'%o' : 8진수로 된 무부호 정수를 일치시킨다. 7.11.4 [수치 입력 변환자] 참조.

'%u' : 10진수로 된 무부호 정수를 일치시킨다. 7.11.4 [수치 입력 변환자] 참조.

'%x', '%X' : 16진 정수로 된 무부호 정수를 일치시킨다. 7.11.4 [수치 입력 변환자] 참조.

`%e', `%f', `%g', `%E', `%G'

선택적 부호의 부동소수점수를 일치시킨다. 7.11.4 [수치 입력 변환자] 참조.

'%s' : 공백이 없는 문자들만을 담고있는 문자열을 일치시킨다. 7.11.5 [문자열 입력 변환 자] 참조.

'%['

지정된 세트에 속하는 문자들로 이루어진 문자열을 일치시킨다. 7.11.5 [문자열 입력 변환자] 참조.

'%c'

하나이상의 문자들로 이루어진 문자열을 일치시킨다. 읽히는 문자의 개수는 변환자에 대해 주어진 최대 필드폭에 의해 조절된다. 7.11.5 [문자열 입력변환자] 참조.

'%p'

printf에 대한 출력 변환자 '%p'에 의해 사용된 동일한 실행 정의 형식에 있는 포인터 값을 일치시킨다. 7.11.7 [다른 입력 변환자] 참조.

'%n'

이 변환자는 어떠한 문자도 읽지 않는다; 이것은 이것이 호출된 이후에 읽혀진 문자의 개수를 기록한다. 7.11.7 [여타의 입력 변환자] 참조.

'%%'

이것은 입력 스트림내의 리터럴 '%'문자를 일치시킨다. 상응하는 인수는 사용되지 않는다. 7.11.7 [다른 입력 변환자] 참조.

만약 변환자 지정 문장이 유효하지 않다면, 그 행위가 정의되지 않는다. 만약 할당을 수행하는 템플리트 문자열 내에서 모든 변환자 지정자에 대한 주소를 공급하기로 되어있는 함수 인수들이 충분하지 않거나, 인수들이 올바른 형태가 아니라면, 역시 그 행위가 정의되지 않는다. 한편, 남는 인수는 단순히 무시된다.

 

7. 11. 4 수치 입력 변환자

이 절에서는 숫자 값을 읽어들이는 scanf 변환자에 대해 기술한다.

'%d'

10진수에서 선택적인 부호 정수를 일치시킨다. 인식되는 문장은 기초인수의 값으로서 10을 갖고있는 strtol 함수(14.7. [정수 분석] 참조)에 대한 문장과 같다. '%i'변환자는 C언어가 정수 상수용으로 정의하는 어떤 형식에서나 선택적 부호 정수를 일치시킨다.
 
인식되는 문장은 기초인수의 값으로서 0을 갖고있는 strtol 함수(14.7 [정수 분석] 참조)에 대한 문장과 같다. (당신은 이 문장상의 정수들을 `%x', `%o'나 `%d' 변환자를 갖는 '#'플래그 문자를 사용하여 printf로 프린트할 수 있다. 7.9.4 [정수 변환자] 참조. )
 
예를 들어, '10', '0xa'나 '012'중의 어떤 문자들이든 '%i' 변환자가 관장하는 정수로 읽혀질 수 있다. 이들 각각은 십진 값 10을 갖고서 어떤 수를 지정한다.

'%o', '%u'와 '%x'

변환자는 각기 8진, 10진, 16진무부호 정수를 일치시킨다. 인식되는 문장은 기초인수의 값으로서 적절한 값(8, 10이나16)을 갖고있는 strtol 함수(14. 7. [정수 분석] 참조)에 대한 문장과 같다.

'%X' 변환자

'%x'변환자와 같다. 둘다 대문자나 소문자가 숫자로 사용되는 것을 허용한다. %d나 %i에 대해 상응하는 인수의 내정 형태는 다른 정수 변환자를 위해 int *와 unsigned int *로 되어있다. 당신은 다른 크기의 정수를 지정하려면 다음의 형태 변경자를 사용해야 한다:
'h' 인수가 short int * 또는 unsigned short int * 가 되도록 지정한다.
'l' 인수가 long int * 또는 unsigned long int * 가 되도록 지정한다.
'l' 인수가 long long int * 또는 unsigned long long int * 가 되도록 지정한다. (long long형태는 GNU C 컴파일러에 의해 지원되는 확장이다. ) extra-long 정수가 제공되지 않는 체제에서는 이것은 long int와 같다. )

'%e', `%f', `%g', `%E'와 `%G'

위 입력 변환자들은 모두 상호교환이 가능하다. 이들은 모두 strtod 함수에 대한 동일한 문장에서 선택적 부호 부동소수점수를 일치시킨다. (14.7.2 [부동소수점수 분석] 참조. ) 부동소수점 입력 변환자에 대해, 내정 인수 형태는 float *이다. (이것은 상응하는 출력 변환자와는 다른데, 출력 변환자의 내정 형태는 double이다; printf에 대한 float 인수는 내정 인수 처리에 의해double로 변환되지만, float * 인수는 double *로 처리되지 않는다. ) 당신은 이들 형태 변경자들을 사용하는 다른 크기의 float를 지정할 수 있다.
 
'l' 인수의 형태가 double *가 되도록 지정한다.
'L' 인수의 형태가 long double *가 되도록 지정한다.

 

7. 11. 5 문자열 입력 전환들

이 절은 문자열과 문자 값들을 읽어오기 위해 scanf입력 전환에 대해 설명하고 있다. :

'%s', '%[', 과 '%c'

당신에게는 이 전환들로부터 입력을 어떻게 받을 것인가에 대한 두 개의 옵션들이 있다.
그것을 저장하기 위한 버퍼를 제공하라. 이것은 디폴트로서. 당신이 char *형으로 인수를 주어야 할 것이다.
 
주의 : 튼튼한 프로그램을 만들려면, 당신은, 입력이 당신이 제공한 버퍼의 크기를 넘지 않는지( 종료문자 널을 더해서 ) 확인해야만 한다. 일반적으로, 이것을 하는 유일한 방법은 버퍼크기보다 하나 적은 최대 필드 너비로 정하는 것이다. 만일 당신이 버퍼를 만들려면, 오버플로우를 예방하기 위해 최대 필드 너비로 정하라.
버퍼의 크기를 크게 할당하려면 'a' 플래그 문자를 지정해서 scanf에게 요청하라. 이것은 GNU 확장이다. 당신은 버퍼의 주소가 저장되도록 char ** 타입의 인수를 주어야 할 것이다. 7.11.6절 [Dynamic String Input] 참조.

'%c' 전환

간단하다. 항상 정해진(사용자가) 개수의 문자를 출력한다. 많은 문자들을 읽을 때에는 최대 수를 정해줘야 하는데, 만약 그 개수를 정하지 않으면 디폴트값은 1이다. 이 전환은 읽은 텍스트의 끝에 널 문자를 덧붙이지 않는다. 또한 이 전환은 앞쪽의 공백(whitespace) 문자를 무시하지 않고, 정확하게 주어진 n개의 문자를 읽고, 만약 그만큼을 얻을 수 없다면 실패한다. 항상 '%c'와 함께 읽어올 최대개수를 주어라. (정해지지 않으면 디폴트는 1이다. ) 당신은 충분히 긴 버퍼를 만들어서 항상 오버플로우를 방지하라.

'%s'전환

비 공백(non_whitespace) 문자들과 대응된다. 이 전환은 앞쪽의 공백문자를 건너뛰고, 무시하지만, 공백이 아닌 다른 문자를 읽은 후에 또 다른 공백문자를 만나면 그곳에서 읽은걸 멈춘다. 이 전환은 읽은 텍스트의 끝에 널 문자를 저장한다.
예를 들어, 다른 입력을 읽으면;
hello, world
'%10c' 전환이 주어지면 그 결과는 " hello, wo"가 되지만, 같은 입력을 '%10s'로 읽으면 그 결과는 "hello, "가 된다.
 
주의 : 만약 당신이 '%s'와 함께 필드의 너비(읽어올 개수)를 정하지 않았다면 읽을 문자들의 개수는 오직 문자들에 나타난 다음 공백문자가 있는 곳으로 제한된다. 이것은 거의 확실하게, 올바르지 못한 입력으로는 당신의 프로그램이 버그로 파괴될 수 있음을 의미한다.

'%['전환

당신이 선택한 제멋대로인 문자열 셋에서 문자들을 읽으려면, '%[' 전환을 사용하라. 당신은 일반적인 표현에서 사용되는 같은 구문을 사용하여 '[' 문자와 ']' 문자 사이에 셋을 정한다.
다음 특별한 경우:
문자 ']'는 문자 셋의 첫 번째 문자로서 정해질 수 있다.
끼워진 '-' 문자( 그것은 문자 셋의 처음이거나 마지막이 아닌 것 )는 문자들의 범위를 정하는데 사용된다.
만일 처음 '[' 의 곧바로 다음에 부호 '^'가 따르면, 입력문자에서 '[' 과 ']' 안에 리스트된 문자들을 제외한 모든 것의 출력을 허용한다.
 
'%[' 전환은 처음의 공백문자(whitespace)를 건너뛰지 않는다. 이곳에 '%[' 전환과 그것이 무엇을 의미하는지에 대한 예가 몇 가지 있다.
`%25[1234567890]'
25개의 숫자로 문자열을 구성한다.
`%25[][]'
25개의 대괄호로 문자열을 구성한다.
`%25[^ \f\n\r\t\v]'
25개의 길이를 가진 문자열을 만드는데, 거기에는 어떠한 표준 공백문자들이 포함되지 않는다. 이것은 '%s'와는 약간 다른데 그 다른 점이란 만약 입력이 공백문자로 시작된다면 '%s'는 간단히 앞쪽의 공백문자를 무시해버리지만 '%['는 대응에 실패를 한다.
`%25[a-z]'
25개의 소문자로 구성된 문자열을 만든다.

 

한 번 더 기억 : '%s'와 '%[' 전환은 최대 너비를 정하지 않거나, 'a'플래그를 사용하지 않으면 위험하다. 왜냐하면 입력이 너무 길어서 당신이 제공한 버퍼가 오버플로우를 발생시킬 수 있기 때문이다. 당신이 제공한 버퍼가 주어진 입력보다 길다면 아무런 문제가 없다. 잘 만들어진 프로그램은 유용하지 못한 입력에 이해할 수 있는 에러메시지를 내서, 프로그램이 문제를 일으키지 않게 한다.

 

7. 11. 6 동적으로 할당하는 문자열 전환들.

GNU 확장은 최대 크기가 아닌 것으로도 문자열을 안전하게 읽는걸 허용한다. 이것을 사용하면 당신은 버퍼를 제공하지 않아도 되는데; 대신에 scanf는 당신이 준 주소에 데이터를 저장하기 위해 충분한 크기의 버퍼를 할당한다. 이것을 사용하려면, 플래그 문자로 'a'를 써서, '%as' 나 '%a[0-9a-z]로 쓴다.

입력을 저장하기 위해 당신이 제공한 포인터 인수는 char ** 형을 가진다. scanf함수는 버퍼를 할당하고 그 포인터 인수에 워드(word)로, 그 할당된 영역의 주소를 저장한다. 더 이상 그것이 필요치 않으면 free로 버퍼를 해제해야한다. 여기에 'varilble = value' 형태의 "variable assignment"를 읽기 위해 'a'플래그와 함께 '%['변환지정을 사용한 예가 있다.

{
char *variable, *value;
if (2 > scanf ("%a[a-zA-Z0-9] = %a[^\n]\n", &variable, &value))
{
invalid_input_error ();
return 0;
}
. . .
}

 

7. 11. 7 다른 입력 전환들

이 절에는 잡다한 입력 전환들에 대해 설명하고 있다.

'%p'전환

포인터 값을 읽기 위해 사용된다. printf를 위해 출력전환으로 사용되는 '%p'와 동일한 구문으로 인식되고( 7. 9. 6 [Other Output Conversions] 참조); 받아들여진 '%x' 전환은 단지 16진수를 위한 입력이다. 대응하는 인수로 void ** 형이 주어져야 하고; 그것은 포인터를 저장하기 위한 장소의 주소이다. 그 결과인 포인터 값은 만약 그 안에 읽는 동일한 프로그램 실행(execution)동안에 원래대로 써지지 않는다면 그것이 유용한 것인지는 보장할 수 없다.

'%n'전환

이 호출에 의해 읽혀진 문자들의 수를 만든다. 그 대응인수는 int *가 되어진다. 이 전환은 printf에서 사용하는 '%n'전환과 동일한 방법으로 작동한다; 7. 9. 6[Other Output Conversions] 에서 예제를 참조하라. '%n'전환은 문자의 작용(역주: 입력이나 출력등 어떤 문자열에 가해진 작용인 듯)의 성공여부를 결정하거나 은폐된 인수로의 전환을 위한 메커니즘이다. 만약 '%n'에 대응실패가 따르면 '%n'의 진행 전에 scanf가 반환한 그곳에는 아무 값도 저장되지 않은 것이다. 만일 당신이 scanf를 호출하기 전에 그 인수에 -1을 저장하면 scanf는 '%n'에 도달되기 전에 에러의 발생을 지적한다.

'%%' 전환

인수를 사용하지 않고 입력 스트림에 '%'문자를 넣는다. 이 전환은 어느 플래그, 필드의 너비나 타입수정자든, 어떤 것도 허용하지 않는다.

 

7. 11. 8 형식화된 입력 함수들.

이곳에서는 형식화된 입력을 수행하는 함수들을 설명하고 있다. 이 함수들을 위한 프로토타입은 헤더파일 'stdio. h'에 있다.

함수 : int scanf (const char *template, . . . )

scanf함수는 표준 스트림에서 템플릿 문자열의 제어에 따라서 형식화된 입력을 읽는다. 임의의 인수는(optional arguments) 반환된 값을 받기 위한 장소의 포인터이다. 그 반환 값은 보통 저장된 입력 필드의 수를 반환한다. 만일 어느 것도 읽혀지기 전에 파일의 끝인 상황이 검출되면, ( 템플리트에 공백문자와 다른 문자들을 읽는 것을 포함하여), 그러면 EOF가 반환된다.

함수 : int fscanf (FILE *stream, const char *template, . . . )

이 함수는 표준 스트림이 아닌 스트림에서 읽어오는걸 제외하면 scanf와 같다.

함수 : int sscanf (const char *s, cost char *template, . . . )

이 함수는 스트림대신에 널 종료문자를 가진 문자열로부터 문자들을 읽는다는걸 제외하고는 scanf와 같다. 스트링의 끝에 도달하면 파일의 끝인 상황처럼 취급한다. 이 함수의 동작이 만일 오버랩할 objects와 저장할 장소를 취하는 것 사이에 정의되지 않는다면, 만일 s가 스트링을 받기 위한 인수로서 주어진다면 '%s'의 제어하에 문자들을 읽는다.

 

7. 11. 9 변수 인수들의 입력 함수들

vscanf와 그와 유사한 함수들은 당신 자신만의 다양한 scanf와 같은 함수를 정의할 수 있도록 하는 기능을 제공한다. 이 함수들은 출력함수들인 vprintf 시리즈와 유사하다. 7.9.9 [Variable Argumints Output] 를 참조하여 그들은 어떻게 사용하는지에 대한 중요한 정보를 보라.

호환성 노트 : 이 절에 리스트된 함수들은 GNU확장이다.

함수 : int vscanf (const char *template, va_list ap)

이 함수는 직접적으로 인수들의 개수를 가진 변수를 취하는 대신에 인수 리스트로 va_list 형의 포인터 ap를 취하는걸 제외하고는 scanf와 유사하다.

함수 : int vfscanf (FILE *stream, const char *template, va_list ap)

이 함수는 직접적으로 인수 리스트를 변수로 취하면 fscanf와 동등하다.

함수 : int vsscnaf (const char *s, const char *template, va)list ap)

이 함수는 직접적으로 인수 리스트를 변수로 취하면 sscanf와 동등하다.


7. 12 블록 입력/출력

이 절은 데이터 블록의 입력과 출력 동작을 어떻게 하는지에 대해 설명하고 있다. 당신은 문자들이나 라인 대신에 정해진 크기의 블록에 텍스트를 읽고 쓰는 것은 물론, 바이너리 데이터를 읽고 쓸 수 있는 그러한 함수들을 사용할 수 있다.

바이너리 파일들은 실행되고있는 프로그램 안에서 데이터를 나타내기 위해 사용되어진것과 동일한 형식으로 데이터의 블록들을 읽고 쓰기 위해 사용된다. 다른 말로 하자면, 단지 문자나 스트링 오브젝트가 아닌 , 메모리의 블록을 바이너리 파일로 쓸 수도 있고, 동일한 프로그램에 의해 다시 의미있게 읽혀질 수도 있다.

바이너리 파일의 형식으로 데이터를 저장하는 것은 형식화된 입/출력 함수들 사용하는 것 보다 종종 상당히 더 효과적이다. 또한 플로팅 포인트 숫자들에 바이너리 형식의 사용은 변환작업에서 정밀도를(precision) 잃을 가능성을 피하게 한다. 하지만 한편으로, 바이너리 파일들은 많은 기본적인 파일 유틸리티( 텍스트 에디터 같은 )들을 사용하여 쉽게 시험하거나 수정할 수 없고, 다른 종류의 컴퓨터들이나 다른 언어에 적용시키는 것은 호환성이 없다.

이 함수들을 'stdio. h'에 선언되어 있다.

함수 : size_t fread (void *data, size_t size, size_t count, FILE *stream)

이 함수는 스트림 stream으로부터 배열 data안으로 size의 크기의 count개의 objects를 읽는다. 이것은 실제로 읽혀진 objects의 개수를 반환하고, 만약 에러가 발생하거나, 파일의 끝에 도달하면 count보다는 적은 수를 반환할 것이다. 만약 size나 count가 영이면 그 함수의 반환 값은 영이된다(아무 것도 읽지 않았다) 만약 object의 중간에 파일의 끝이 있다면 fread는 완전한 objects의 수를 반환하고, 나머지 object는 버린다. 그러므로, 그 스트림에는 실제 파일의 끝이 남는다.

함수: size_t fwrite (const void *data, size_t size, size_t count, FILE *stream)

이 함수는 배열 data에서 스트림 stream으로 size크기의 count개의 objects를 저장한다. 반환 값은 호출이 성공하면 일반적으로 쓰는데 성공한 count수이다. 다른값이 반환되면 에러나 실행이 실패하였음을 의미한다.


7. 13 파일의 끝과 에러들.

이장에 설명된 많은 함수들은 오퍼레이션의 불완전한 수행을 지적하기 위해 매크로 EOF의 값을 반환하는 함수이다. 파일의 끝이나 임의의 에러를 지적하는데 사용되고, 그것은 종종 명백하게 파일의 끝을 체크하기위해 feof, 그리고 에러들은 체크하는데 ferror를 사용하는 것이 좋다. 이 함수들은 스트림 오브젝트의 내부적 상황의 부분인 지시자, 스트림에서 앞에서 행해진 입/출력 오퍼레이션에 의해 검출된 적절한 상황을 가진 지시자 세트를 체크한다.

이 심볼들은 헤더파일 'stdio. h'에 선언되어 있다.

매크로 int EOF

이 매크로는 파일의 끝인 상황을 지적하거나 어떤 다른 에러 상황에서 사용된 함수들에 의해 반환되는 정수값이다. GNU라이브러리에서 EOF는 -1이고, 다른 라이브러리들에서는 어떤 다른 음의 값을 가질 것이다.

함수 : void clearerr (FILE *stream)

이 함수는 스트림 stream을 위해 파일의 끝과 에러 지시자를 클리어 한다. 이 파일 상태(positioning) 함수들은( 7. 15절 [File Positiong] 참조) 또한 스트림의 파일끝 지시자를 클리어 한다.

함수 : int feof (FILE *stream)

이 feof함수는 스트림에서 만일 단지 파일의 끝 지시자이면 영이아닌 값을 반환한다.

함수 : int ferror (FILE *stream)

이 ferror 함수는 스트림에서 에러지시자를 만나면 영이아닌 값을 반환하여 스트림에서 앞의 오퍼레이션에서 발생한 에러를 지적한다.

스트림과 연관하여 에러 지시자를 세팅하는 것 뿐만 아니라, 파일 기술자에 수행한 연관된 저수준 함수들과 같은 방법으로 에러번호(errno) 또한 세트 할 수 있다. 예를 들어. fputc, printf, 그리고 fflush등 스트림의 출력을 수행하는 모든 함수들에서 출력을 수행하는 동안에 에러의 상황에 따라 정의된 에러번호는 이 함수들에게 의미가 있다. 이 저수준 입/출력 함수 기술자에 대한 정보는 8장 [Low-Level I/O] 를 참조하라.

==> 지금까지 system을 "체제"로 번역하였으나, operating system은 "운영체제"로 번역하되, 다른 system 은 "시스템"으로 번역함. 왜냐하면, 이미 업계에서 "시스템"이라고 그냥 부르는 경우가 많기 때문임.


7. 14 텍스트와 바이너리 스트림.

GUU 시스템과 다른 POSIX 호환 운영체제들은 문자들의 열을 같은 형식으로 해서 모든 파일을 구성한다. 그렇지만, 다른 시스템들에서는 텍스트 파일과, 바이너리 파일사이에 다른점이 있고, ANSI C의 입력과 출력 도구들은 이 차이를 지원한다. 이 절은 다른 시스템들과 호환 되도록 어떻게 프로그램을 만들 것인가에 대한 정보가 있다.

스트림을 개방할 때, 당신은 텍스트 스트림인지 바이너리 스트림인지를 정할 수 있다. 당신이 바이너리 스트림을 원하면, fopen 의 opentype인수에 'b'라고 지정하면 원하는걸 얻을 수 있다. 7. 3절 [Opening Streams] 참조. 'b' 옵션이 없이 사용하면 텍스트 스트림으로 파일이 개방된다. 텍스트와 바이너리 스트림의 여러 가지 차이.

텍스트 스트림에서 읽어온 데이터는 새줄('\n')문자에 의해 종료된 라인들로 구분되지만, 바이너리 스트림은 간단히 문자들의 긴 연속이다. 어떤 시스템에서 텍스트는 254개의 문자보다 긴 문자를 취급할 수가 없다. (새줄 문자를 포함해서 )

어떤 시스템에서, 텍스트 파일들은 오직 프린트 가능한 문자와, 수평탭, 새줄기호 등을 포함할 수 있고, 그 외 다른 문자들은 지원하지 않는다. 하지만 바이너리 스트림들은 어떤 문자 값이라고 취급할 수 있다.

텍스트 스트림에서 새줄기호 앞에 쓰여진 공백문자는 다시 그 파일을 읽을 때는 사라질지도 모른다.

더 일반적으로, 실제 파일안의 문자들과, 텍스트 스트림에서 읽거나, 쓴 문자들 사이에 일대일로 대응되어질 필요가 없다.

바이너리 스트림이 텍스트 스트림보다 항상 더 유능하고, 더 예측 가능한데도 불구하고, 무슨 목적으로 텍스트 스트림이 제공되는지 당신은 의아해할 것이다. 바이너리 스트림의 사용은 왜 항상 간단하지 않은가? 이것에 대한 대답은 텍스트와 바이너리 파일의 형식을 다르게 사용하고, 텍스트를 기반으로한 프로그램으로 작업을 하는 "보통의 텍스트 파일"을 읽거나 쓰는 유일한 방법이 텍스트 스트림을 통해서만 가능한 그런 운영체제들 때문이다.

GNU 라이브러리와 모든 POSIX 시스템들에서는 텍스트 스트림과 바이너리 스트림 사이에 아무런 차이점이 없다. 당신이 스트림을 개방할 때, 바이너리 스트림으로 요청하는걸 신경을 쓰지 않아도 바이너리 스트림을 얻을 수 있다. 이 스트림은 텍스트 스트림이 가진 그런 제한없이 어떤 내용이라도 취급할 수 있다.


7. 15 파일 위치시키기

스트림의 파일 위치는 파일 스트림에서 현재 어느곳을 읽거나, 쓸것인지에 대한 것이다. 스트림의 입출력은 파일의 위치를 진보시킨다. GNU시스템에서는 파일의 위치는 파일의 시작점으로 부터 바이트의 수를 셈한 정수 값으로 나타낸다. 6.1.2절 [File Position] 참조.

 
역자주: 이곳에서 진보란 말은 파일의 위치가 앞으로 옮겨진다는 말.

일반적 디스크 파일에서 입/출력 동안에, 당신은 파일의 어느 부분을 읽거나, 쓰기 위하여, 당신이 원하면 언제든지 파일의 위치를 변경할 수 있다. 대부분의 파일들에서 이것이 허용될 것이다. 파일 위치의 변경을 지원하는 파일들은 때로는 랜덤-억세스 파일이라 불려진다. 당신은 스트림과 관련하여 파일 위치를 시험하거나, 수정하기 위해 이 절에서 설명한 함수들을 사용할 수 있다.

밑에 설명된 것들은 헤더파일 'stdio. h'에 설명되어 있다.

함수 : long int ftell (FILE *stream)

이 함수는 스트림 stream의 현재 파일 위치를 반환한다. 이 함수는 만일 스트림이 파일 위치 변경을 지원하지 않거나, 혹은 파일 위치를 long int로 표현할 수 없거나, 또는 다른 가능한 여러 가지 이유가 발생하면 실패할 수 있다. 만약 실패하면 -1의 값을 반환한다.

함수 : int fseek (FILE *stream, long int offset, int whence)

fseek함수는 스트림 stream의 파일 위치를 변경하는데 사용된다. whence값은 SEEK_SET, SEEK_CUR, 또는 SEEK_END 상수들 중 하나가 되어야 하는데, 이 상수 값들은 offset과 연관시켜서 바꾸기 원하는 파일의 위치를 지적하게 된다. 즉, SEEK_SET는 파일의 시작점과 offset를 연관시키고, SEEK_CUR은 파일의 현재위치와 offset를 연관시키고, SEEK_END는 파일의 끝과 offset를 연관시키는 것이다.
 
이 함수는 성공하면 0의 값을 반환하고, 실패하면 0이 아닌 값을 반환한다. 성공적인 호출은 또한 스트림의 파일끝(end-of-file) 지시자를 클리어 시켜, ungetc를 통한 "뒤로 밀림"동작이 무시되도록 한다. fseek는 파일 위치를 변경하기 전에 버퍼된 출력을 모두 플러쉬 시키고, 파일의 적당한 위치에 그것을 적는다.
호환성 노트 : 비-POSIX 시스템들에서, ftell 과 fseek만이 바이너리 스트림 상에서 믿음직하게 동작한다. 7. 14절 [Binary Streams] 참조하라. 다음에 설명된 심벌 상수들은 fseek함수의 whence 인수로 사용하기 위해 정의되었다. 그들은 또한 lseek함수에서도 사용되고, ( 8. 2절 [I/O Primiteves] 참조) 파일 잠금(locks)에서 offset을 정하기 위해( 8. 7절 [Control Operations] 참조) 사용된다.

매크로 int SEEK__SET

이것은 정수 상수로서 fseek함수에 whence인수로 사용되면, offset을 파일의 시작점과 연관시킴을 지정한다.

매크로 int SEEK__CUR

이것은 정수 상수로서 fseek 함수에서 whence인수에 사용되면 offset를 파일의 현재의 위치와 연관시킴을 지정한다.

매크로 int SEEK__END

이것은 정수 상수로서 fseek함수에서 whence인수에 사용되면 offset를 파일의 끝 위치와 연관시킴을 지정한다.

함수 : void rewind (FILE *stream)

rewind 함수는 파일의 시작점으로 스트림 stream의 위치를 정한다. 이 함수는 반환 값이 버려지고, 스트림의 에러 지시자가 다시 고쳐지는 것을 제외하면 SEEK_SET을 whence인수에서 사용하고, 0L이 offset 인수에서 사용된 fseek함수와 동등하다. 이들 SEEK_. . . '로 시작되는 세 가지 상수들은 예전의 BSD 시스템들과 호환을 목적으로 존재한다. 그들은 두 개의 다른 헤더파일 'fnctl. h' 와 'sys/file. h'에 정의되어 있다.

L_SET : SEEK_SET의 다른 이름.

L_INCR : SEEK_CUR의 다른 이름.

L_XTND : SEEK_END의 다른 이름.


7. 16 호환성 있는 파일-위치 함수들

GNU 시스템 상에서, 파일의 위치로 문자들을 세어서 할 수 있다. 당신은 fseek의 인수로 문자 개수의 값을 정할 수도 있고, 랜덤 억세스파일에서 이해 가능한 결과를 얻는다. 그렇지만 ANSI C시스템에서는 이 방법으로 파일 위치를 표현할 수 없다. 텍스트 스트림과 바이너리 스트림이 차이를 갖고 있는 시스템들에서는, 파일의 시작점에서 문자들을 세는 것으로 텍스트 스트림의 파일 위치를 표현하는 것은 불가능하다. 예를 들어, 어떤 시스템들에서 파일 위치는 파일안의 레코드 옵셋과, 레코드의 문자 옵셋이 둘을 다 해줘야한 한다. 결과적으로, 당신이, 당신 프로그램이 이 시스템들과 호환되기를 원한다면 어떤 규칙들을 준수해야하는 것이다.

텍스트 스트림 상에서 ftell로 부터 반환되는 값은 당신이 지금까지 읽은 문자들의 수와 아무런 연관이 없다. 당신이 의지할 수 있는 유일한 것은 같은 파일 위치에서 뒤로 옮기기 위해 fseek함수에 offset인수로 사용할 수 있다는 것이다.

텍스트 스트림 상에서 fseek를 호출하려면, offset이 0이 되거나아니면 whence는 SEEK_SET으로 주고 offset는 동일한 스트림 상에서 미리 ftell호출로 얻어진 값이 되어야 한다.

텍스트 스트림에서 파일 위치의 값은 ungetc를 통해서 뒤로 밀린(pushed back)읽혀지지 않았거나, 버려진 문자들에 대한 것은 정의 되어있지 않다. 7. 8절 [Unreading] 참조

그러나 당신이 이들 규칙을 준수한다고 하더라도, 당신은 여전히 긴 파일들을 다룰 때 문제가 있는데, 그것은 ftell과 fseek가 파일 위치를 나타내는 값으로 long int 값을 사용하기 때문이다. 그 형으로는 긴 파일안의 모든 파일위치를 표현해줄 수 없다. 그래서 만일 당신이 이러한 파일위치를 나타내기 위해서 특수한 시스템 지원을 원한다면 ftell과 fseek대신에 fgetpos와 fsetpos를 사용하는 편이 좋다. 이 함수들은 시스템에서 시스템으로 내부적 표현을 바꾸는 데이터 타입 fpos_t를 사용해서 파일 위치를 나타낸다.

이 심벌들은 헤더파일 'stdio. h'에 선언되어 있다.

데이터 타입 : fpos__t

이것은 fgetpos와 fsetpos함수에 의해 사용되는, 스트림의 파일 위치에 대한 정보를 암호화할 수 있는 object의 타입니다. GNU시스템에서는, fpos_t는 off_t나 long int와 동일하지만 다른 시스템에서는, 어떤 내부적 차이를 가질 것이다.

함수 : int fgetpos (FILE *stream, fpos_t *position)

이 함수는 스트림 stream을 위해서 파일 위치 지시자의 값을 fpos_t의 형을 가진 position이 지정하고 있는 곳에 저장하는 기능을 한다. 만일 성공하면 fgetpos는 0을 반환하고, 그렇지 않으면 영이아닌 값을 반환하고 에러번호 errno를 저장한다.

함수 : int fsetpos (FILE *stream, const fpos_t position)

이 함수는 스트림을 위한 파일지시자의 값을 동일한 스트림 상에서 position위치로 설정하는데, 그 새로운 위치는 이전에 호출한 fgetpos에 의해 얻어진 값이어야 한다. 만일 성공하면, fsetpos는 스트림상의 end-of-file 지시자를 클리어 시켜 ungetc를 통한 "pushed back"동작이 불가능하게 하고, 0의 값을 반환한다. 그렇지 않으면 fsetpos는 0이 아닌 값을 반환하고 에러번호 errno를 저장한다.


7. 17 스트림 버퍼링

문자들은 어플리케이션 프로그램에 의해 출력으로 바로 나타나는 대신에 일반적으로 문자들을 모아서 스트림으로 출력하거나, 하나의 블록으로 파일에게 비동기적으로 전송된다. 유사하게, 스트림들은 종종 문자 대 문자란 원리보다는 블록으로부터 입력을 검색한다. 이것을 버퍼링이라 부른다. 만약 당신이 스트림을 사용해서 입력과 출력을 행하는 프로그램을 만들려 한다면, 당신이 당신의 프로그램에서 유저 인터페이스를 구성할 때, 버퍼링이란 것이 어떻게 작동되는지 이해할 필요가 있다. 그렇지 않으면 당신은 당신이 의도한대로 나타나지 않은 출력을 찾거나, 유저에 의해 입력된(typed) 입력이 다른 예상하지 못한 동작이거나, 단일 문자인지를 체크하고, 그것이 유용하게 되도록 만들어야한다. 이 절은 반향(echoing), 제어흐름( flow control ), 그리고 디바이스들의 정해진 부류로 취급되어지는 것과 같은 것이 아닌 파일이나 디바이스들과, 스트림 사이에 문자들이 송신되어질 때 제어하는 것을 취급하고 있다. 터미널 디바이스의 일반적 제어 명령들에 대한 정보는 12장 [Low-Level Terminal Interface]를 참조. 당신은 파일기술자에서 동작하는 저수준 입력과 출력 함수들을 사용하면 스트림 버퍼링 도구들은 무시할 수 있다.

 

7. 17. 1 버퍼링 개념들

버퍼링 방법에 대한 세 가지 종류.

  • 비버퍼화된 스트림으로부터 읽거나 쓸 문자들은 가능한 한 빨리 파일로 또는, 파일로부터 개별적으로 전송되어진다.
  • 라인 버퍼 스트림으로 부터 읽거나 쓸 문자들은 새줄 문자를 만날 때 블록단위로 파일로, 또는 파일에서 전송되어진다.
  • 완전 버퍼 스트림으로부터 읽거나 쓸 문자들은 다양한 크기의 블록 형태로 파일로, 또는 파일로부터 전송되어진다.

새로이 개방된 스트림들은 한가지를 제외하고는, 일반적으로 완전한 버퍼이다; 터미널과 같은 대화식 디바이스와 연결된 스트림은 처음에 라인 버퍼로 되어진다.

대화식(interactive) 디바이스들을 위한 라인 버퍼의 사용에서 새로운 줄로( newline ) 끝나는 출력 메시지는 보통은 즉각적으로 출력될 것이고 이것은 보통 당신이 원하는 것이다. 새로운 줄로 끝나지 않는 출력은 즉시 출력되지 않을지도 모르는데, 그래서 만일 당신이 그들이 즉각적으로 출력되길 원한다면, 당신은 fflush를 사용해서 명시적으로 버퍼된 출력을 나오게 해야한다.

라인 버퍼링은 터미널 상에서 입력을 받기에 좋은데, 그 이유는 대부분의 대화식(interactive) 프로그램들이 보통 단일한 라인으로 된 명령을 읽기 때문이다. 프로그램은 즉시 각 라인을 실행할 수 있을 것이다. 라인 버퍼 스트림은 이것을 허용하지만, 완전한 버퍼 스트림은 그것을 읽도록 프로그램에 허용되기 전에 버퍼를 채워 항상 충분한 텍스트를 읽는다. 라인 버퍼링은 대부분의 운영체제에서 라인 단위의 입력에서 작동하는 보통의 입력-편집기로 알맞다.

어떤 프로그램들은 비버퍼화된 터미널 입력 스트림이 필요하다. 이 프로그램들은 단일-문자 명령들을 읽는 프로그램과 (Emacs와 같은)그들 자신이 입력을 편집하는 프로그램들( readline을 사용하는 것들처럼)이다. 한 번에 한 문자를 읽기 위해서, 입력 스트림의 버퍼링을 멈추게 하는 것만으로 충분하지 않고, 운영체제가 행하는 입력 편집의 작용도 멈추게 해야한다. 이것은 터미널 모드를 바꾸어야 한다. ( 12. 4절 [Terminal Modes] 참조). 만일 당신이 터미널 모드를 바꾸기 원한다면, 당신은 개별적으로-단지 모드들을 변화하지 않는 비버퍼화된 스트림을 사용하여 이것을 해야만 한다.

 

7. 17. 2 버퍼를 쏟아내기

역자주: 이곳에서 쏟아내기란 단어는 버퍼에 있는 내용을 강제적(?)으로 파일로 전송시키고 버퍼를 비우는 그런 의미를 내포하고 있다.

버퍼된 스트림 상에서 출력쏟아내기(Flushing)는 버퍼에 모아진 모든 문자들을 파일로 전송하는 것을 의미한다. 다음은 스트림 상에서 버퍼화된 출력이 자동적으로 쏟아질 때의 많은 상황이다.

  • 당신이 출력을 하려 시도하고, 출력 버퍼는 가득 차있을 때.
  • 스트림이 닫혀있을 때. 7. 4절 [Closing Streams] 참조.
  • 프로그램이 exit를 호출하여 종료될 때. 22. 3. 1절 [Normal Termination] 참조.
  • 만일 스트림이 라인 버퍼라면, 새줄기호를 만났을 때

파일로부터 데이터를 실제적으로 읽는 어느 스트림 상에서 입력 명령이 있을 때이다. 만일 당신이 다른 때(역자주 : 위의 상황이 아닌경우) 버퍼된 출력을 쏟아내길 원한다면, fflush를 호출하라,

그것은 헤더파일 'stdio. h'에 선언되어 있다.

함수 : int fflush (FILE *stream)

이 함수는 스트림 상에서 버퍼화된 어떤 출력을 파일로 배달하는 역할을 한다. 만약 스트림이 널 포인터라면 fflush는 열려진 모든 출력 스트림의 버퍼화된 출력을 강제로 관련 파일에 내보낸다. 이 함수는 만약 쓰기 에러가 발생하면 EOF를 반환하고, 아니면 0을 반환한다.
호환성 노트 : 어떤 머리가 모자라는( 음. . . 이 표현이 가장 적당한 것 같다. 키키키. . . ) 운영체제는 새줄기호를 만나면 라인 버퍼된 스트림을 쏟아내는 라인-지향 입력과 출력으로 완전히 고정시켜 놓은 것으로 알려져 있다! 하지만, 다행스럽게도 이 "특징"은 일반적인 현상은 아닌 것 같다. 당신은 이 GNU 시스템에 대해서는 아무런 걱정할 필요가 없다.

 

7. 17. 3 버퍼링의 종류 제어하기

스트림이 개방된 직후에( 그러나 그 스트림에 어떤 다른 명령도 수행되지 않았다. ), 당신은 setvbuf 함수를 사용해서 당신이 원하는 버퍼링의 종류를 명시적으로 지정할 수 있다. 이 절에 리스트된 도구들은 헤더파일 'stdio. h'에 선언되어 있다.

함수: int setvbuf (FILE *stream, char *buf, int mode, size_t size)

이 함수는 스트림인 stream을 버퍼링 모드인 mode를 갖도록 정하는데 사용한다. mode에는 _IOFBF(완전한 버퍼링), _IOLBF(라인 버퍼링), 이나 _IONBF( 비버퍼화된 입/출력을 위해 )들 중에서 사용할 수 있다. 만일 당신이 buf인수에 널 포인터를 정하면, setvbuf는 malloc를 사용하여 스스로 버퍼를 할당한다. 이 버퍼는 당신이 스트림을 닫을 때 해제된다. 그렇지 않으면, buf는 적어도 문자열을 저장할 수 있는 문자배열이 되어야 한다. 당신은 그 스트림이 열려진 상태로 있고, 이 배열이 버퍼 안에 남아있는 동안은 이 배열을 위해 할당된 공간을 해제할 수 없다. 당신은 버퍼를 정적으로 할당하거나, molloc을 사용해야 한다. ( 3. 3. 절 [Unconstrained Allocation] 참조].
 
자동 배열을 사용하는 것은 배열을 선언한 블록이 존재하기도 전에 그 파일이 닫힐 수도 있으므로 좋은 생각이 아니다. 배열이 스트림 버퍼에 남아있는 동안 스트림 입/출력 함수는 그들의 내부의 목적들로 버퍼를 사용할 것이다. 당신은 스트림이 버퍼링을 위해 사용되고 있는 동안에는 직접적으로 그 배열의 값을 억세스하려 시도할 수 없다. setvbuf함수는 성공하면 0을 반환하고, mode의 값이 유용하지 않거나, 그 요청이 받아들여질 수 없으면 0이 아닌 값을 반환한다.

매크로 : int __IOFBF

이 매크로의 값은 정수 상수 표현으로 스트림을 완전한 버퍼로 정하기 위해서, setvbuf 함수에서 mode 인수에서 사용되어질 수 있다.

매크로 : int __IOLBF

이 매크로 값은 스트림을 라인 버퍼로 정하기 위해서 setvbuf 함수에서 mode 인수로서 사용되어질 수 있는 정수 값의 상수 표현이다.

매크로 : int __IONBF

이 매크로 값은 스트림을 비버퍼화로 정하기 위해 setvbuf함수에서 mode 인수로 사용되어질 수 있는 정수 값의 상수 표현이다.

매크로 : int BUFSIZ

이 매크로 값은 setvbuf 함수에서 size 인수로 사용되기에 좋은 정수 값의 상수 표현이다. 이 값은 적어도 256을 보장한다. BUFSIZ의 값은 입/출력 스트림을 능률적으로 만들기 위해서 각 시스템에 따라 선택된다. 그래서 당신이 setvbuf를 호출할 때 버퍼를 위한 크기로 BUFSIZ을 사용하는 것이 좋다. 실제로, 당신은 fstat( 역자주: 앙~ 뭔지 모르겠다. ) 시스템 호출의 방법으로 버퍼 크기에 사용하기 위해 더 좋은 값을 얻을 수 있다. 이것은 파일 속성의 st_blksize 필드에서 발견되어진다.
9. 8. 1절 [Attribute Meanings] 참조. 때때로 사람들은 또한 fgets( 7. 6절 [Character Input] 참조)를 사용해서 입력의 한 라인을 받아들이는데 사용되는 문자열(strings)처럼, 연관된 목적을 위해 사용하는 버퍼들의 할당 크기로 BUFSIZ를 사용한다. 효과적인 덩어리로 입/출력을 행하기 위한 것을 제외하고는, 어느 다른 정수 값 대신에 BUFSIZ을 사용할 특별한 이유가 아무 것도 없다.

함수 : void setbuf (FILE *stream, chr *buf)

만일 buf가 널 포인터라면 이 함수를 사용한 효과는 _IONBF의 모드 인수를 사용해서 setvbuf를 호출한 것과 동등하다. 그렇지 않다면, _IOFBF의 모드 인수과 BUFSIZ의 크기 인수를 사용한 buf로 setvbuf를 호출한 것과 동등하다. 그 setbuf 함수는 오래된 코드와의 호환성을 위해 제공되고 있다. ; 새로운 프로그램들은 모두 setvbuf를 사용하라.

함수 : void setbuffer (FILE *stream, char *buf, size_t size)

만일 buf가 널 포인터라면, 이 함수는 비버퍼화된 스트림을 만든다. 그렇지 않다면 이 함수는 버퍼로서 완전한 버퍼화된 스트림을 만든다. size인수는 buf의 길이를 정한다. 이 함수는 오래된 BSD 코드와의 호환성 때문에 제공되고 있다. 대신에 setvbuf를 사용하라.

함수 : void setlinebuf (FILE *stream)

이 함수는 라인 버퍼된 스트림을 만들고, 버퍼를 할당한다. 이 함수는 오래된 BSD 코드와의 호환을 위해 제공되고 있다. setvbuf를 사용하라.


7. 18 다른 종류의 스트림

GNU 라이브러리는 개방된 파일과 교류하는 것이 필요하지는 않은 스트림의 부가적인 종류를 정의하는 방법을 제공하고 있다.

스트림의 어떤 타입은 한 문자열로부터 입력을 취하거나, 문자열에 출력을 쓴다. 스트림의 이러한 종류들은 sprintf와 sscanf 함수들을 위해 내부적으로 사용되어진다. 당신은 또한 7. 18. 1절 [String Streams] 88에서 설명하고 있는 함수들을 사용해서, 명시적으로 이러한 스트림을 만들 수 있다. 더 일반적으로, 당신은 당신의 프로그램에서 제공되는 함수들을 사용해서 다양한 objects에 입출력을 행하는 스트림들을 정의할 수 있다. 이 프로토콜은 7. 18. 3절 [Custom Streams] 100에서 논의되고 있다.

 

호환성 노트 : 이 절에서 설명하고 있는 도구들은 GNU로 정해진다. 다른 시스템이나 C 작동들은 동등한 함수들을 제공하거나, 혹은 제공하지 않을 수 있다.

 

7. 18. 1 문자열 스트림

fmemopen 과 open_memstream 함수들은 당신이 문자열이나 메모리 버퍼로 입출력을 행하는 것을 허용하는 함수이다. 이 도구들은 'stdio. h'에 선언되어 있다.

함수 : FILE * fmemopen (void *buf, size_t size, const char *opentype)

이 함수는 buf 인수에 의해 정해진 버퍼에서 읽거나 쓰기 위해, 정해진 opentype 인수로 억세스 하는걸 허용하는 스트림을 열어준다.
 
이 배열은 적어도 long 바이트의 크기를 가져야만 한다. 만일 당신이 buf 인수로 널 포인터를 정하면, fmemopen은 long 바이트 크기의 배열을 동적으로 할당한다. ( malloc을 사용해서; 3.3절 [Unconstrained Allocation] 참조). 이것은 버퍼에 어떤 것을 쓰고나서 다시 뒤로 가서 그들을 읽으려할 때 유용한 함수다, 왜냐하면 당신은 버퍼로부터 포인터를 얻을 수 있는 아무런 방법을 가지고 있지 않기 때문이다. (이것을 위해, open_memstream, 아래). 이 버퍼는 스트림을 열었을 때 freed된다.
 
opentype인수는 fopen과 같다( 7.3절 [Opening Streams] 참조) 만일 opentype이 append 모드로 정해진다면, 처음의 파일 위치는 파일에서 첫 번째 널 문자에 위치된다. 그렇지 않으면 파일 처음 위치는 버퍼의 시작점이다. 쓰기 위하여 개방된 스트림이 플러쉬(flushed-위에 이 개념이 있습니다. 히. . )되어지거나 닫혀질 때, 널 문자는( 0 바이트 ) 만일 그것이 적당하다면 버퍼의 끝에 쓰여 진다.
 
당신은 이것을 셈하기 위해서 size인수에 여분의 바이트를 더할 수 있다. 버퍼에 size보다 더 많이 쓰기를 시도하면 에러의 결과에 이른다. 읽기 위해 개방된 스트림에서, 버퍼안의 널 문자들은(0 바이트) "파일의 끝"으로 간주되지 않는다. 파일의 위치가 size를 넘겨 진행될 때, 파일의 끝임을 지적한다. 그래서 당신은 만일 당신이 널 종료문자로 끝나는 문자열을 읽기 원한다면, 당신은 size인수로 문자열의 길이를 공급해야한다.
 
이곳에 문자열을 읽기 위해 fmemopen을 사용해서 스트림을 만드는 예가 있다.
#include <stdio. h>
static char buffer[] = "foobar";
 
int main (void)
{
int ch;
FILE *stream;
 
stream = fmemopen (buffer, strlen (buffer), "r");
while ((ch = fgetc (stream)) != EOF)
printf ("Got %c\n", ch);
fclose (stream);
 
return 0;
}
 
이 프로그램은 다음과 같은 결과를 낸다.
 
Got f
Got o
Got o
Got b
Got a
Got r
/* 왜 이런 결과가 나왔는지 아시죠? */

함수 : FILE * open__memstream (char **ptr, size_t *sizeloc)

이 함수는 버퍼에 쓰기 위해 스트림을 연다. 그 버퍼는 동적으로 할당되고 ( malloc을 사용해서. 3. 3절 [Unconstrained Allocation] 참조)필요에 따라 늘린다. 스트림이 fclose를 사용하여 닫히거나, fflush를 사용하여 플러쉬될 때 ptr의 위치와 sizeloc는 버퍼의 포인터를 포함하고 있는 곳과, 그 크기로 갱신된다. 그 값은 스트림에 길이만큼 출력으로 저장된다. 만일 당신이 더 출력하기 원한다면, 당신은 그들을 다시 사용하기 전에 새로운 값을 저장하기 위해서 스트림을 플러쉬(flush)해야한다. 널 문자는 버퍼의 끝에 쓰여 진다. 이 널 문자는 sizeloc에 저장되어진 size값에 포함되지 않는다. 당신은 fseek( 7. 15절 [File Positioning] 참조)를 사용하여 스트림의 파일 위치(stream's file position)를 옮길 수 있다. 파일 위치를 파일의 끝을 지나서 옮기면 그 공백들은 영으로(zeros) 채워져 쓰여진.
 
이곳에 open_memstream을 사용한 예가 있다
#include <stdio. h>
 
int main (void)
{
char *bp;
size_t size;
FILE *stream;
 
stream = open_memstream (&bp, &size);
fprintf (stream, "hello");
fflush (stream);
printf ("buf = %s, size = %d\n", bp, size);
fprintf (stream, ", world");
fclose (stream);
printf ("buf = %s, size = %d\n", bp, size);
return 0;
}
 
이 프로그램은 다음과 같은 결과를 낸다.
buf = `hello', size = 5
buf = `hello, world', size = 12

 

7. 18. 2 Obstack 스트림

당신은 obstack에 데이터를 넣는 출력 스트림을 개방할수 있다. 3. 4절 [Obstacks] 를 참조하라.

함수 : FILE * open__obstack__stream (struct obstack *obstack)

이 함수는 obstack에 데이터를 쓰기 위하여 스트림을 개방한다. 이것은 obstack안에서 object를 시작하고, 쓰여진 데이터에 따라 늘려서 만든다. (3.4.6절 [Growing Objects] 참조) 이 스트림 상에서 fflush를 호출하면 쓰여진 데이터의 양에 맞추어서 object의 현재의 크기를 갱신한다. fflush를 호출한 후에, 당신은 일시적으로 object를 시험할 수 있다. 당신은 fseek(7.15절 [File Positioning] 참조)를 사용하여 obstack 스트림의 파일 위치를 옮길 수 있다. 파일의 끝을 지난곳으로 파일 위치를 옮기면 영(0)으로 공백들이 채워 저장된다.
변하지 않는 object를 만들려면, fflush를 사용하여 obstack을 갱신하고, 그 다음 object를 결말짓기 위해 obstack_finish를 사용하고, 그 주소를 얻어라. 그 스트림에 쓴 다음에 obstack에 새로운 object를 시작하면 당신이 다른 fflush와 obstack_finish를 사용하여 그 object에 더하여 쓸 수 있다. 그러나 당신이 object의 길이가 얼마인지 어떻게 알것인가? 당신은 obstack_object_size( 3.4.8절 [Status of an Obstack] 참조)를 호출함으로써 바이트 단위로 그 길이를 얻을 수 있거나 이와 같이 object를 널 종료시킬 수 있다.
 
obstack_1grow (obstack, 0);
 
당신이 무엇을 하던 지간에, obstack_finish를 호출하기 전에 그것을 해야 한다. ( 당신이 원하면 양쪽 다 할 수 있다. )
 
여기에 open_obstck_stream 함수의 예제가 있다.
char *make_message_string (const char *a, int b)
{
FILE *stream = open_obstack_stream (&message_obstack);
output_task (stream);
fprintf (stream, ": ");
fprintf (stream, a, b);
fprintf (stream, "\n");
fclose (stream);
obstack_1grow (&message_obstack, 0);
return obstack_finish (&message_obstack);
}

 

7. 18. 3 프로그래밍한 당신 자신만의 주문 스트림

이 절은 다양한 데이터 소스로부터 입력을 얻는 스트림이나, 당신에 의해 프로그램된 다양한 데이터 저장고(sink)에 출력을 쓰는 스트림을 어떻게 만들 수 있는지에 대한 설명이 있다. 우리는 이것을 주문(custom) 스트림이라고 부른다.

 

7. 18. 3. 1 주문 스트림과 Cookies

모든 주문 스트림 내부에는 cookie라고 불리는 특별한 object가 있다. 이것은 읽거나 쓴 데이터를 추출하거나 저장하기 위한 레코드들로 당신에 의해 제공되는 오브젝트이다. 당신에게 cookie에 사용될 데이터 타입을 정의하도록 한다. 라이브러리 안의 스트림 함수들은 결코 그 안의 내용을 직접적으로 참조하지 않고, 그들은 심지어 그것이 무슨 타입인지 알지 못한다; 그들 레코드의 주소는 void * 타입으로 저장되어 있다.

주문 스트림을 도구로 하려면, 당신은 정해진 위치에 데이터를 어떻게 저장하거나, 추출할 것인지 정해줘야만 한다. 당신은 읽거나 쓰거나, "파일 위치"를 변경하거나, 스트림을 닫거나 하는 후크(hook) 함수들을 정의함으로써 이것을 한다. 이 네 개의 함수들에 스트림 cookie가 전달되어지면 그들은 추출하거나 저장할 데이터의 위치를 알려줄 수 있다. 라이브러리 함수는 cookie안에 무엇이 있는지 알지 못하지만, 당신의 함수들은 알 것이다.

당신이 주문 스트림을 만들 때, 당신은 cookie 포인터를 정해 주어야만하고, 또한 네 개의 후크 함수들은 struct cookie_io_functions의 타입으로 구조체 안에 저장한다.

이 도구들은 'stdio. h'에 선언되어 있다.

데이터 타입 : struct cookie__io__functions

이것은 스트림과 cookie 사이의 통신 프로토콜을 정의하는 함수를 유지하기 위한 구조체 타입이다. 다음과 같은 멤버를 가지고 있다.

cookie_read_function *read

이것은 cookie로부터 데이터를 읽기 위한 함수이다. 만약 그 값이 함수 대신에 널 포인터라면, 스트림 상에서 읽기 동작들은 항상 EOF를 반환한다.

cookie_write_function *write

이것은 cookie에 데이터를 쓰기 위한 함수이다. 만약 그 값이 함수대신에 널 포인터라면, 스트림에 쓰여진 데이터는 버려진다.

cookie_seek_function *seek

이것은 cookie에 파일 위치시키기와 같은 동작들을 수행하는 함수이다. 만약 그 값이 함수대신에 널 포인터라면, 이 스트림에서 fseek를 호출하면, 오직 버퍼안의 위치만 찾을 수 있다; 버퍼의 밖에서 찾으려는 어떤 시도는 ESPIPE 에러를 반환할 것이다.

cookie_close_function *close

이 함수는 스트림이 닫힐 때 cookie에 적당한 정리작업을 수행한다. 만약 그 값이 함수대신에 널 포인터라면, 스트림이 닫힐 때 어떤 특별한 일을 행함이 없이 cookie를 닫는다.

함수 : FILE * fopencookie (void *cookie, const char *opentype, struct cookie_functions io_functions)

이 함수는 io_functions 인수 안에 있는 그 함수들을 사용해서 cookie와 통신하기 위한 스트림을 실제로 생성한다. opentype 인수는 fopen에서 사용되는 것과 같다. 7. 3절 [Opening Stream]를 참조하라. ( 하지만 "truncate on open" 옵션은 무시됨을 기억하라. 새로운 스트림은 완전히 버퍼 된다. fopencookie 함수는 새로이 생성된 스트림을 반환하거나, 아니면 에러가 발생한 경우에 널 포인터를 반환한다.

 

7. 18. 3. 2 주문 스트림 후크 함수들

주문 스트림이 필요로 하는 네 개의 후크 함수들을 정의하는 방법에 대해 자세히 설명한다.

당신은 cookie로부터 데이터를 읽기 위한 함수를 다음처럼 정의할 수 있다.

ssize_t reader (void *cookie, void *buffer, size_t size)

이것은 read 함수와 매우 유사하다; 8. 2절 [I/O Primitives] 참조하라. 당신의 함수는 버퍼 안에서 size 바이트를 참조하고, 읽은 바이트의 수를 반환하거나, 아니면 end-of-file을 지시하기 위해 0을 반환한다. 당신은 에러가 발생한 경우에 -1의 값을 반환할 수 있다. 당신은 cookie에 데이터를 쓰기 위한 함수를 다음처럼 정의할 수 있다.

ssize_t writer (void *cookie, const void *buffer, size_t size)

이것은 write함수와 매우 유사하다; 8. 2절 [I/O Primitives] 참조하라. 당신의 함수는 버퍼로부터 size 바이트를 참조하고, 씌어진 바이트의 수를 반환한다. 당신은 에러를 지적하기 위해 -1의 값을 반환할 수 있다.

당신은 cookie에 검색 명령을 수행하기 위한 함수를 다음처럼 정의할 수 있다.

int seeker (void *cookie, fpos_t *position, int whence)

이 함수에서, position과 whence 인수들은 fgetpos처럼 해석되어진다; 7. 16절 [Portable Positiong]참조하라. GNU 라이브러리에서는, fpos_t 타입은 off-t나 long int와 동등하고, 파일의 시작점으로 부터 바이트의 수를 표현한다. 검색 오퍼레이션이 실행된 이후, 당신의 함수는 파일의 시작점을 기준으로 한 파일의 위치를 결과로 하여 저장할 것이다. 당신의 함수는 성공하면 0을 반환하고, 아니면 실패일 경우 -1을 반환한다.

당신은 스트림을 닫을 경우 cookie에 적당한 명령을 주어서 정리 작업(cleanup)을 하기 위한 함수를 다음처럼 정의할 수 있다.

int cleaner (void *cookie)

당신의 함수는 성공하면 0, 그렇지 않으면 에러를 지적하기 위해 -1을 반환한다.

데이터 타입 : cookie __read__function

이것은 주문 스트림을 위한 읽기 함수의 데이터 타입이다. 만일 당신이 위에 보여준 것처럼 함수를 선언하면, 그때 그 함수가 갖는 데이터 타입이다.

데이터 타입 : cookie__write__function

주문 스트림을 위한 쓰기 함수의 데이터 타입

데이터 타입 : cookie__seek__function

주문 스트림을 위한 검색 함수의 데이터 타입.

데이터 타입 : cookie__close__function

주문 스트림을 위한 종료 함수의 데이터 타입


목차 이전 : 6. 입출력 개요 다음 : 8. 저수준 입출력

'Programming > Unix Programing' 카테고리의 다른 글

9. 파일시스템 인터페이스  (0) 2007.12.22
8. 저수준 입출력  (0) 2007.12.22
7. 스트림의 입출력  (0) 2007.12.22
6. 입출력 개괄  (0) 2007.12.22
5. 문자열과 배열 유틸리티  (0) 2007.12.22
4. 문자 처리  (0) 2007.12.22
Comment 0 Trackback 0
Top

6. 입출력 개괄

목차 이전 : 5. 문자열과 배열 유틸리티 다음 : 7. 스트림에서의 입출력


6 입출력 개요

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

7장 [스트림 입출력]

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

8장 [저수준 입출력]

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

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

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

10장 [파이프와 FIFO]

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

11장 [소켓]

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

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

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

6.1 입출력의 개념

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


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 체제에서는 당신은 이러한 제한에 대해 염려할 필요가 없다; 널 문자를 제외한 어떠한 문자든지 파일명칭 문자열에 허용되며, 파일명칭 문자열 길이에 대한 제한도 없다.


목차 이전 : 5. 문자열과 배열 유틸리티 다음 : 7. 스트림에서의 입출력

'Programming > Unix Programing' 카테고리의 다른 글

8. 저수준 입출력  (0) 2007.12.22
7. 스트림의 입출력  (0) 2007.12.22
6. 입출력 개괄  (0) 2007.12.22
5. 문자열과 배열 유틸리티  (0) 2007.12.22
4. 문자 처리  (0) 2007.12.22
3. 메모리 점유  (0) 2007.12.22
Comment 0 Trackback 0
Top

5. 문자열과 배열 유틸리티

목차 이전 : 4. 문자 다루기 다음 : 6. 입출력 개요


5 문자열과 배열 유틸리티

문자열(또는 문자의 배열) 처리는 많은 프로그램에 있어 중요한 부분이다.GNU C 라이브러리는 많은 문자열 유틸리티 함수를 제공하는데, 여기에는 문자열의 복사, 연결, 비교, 검색을 위한 함수들이 포함되어 있다. 많은 문자열 처리함수들은 저장소의 임의의 구역을 처리할 수 있다; 예컨대, memcpy 함수는 어떠한 종류의 배열 내용도 복사해 낼 수 있다.

초보 C 프로그래머들이 자신의 코드로 이 기능을 중복되게 작성하여 "도구를 재발명"하는 일은 너무도 흔한 일이다. 그러나 그렇게 함으로써 라이브러리 함수들에 친숙해지고 그것들을 사용할 수 있게되므로, 이러한 일은 유지, 효율성, 운용에 이점을 제공하는 셈이다.

예컨대, 당신은 2행의 C 코드에서 하나의 문자열을 다른 문자열과 쉽게 비교할 수 있다. 만약 당신이 기존의 strcmp함수를 사용한다면 실수를 줄일 수 있다. 또한 이러한 라이브러리 함수들은 전형적으로 매우 최적화 되어 있으므로 당신의 프로그램은 빠르게 실행될 수도 있다.


5.1 문자열의 표현

이 절에서는 초보 C 프로그래머들을 위해 문자열의 개념을 간명하게 요약하였다.C에서 문자열이 어떻게 표현되며 흔히 있을 수 있는 함정은 무엇인가를 기술해 놓았다. 만약 당신이 이미 이러한 내용들을 잘 알고 있다면, 이 절을 건너뛰어도 좋다.

문자열은 char 대상물의 배열이다. 그러나 문자열 값을 가지는 변수들은 항상 문자포인터(char *) 형태로 선언된다. 그러한 포인터 변수들은 어떤 문자열의 내용물을 위한 공간을 포함하고 있지 않다; 그 내용물은 어떤 다른 곳에 저장된다_즉, 어떤 배열 변수, 문자열 상수, 또는 동적으로 할당된 메모리 (3장.[메모리 할당] 참조)에 저장된다. 선택된 메모리 공간의 주소를 포인터 변수에 저장하는 일 당신의 몫이다. 선택적으로 당신은 포인터 변수에 널(null) 포인터를 담을 수도 있다. 이 널 포인터는 어떠한 곳도 가리키지 않는 것이므로 그것이 가리키는 문자열을 참조하려고 시도한다면 에러를 만날 것이다.

관행적으로 널 문자 '\0'은 문자열의 끝을 가리킨다. 예를 들어, char형 포인터 변수 p가 문자열의 끝을 가리키는 널 문자를 가리키는지 아닌지를 알아보고 싶으면, !*p 또는 *p == '\0'이라고 써보아라. 널 문자는 개념상으로 널 포인터와는 아주 다르다. 물론 그것들이 모두 정수 0으로 표현되기는 하지만.

C 프로그램 소스에서 문자열 리터럴은 이중 인용부호('"')사이에 있는 문자들의 문자열로서 나타난다.ANSI C에서는 문자열 리터럴은 문자열의 연결에 의해서 만들어질 수도 있다."a" "b"는 "ab"와 같다. 문자열 리터럴의 변경은 GNU C 컴파일러에서는 허용되지 않는다. 왜냐하면, 리터럴은 읽기 전용의 저장소에 위치하기 때문이다. <역자주석>리터럴:C 명령문 상에서 직접 입력하는 데이터 const로 선언된 문자 배열들 역시 변경이 금지된다. 변경금지의 문자열 포인터를 const char * 형태로 선언하는 것은 일반적으로 훌륭한 방식이다. 이렇게 하면, C 컴파일러는 당신의 프로그램이 그 문자열을 갖고서 무엇을 하고자 하는지에 관한 정보를 상당히 얻을 수 있으며, 또한 그 문자열의 우연한 변경에 대해 조사할 수 있게 된다.

문자 배열에 할당된 메모리의 양은 문자열의 끝 부분임을 통상적으로 표시하는 널 문자를 넘어서는 곳까지 확장될 수도 있다. 이 책에서는 할당크기(allocation size)라는 용어는 항상 문자열에 할당된 메모리의 총량을 가리키게 될 것이고, 반면에 길이(length)라는 용어는 종료시키는 널 문자까지의(포함하지는 않음) 문자의 개수를 가리키게 된다.

악명 높은 프로그램 소스의 버그는 문자열에서 그것의 적합한 크기보다 더 많은 문자를 집어넣으려고 하는 것이다. 문자열을 확장하고 이미 할당된 배열에 문자를 이동시키는 소스를 쓸 때, 당신은 항상 신중하게 텍스트의 길이를 추적해야 하며 배열이 넘치지 않는지를 분명하게 체크하여야한다. 많은 라이브러리 함수들이 알아서 이것을 해주지는 않는다! 그리고 당신은 문자열의 끝을 나타내는 널 문자를 담아둘 여분의 한 바이트를 할당해야함을 잊지 말아야 한다.


5.2 문자열과 배열 규정

이 장에서는 임의의 배열이나 메모리 블럭에서 작동되는 함수들과 널로 종료되는 문자의 배열에 한정되어 사용되는 함수들에 대해 설명한다. 임의의 배열이나 메모리 블럭을 처리하는 함수들은 'mem'(memcpy처럼)으로 시작되는 명칭을 가지며 항상 처리할 메모리 블럭의 크기를(바이트로) 지정하는 인수를 갖는다. 이 함수들의 배열 인수와 그 반환 값은 void * 형태를 가지며, 형식상 이러한 배열의 요소들은 "바이트"로서 참조된다. 당신은 이 함수들에게 어떤 종류의 포인터를 전달할 수 있으며, size 인수의 값을 계산하는데는 sizeof 연산자가 유용하다.

대조적으로, 특별히 문자열을 처리하는 함수들은 'str'로 시작하는(strcpy와 같이) 명칭을 가지며, 분명한 크기의 인수를 전달받기를 요구하는 대신에 문자열을 종료시키는 널 문자를 요구한다.(이러한 함수들 중에 어떤 것들은 특정한 최대 길이를 받아들이지만, 역시 널 문자를 써서 사전 종료를 체크하고 있다.) 이러한 함수들의 배열 인수와 반환 값은 char * 형태를 가지며, 배열 요소들은 "문자들"로서 참조된다.

많은 경우에 한 함수에는 'mem' 버전과 'str' 버전이 모두 갖춰져 있다. 그 중의 한편의 함수는 훨씬 더 적합하게 사용할 수 있으나 정확한 상황을 요구한다. 만약 당신의 프로그램이 임의의 배열이나 저장소의 블럭을 처리하려 한다면, 당신은 'mem'함수들을 쓰는 편이 좋다. 반면에, 당신이 사전에 문자열의 길이를 알지 못한 상태에서 널로 끝나는 문자열을 처리할 때는 str'함수들을 사용하는 편이 편리하다.


5.3 문자열 길이

당신은 strlen 함수를 사용하여 문자열의 길이를 구할 수 있다.

이 함수는 'string.h'에 선언되어 있다.

함수 size_t strlen (const char *s)

함수 strlen은 널로 종료되는 문자열 s의 길이를 반환한다.(다른 말로 표현하면, 그것은 배열내의 종료하는 널문자의 오프셋을 반환한다.)
예를 들면,
strlen ("hello, world")
한 문자의 배열에 적용해보면, strlen 함수는 그곳에 저장된 문자열의 길이를 반환하는 것이지, 그것의 할당 크기를 반환하는 게 아니다. 문자의 할당 크기는 sizeof 연산자를 사용하여 구할 수 있다.
char string[32] = "hello, world";
/*위의 배열에는 31개의 문자와 종료 널 문자 1개를 담을 수 있다*/
sizeof (string)
) 32 /*할당된 배열의 크기는 32*/
strlen (string)
) 12 /*실제의 문자열의 길이는 12*/

5.4 (문자열) 복사와 결합

이 절에서 서술해 놓은 함수들은 문자열과 배열을 복사하거나 한 문자열을 다른 문자열에 결합할 때 사용할 수 있다. 이 함수들은 'string.h'에 선언되어 있다.

이 함수들의 인수 기입 순서를 외우는 방법은 소스 배열의 왼편에 목적 배열을 두어서 할당 표현과 일치시키는 것이다. 이러한 함수들 전부가 목적 배열의 주소를 반환한다.

이 함수들의 대부분은 소스 배열과 목적 배열이 중첩될 경우에 제대로 작동하지 않는다. 예를 들면, 목적 배열의 시작 부분이 소스 배열의 끝 부분과 중첩되게 되면, 소스 배열 중의 그 부분의 내용이 복사되기 전에 덮이게 된다. 더욱 심각한 것은, 문자열 함수의 경우에, 문자열의 끝을 나타내는 널문자가 상실되고, 그 복사 함수는 당신의 프로그램에 할당된 모든 메모리를 돌아다니는 루프에 빠져버릴 것이다.

중첩된 배열사이에서 복사하는 문제를 갖는 모든 함수들이 이 안내서에서 분명하게 지적되어있다. 이 절의 함수들뿐만 아니라 sprintf(7.9.7 [형식화된 출력 함수] 참조.)나 scanf(7.11.8 [형식화된 입력 함수] 참조.)와 같은 몇몇 함수들이 있다.

함수 void * memcpy (void *to, const void *from, size_t size)

memcpy 함수는 from에서 시작되는 대상물로부터 to로 시작되는 대상물로 size 바이트를 복사한다. 만약 2개의 배열 from과 to가 중첩될 경우의 행위는 정의되어있지 않다; 중첩될 경우에는 이 함수 대신에 memmove를 사용하라. memcpy가 반환하는 값은 to의 값이다. 어떤 배열의 내용물을 복사하기 위해 memcpy 함수를 사용하는 예를 들어보자:
struct foo *oldarray, *newarray;
/*foo라는 명칭의 struct 가 2개의 포인터에 할당되고*/
int arraysize;
. . .
memcpy (new, old, arraysize * sizeof(struct foo));
/* old의 내용을 new에 <struct 크기 * 특정숫자>크기만큼 복사하라는 것*/

함수 void * memmove(void *to, const void *from, size_t size)

memmove는 2개의 블럭 공간이 중첩될 경우에조차도 from에서 size 바이트를 to의 size 바이트에 복사한다. 중첩이 있을 때, memmove는 주의 깊게 from블럭에 있는 바이트에서 최초의 값을 복사하는데, 거기에는 to블럭에 속하는 바이트도 포함되어 있기 때문이다.

함수 void * memccpy(void *to, const void *from, int c, size_t size) Function

이 함수는 from에서 to로 정확히 size 바이트만큼만 복사하고,c에 일치하는 한 바이트가 발견되면 중지한다. 반환 값은 c가 복사된 곳 바로 다음의 바이트에 대한 포인터이며, 만일 from의 최초의 size 바이트에 c에 일치하는 바이트가 없으면 널 포인터를 반환한다.

함수 void * memset (void *block, int c, size_t size)

이 함수는 c의 값을(unsigned char로 변환하여) block 에서 시작되는 대상물의 최초의 size 바이트의 각 부분에 복사한다. 이 함수는 블럭의 값을 반환한다.

함수 char * strcpy (char *to, const char *from)

이 함수는 문자열 from(종료하는 널 문자까지 포함하여)으로부터 문자열 to로 문자들을 복사한다.memcpy처럼 문자열이 중첩되게 되면 이 함수도 알 수 없는 결과를 초래한다. 반환 값은 to의 값이다.

함수 char * strncpy (char *to, const char *from, size_t size)

이 함수는 strcpy와 비슷하지만 항상 정확히 size 바이트만큼만 to에 복사한다. 만약 from의 길이가 size보다 크면, 이 함수는 앞부분의 size 바이트를 복사한다. 만약 from의 길이가 size보다 작으면, 이 함수는 from의 모든 것을 복사하고 그 크기만큼의 공백을 널 문자로 채운다. 이와 같은 행위는 별로 유용하지 않지만 ANSI C 표준에 의해 규정된 사항이다.
 
문자열이 중첩될 경우의 strncpy의 행위는 정의되어 있지 않다.strcpy와는 다르게 strncpy를 사용하는 것이 to의 할당된 공간의 끝 부분 이후에 널 문자를 채움으로써 버그를 피하는 수단이 된다. 그러나, 어떤 경우에는 프로그램의 실행을 느리게 하기도 한다: 큰 버퍼에 작은 문자열을 복사하는 경우이다. 이런 경우에는, size가 너무 커서 strncpy가 널 문자를 채우는 시간이 상당히 낭비되는 것이다.

함수 char * strdup (const char *s)

이 함수는 널로 종료되는 문자열 s를 새롭게 할당되는 문자열로 복사한다. 문자열은 malloc을 써서 할당된다. 3.3 [제한 없는 할당] 참조.
만약 malloc이 새로운 문자열을 위한 공간을 할당할 수 없게되면,strdup는 널 포인터를 반환한다. 그렇지 않으면 이 함수는 새로운 문자열에 대한 포인터를 반환한다.

함수 char * stpcpy (char *to, const char *from)

이 함수는 strcpy와 같으나 한가지가 다르다. 이 함수는 시작부분의 포인터가 아니라 문자열 to의 끝 부분에 대한 포인터(즉, 종료하는 널문자의 주소)를 반환한다.
예를 들면, 다음 프로그램은 stpcpy 함수를 사용해서 'foobar'를 만들기 위하여 'foo'와 'bar'를 결합하였고, 그런 다음에 그것을 출력해 보았다.
#include <string.h>
#include <stdio.h>
 
int main (void)
{
char buffer[10];
char *to = buffer; /*버퍼의 값을 to에 넣고?*/
to = stpcpy (to, "foo"); /*문자열 "foo"를 to에 복사*/
to = stpcpy (to, "bar"); /*문자열 "bar"를 "foo"가 담긴 to에 복사*/
puts (buffer); /*버퍼의 문자열을 꺼내보면, "foobar"가 되었겠지?*/
return 0;
}
 
이 함수는 ANSI나 POSIX 표준의 일부분이 아니며, 유닉스 체제에서 일상적인 것도 아니지만, 우리가 이것을 만들어 낸 것도 아니다. 아마도 이것은 MS-DOS(?)에서 만들어진 것 같다. 문자열이 중첩될 경우의 행위도 정의되어 있지 않다.

함수 char * strcat (char *to, const char *from)

strcat 함수는 strcpy와 유사하지만 덮어쓰는 대신에 from의 문자들을 to의 끝에 결합하거나 추가하는 점만 다르다. 즉,from의 처음 문자가 to의 끝 부분을 나타내는 널 문자를 덮어쓰게 된다.
 
strcat와 같은 정의는 아마 이렇게 될 것이다:
char * strcat (char *to, const char *from)
{
strcpy (to + strlen (to), from);
/*strcpy(to,from);은 from이 to의 기존내용을 덮어쓰지만, 위의 것은 to의 기존주소에다 to의 문자열 길이 만큼을 더한 주소에 from을 복사하므로,to의 널 문자를 제외한 기존 문자열이 그대로 살아남는다는 뜻?*/
return to; /*주소가 반환되남?*/
}
 
이 함수에도 문자열이 중첩될 경우가 정의되어 있지 않다.

함수 char * strncat (char *to, const char *from, size_t size)

이 함수는 strcat와 같으나,from의 size 문자만큼만 to의 끝 부분에 추가된다는 점에서 다르다. 이 함수 역시 하나의 널 문자를 to의 끝에 추가하며, 그리하여 to의 할당된 전체 크기는 그것의 최초의 길이보다 적어도 size+1 이상이 되어야 한다.
strncpy 함수는 아래와 같이 만들어졌을 것이다.
 
char *strncat(char *to, const char *from, size_t size)
{
strncpy (to + strlen (to), from, size);
return to;
}
 
이 함수 역시 문자열 중첩의 경우를 정의하지 않고 있다. 다음에 strncpy와 strncat의 예를 보자. strncat 함수를 호출할 때 문자 배열 버퍼가 중첩되는 것을 피하기 위해서 size 파라미터가 계산되는 방식에 주의하라.
 
#include <string.h>
#include <stdio.h>
#define SIZE 10
 
static char buffer[SIZE]; /*음..정적 변수는 그 값이 불변으로 저장?*/
main ()
{
strncpy (buffer, "hello", SIZE); /*버퍼에 "hello"를 10 바이트만 복사*/
/*그러면 buffer가 h e l l o \0 \0 \0 \0 \0 이렇게 되는 것?*/
puts (buffer);
strncat (buffer, ", world", SIZE - strlen(buffer) - 1);
/*음..10-5-1,즉,4만큼만 ", world"를 추가하면 결국, ", wo"만 결합되는군*/
puts (buffer);
}
 
이 프로그램의 출력은 아래와 같다.
hello
hello, wo

함수 void * bcopy (void *from, const void *to, size_t size)

이 함수는 BSD에서 유래된 것으로서 memmove와 대체할 수 있어 쓸모 없게 된 함수다. 다만 인수를 배치하는 순서가 다른 점이 있어서 memmove와 완전히 같을 수 없음에 유의하라.

함수 void * bzero (void *block, size_t size)

이 함수는 BSD에서 유래된 것으로서 memset와 대체할 수 있어 쓸모 없게 된 함수이며, 이 함수가 저장할 수 있는 유일한 값은 영(0)이다. 어떤 장치들은 제로 화된 메모리를 위한 특별한 도구들을 갖추고 있는 경우가 있는데, 이때는 bzero가 memset보다 훨씬 효과적이다.

5.5 문자열/배열 비교

당신은 문자열과 배열의 내용을 비교하기 위해서 이 절에 있는 함수들을 사용할 수 있다. 비교뿐만 아니라 정렬을 할 때 순서관계를 결정하는 함수로서도 사용할 수 있다. 15장 [Searching and Sorting] 참조.

다른 대부분의 비교와 달리 C에서, 문자열 비교 함수는 그 비교의 결과가 동등하지 않을 경우 0이 아닌 값을 반환한다. 그 값의 의미는 비교한 문자열에서 동등하지 않은 첫 번째 문자의 순서관계를 가리킨다. 즉 음수 값은 첫 번째 문자열이 두 번째 문자열보다 작다는 걸 의미하고 그 값이 양수이면 첫 번째 문자열이 두 번째 문자열보다 크다는 걸 의미한다.

만약 당신이 단순하게 동등한지 아닌지 만을 체크하기 위해 이 함수들을 사용한다면 당신은 밑에 있는 것처럼 매크로 정의를 이용하여 좀더 깨끗한 프로그램을 만들 수도 있다.

#define str_eq(s1,s2) (!strcmp ((s1),(s2)))

이 함수들은 헤더파일 'string.h'에 정의되어 있다.

함수: int memcmp (const void *al, const void *a2, size_t size)

이 함수는 메모리의 a1로 시작하는 size만큼을 a2로 시작하는 같은 size만큼과 비교하는 함수이다. 바이트( unsigned char objects로 해석되고, int로 승격된)의 첫 번째 다른 쌍이 나타나면 다름을 나타내는 신호를 반환한다. 만약 두 블록의 내용이 동등하면 memcmp는 0을 반환한다. 정해지지 않은 배열에서 memcmp함수는 동등함을 비교하는데 대개는 유용하게 쓰인다. 그렇지만 어떤 배열에서 한 바이트별로 크기 비교를 할 때는 유용하지 않다. 예를 들어, 플로팅 포인트가 있는 여러 바이트에서 한 바이트 비교는 실수들값 사이의 관계에 대해서 아무 것도 말해주지 않는 것이다.
 
당신이 "holes(구멍)".. 즉.. unions의 끝에 덧붙여진 공백, 문자열의 끝에 덧붙여진 문자들처럼 그들이 실제로 저장된 공간보다 전체 길이를 더 차지하게 되고, 강제로 필요에(alignment) 의해 요구된 어떤 object들에 덧붙여진 것과 같은 "holes"를 포함할 수 있는 objects를 비교하기 위해 memcmp를 사용하는 것은 주의가 필요하다. 이들 "구멍들"은 바이트별 비교를 수행할 때 애매하고 이상한 작동의 원인이 될 수 있고 더 나아가서는 충돌 성분별 비교를 수행하는 것과 같은 결과를 낸다.
 
예를 들어 밑의 구조체형 정의처럼;
struct foo{
unsigned char tag;
union
{
double f;
long i;
char *p;
} value;
};
 
당신은 구조체 foo 와 같은 object들에 비교함수를 쓸 때는 memcmp대신에 특별 화된 비교함수를 사용하는 것이 더 좋다.

함수: int strcmp (const char *s1, const char *s2)

strcmp함수는 문자열 s1과 s2를 비교해서 첫 번째 다른 문자의 ( unsigned char로 object를 해석하고 int로 승격된 ) 쌍이 나오면 그 다음에 해당하는 값을 반환한다. 만약 두 개의 문자열이 같으면strcmp는 0을 반환한다. strcmp를 사용했을 때 만약 s1이 s2와 앞쪽의 문자열이 같은 substring(부문자열)이라면 s1이 s2보다 작다고 간주한다.

함수: int strcasecmp (const char *s1, const char *s2)

이 함수는 무시되는 경우의 차이를 제외하면 strcmp와 같다.

함수: int strncasecmp (const char *s1, const char *s2)

이 함수는 무시되는 경우의 차이를 제외하고는 strncmp와 같다. strncasecamp는 GNU확장이다.

함수: int strncmp (const char *s1, cnost char *s2, size_t size)

이 함수는 비교될 문자들의 길이가 인수로 필요한걸 제외하면 strcmp와 비슷하다. 다른 말로 하면 만일 두 개의 문자열이 주어진 크기 안에서 그 안에 있는 문자들이 같다면 반환 값은 zero(0)이다. 여기에 strcmp와 strncmp의 사용을 보여주는 몇 가지 예가 있다. 이 예들은 ASCII 문자 셋을 사용한다고 가정하자. ( 만약 ASCII 대신에 EBCDIC과 같은 다른 문자 셋을 사용하면 glyphs(문자)를 다른 숫자 코드들로 연관시켜서 결과 값이나 순서관계가 우리의 예상과 다를 수 있다.
 
strcmp ("hello", "hello")
) 0 /* 이 두 개의 문자열은 같다. */
strcmp ("hello", "Hello")
) 32 /* 비교는 case-sensitive 하다.( 즉 대, 소문자 구별을 한다..) */
strcmp ("hello", "world")
) -15 /* 문자 'h'는 문자 'w'보다 앞에 존재한다. 즉 h가 w보다 적다는 얘기겠지요. */
strcmp ("hello", "hello, world")
) -44 /* 컴마(,)와 널문자의 비교. 공백이 작다. */
strncmp ("hello", "hello, world"", 5)
) 0 /* 앞에서 5개 까지는 문자가 같다. */
strncmp ("hello, world", "hello, stupid world!!!", 5)
) 0 /* 앞에서 5개 까지는 문자들이 같다. */

함수: int bcmp (const void *a1, const void *a2, size_t size)

이 함수는 BSD확장으로 memcmp의 퇴화된 기능을 가졌다고 보면 된다.

5.6 대조 함수들

어떤 지역들에서는, 사전 편찬상의 순서가 문자 코드들의 엄격한 숫자적 순서와는 다르다. 예를 들어 스페인에서 대부분의 문자(glyphs)는 비교할 목적일 때 하나의 독립적인 문자로 간주되지 않는 악센트와 같은 발음 구별 기호가 있다. 한편으로는 두 문자의 문자열인 'll'이 'l'의 바로 다음순서를 갖는 단일 문자열로 취급되는 경우도 있다.

당신은 strcoll 과 strxfrm 함수( 헤더파일 'string.h'에 정의된.)를 사용하여 현재 지역에서 사용하는 대조순서를 사용하여 문자열을 비교할 수 있다. 이 함수들이 사용될 지역은 LC_COLLATE 분류에 속한 지역을 선택하여 지정할 수 있다. 19장 [Locals] 참조.

표준 C 에서는 strcoll을 위한 비교 문자열도 strcmp와 같다.

효과적으로, 이 함수들을 사용하려면 문자열 안에 있는 문자들을 현재지역의 대조열 안의 문자 위치를 표현하는 byte열로 문자열 안의 문자를 변형시켜 대응되게 사용하면 된다. 간단한 형태의 byte열을 비교함은 지역의 대조열로 문자열을 비교하는 것과 같다. strcoll함수는 비교를 하기 위한 순서의 하나로, 암묵적으로 이 변형을 수행한다. strxfrm와 비교하면 strxfrm은 명시적으로 이 변형을 수행한다. 만일 당신이 동일한 문자열이나 문자열들의 셋을 다중비교하기 원한다면 일단 모든 문자열들은 변형하기 위해 strxfrm을 사용하고 이어서 strcmp로 변형된 문자열들을 비교하는 것이 더 효과적일 것이다.

함수: int strcoll (const char *s1, const char *s2)

strcoll함수는 strcmp와 비슷하지만 어떤 지역의 문자를 대조하는데 쓰인다. (the LC_COLLATE locale) 여기에 strcoll을 사용하여 비교하기 위해 문자열들을 정렬하는 예가 있다. 이 실제 정렬 알고리즘은 여기에 쓰여지지 않았다; qsort이다. ( 15.3절[Array Sort Function] 참조 ) 여기에 나타난 프로그램 코드들은 그들을 정렬하는 동안 문자열들을 비교하기 위해 어떻게 하는지를 말해주고 있다. ( 이 절의 끝에 strxfrm을 사용하여 이것을 더 효과적으로 수행하는 방법이 있다. )
 
/* 이것은 qsort가 사용된 비교함수이다. */
int compare_elements (char **p1, char **p2)
{
return strcoll (*p1, *p2);
}
 
/* 이것은 지역 대조열을 사용하여 문자들을 정렬하기 위한 함수의 진입지점이다. */
void sort_strings (char **array, int nstrings)
{
/* 문자열을 비교하여 임시문자열을 정렬하다. */
qsort (array, sizeof (char *),
nstrings, compare_elements);
}

함수: size_t strxfrm (char *to, const char *from, size_t size)

이 함수는 현재의 지역으로 선택된 대조 문자열을 사용하여 문자열을 변형시기는 일을 하고 이 변형된 문자열을 배열 안에 저장한다. character의 크기로 저장된다.
( 종료 널 문자가 포함된) 문자열에서 문자열로 대치되는 행동은 정의되지 않았다. 5.4절[Copying and Concatenation] 를 보라. 반환 값은 변형된 문자열의 전체길이이다.
크기가 값으로서 유용하지는 않지만 만일 그것이 크기보다 크다면 그것은 변형된 문자열이 배열크기에 전체적으로 맞지 않다는 것을 의미한다. 이 경우에 오직 그 문자열의 양만큼 실제적으로 맞추어서 저장된다.
 
전체적으로 변형된 문자열을 얻으려면 strcfrm 을 좀더 큰 출력 배열을 사용해서 다시 호출하라. 변형된 문자열은 원래의 문자열보다 길 수도 짧을 수도 있다. 만약 크기가 zero라면 어떤 문자들도 안에 저장되어지지 않았다. 이 경우 strxfrm은 변형된 문자열의 길이가 되어질 문자들의 수를 반환한다.
 
이것은 할당하기 위해 문자열의 크기를 결정하는데 유용하게 쓰인다. 만약 그 크기 가 zero라고 하는 것은 문제가 되지 않는다; 심지어 널 포인터라고 할지라도 여기의 예는 당신이 많은 비교를 수행하려고 계획할 때 strxfrm을 어떻게 사용할 수 있는지를 보여준다.
이것은 전의 예와 같은 것이지만 좀더 빠른데 왜냐하면 다른 문자열과 몇 번의 비교를 수행하던지 상관없이 단 한번의 변형을 위한 작업을 하기 때문이다. 심지어 저장공간을 할당하고 해제하기 위해 필요한 시간이 우리가 많은 문자열들을 저장하기 위한 필요한 시간보다 더 조금 필요하다.
 
struct sorter { char *input; char *transformed; };
/* 이것은 struct sorter의 배열에 정렬하기 위해 qsort를 사용한 비교함수 이다. */
 
int compare_elements (struct sorter *p1, struct sorter *p2)
{
return strcmp (p1->transformed, p2->transformed);
}
 
/* 이것은 지역 대조열을 사용하여 문자열을 정렬하기 위한 함수의 진입지점이다. */
void sort_strings_fast (char **array, int nstrings)
{
struct sorter temp_array[nstrings];
int i;
 
/* temp_array를 정한다. 각 요소는 하나의 입력 문자열과 그 변형된 문자열을 포함한다. */
for (i = 0; i < nstrings; I++) {
size_t length = strlen (array[i]) * 2;
temp_array[i].input = array[i];
 
/* array[i]를 변형하라. 첫째로 버퍼가 충분히 큰지를 시험해 보라. */
while (1) {
char *transformed = (char *) xmalloc (length);
 
if (strxfrm (transformed, array[i], length) < length) {
temp_array[i].transformed = transformed;
break;
}
free (transformed); /* 큰 버퍼로 다시 시도하라 */
length *= 2;
}
} /* for 의 끝 */
 
/* 비교할 변형된 문자열들에 의해 temp_array를 정렬한다. */
qsort (temp_array, sizeof (struct sorter), nstrings, compare_elements);
 
/* 그들을 정렬된 순서로 영구적인 배열 안에 요소들을 저장하라. */
for (i = 0; i < nstrings; i++)
array[i] = temp_array[i].input;
 
/* 우리가 할당했던 문자열을 해제하라 */
for (i = 0; i < nstrings; i++)
free (temp_array[i].transformed);
}

 

호환성 참조 : 이 문자열 대조 함수들은 ANSI C에 새로 첨가된 것으로 오래된 C에서는 이와 동등한 작업을 하는 것은 아무 것도 없다.

5.7 탐색 함수들

이 절은 문자열과 배열들에 적용할 다양한 종류의 탐색 오퍼레이션을 수행하는 라이브러리 함수들을 설명하고 있다. 이 함수들을 헤더파일 'string.h'에 정의되어 있다.

함수: void * memchr (const void *block, int c, size_t size)

이 함수는 블록에서 object의 시작부터 지정된 몇 바이트 크기 내에서 byte c( unsigned char로 변화시킨 )가 첫 번째 나타난 곳을 찾는다. 반환 값을 바이트가 위치한 곳의 포이터이거나 만약 발견하지 못하면 널 포인터이다.

함수: char * strchr (const char *string, int c)

이 strchr 함수는 문자c 가 첫 번째 나타난 곳을 스트링의 시작부터 널 종료문자까지, 그 안에서 찾는다. 반환 값은 문자가 위치한 곳을 가리키는 포인터이거나 발견하지 못하면 널 포인터이다.
 
예를 들어,
strchr ("hello, world", 'l')
) "llo, world"
strchr ("hello, world", '?')
) NULL
 
위의 strchr은 'l'이 hello안에 있으므로 그 위치를 포인터로 반환하게 된다. 그래서 그 포인터를 참조하면 "llo, world" 가 나오는 것이다. 밑의 것은 '?'가 없으므로 널 포인터를 반환.
종료 널 문자는 문자열의 일부분으로 간주된다. 그래서 당신은 c 인자 값으로 널 문자를 줄 수 있고, 그러므로 당신은 문자열의 끝을 가리키는 포인터를 이 함수를 사용해서 얻을 수도 있다.

함수: char * index (const char *string, int c)

index는 strchr의 다른 이름이다; 그들은 거의 동일하다.

함수: char *strrchr (const char *string, int c)

이 함수 strrchr은 문자열의 앞에서, 앞으로 진행하며 문자열을 검색하는 대신에 문자열의 뒤에서 반대방향으로 탐색하는 것을 제외하면 strchr과 같다,
예를 들어
strrchr ("hello, world", 'l')
) "ld"
뒤에서부터 찾아서 그 위치를 포인터로 넘기니까..그 포인터를 참조하면 "ld"값이 나오겠죠.

함수: char * rindex (const char *string, int c)

rindex는 strrchr의 다른 이름이다.; 그들은 거의 동일하다.

함수: char * strstr (const char *haystack, const char *needle)

이 함수는 단일 문자보다는 긴 substring을 탐색하는 것을 제외하면 strchr과 같다. 이것은 찾고자 하는 문자열(haystack)에서 찾기를 원하는 문자열이(needle)나타난 첫 번째 문자의 위치를 포인터로 반환하고 그렇지 않으면 널 포인터를 반환한다. 만약 찾고자 하는 문자열이 공백문자열 이라면 haystack을 반환한다.
 
예를 들어
strstr ("hello, world", "l")
) "llo, world"
strstr ("hello, world", "wo")
) "world"
 
밑에 strstr만 보자면 "wo"의 문자열을 "hello, world"에서 찾으니까 있기 때문에 'w'의 위치를 가리키는 포인터를 반환 합니다. 그래서 그 포인터를 참조하면 "world"가 되죠.

함수: void * memmem (const void *needle, size_t neede_len, const void *haystack, size_t haystack_lne)

이것은 strstr과 같지만 needle과 haystack은 널 종료를 가진 문자열이 아니라 byte array이다. needle_len은 needle의 길이이고 haystack_len은 haystack의 길이이다. 이 함수는 GNU확장이다.

함수: size_t strspn (const char *string, const char *skipset)

이 strspn("string span") 함수는 문자열 sring에서 skipset을 구성하는 문자들로만 이루어진 처음 substring을 찾아서 그 길이를 반환한다. skipset안에서 문자들의 순서는 중요하지 않다.
 
예를 들어
strspn ("hello, world", "abcdefghijklmnopqrstuvwxyz")
) 5
"hello, world"에서 뒤의 문자들로만 구성된 부문자열을 찾으면 hello까지가 되겠죠.. (,)는 뒤의 문자열에 포함되지 않았으니까... 그래서 길이가 5.

함수: size_t strcspn (const char *string, const char *stopset)

strcspn("string complement span") 함수는 stopset의 문자열을 구성하는 문자들로 구성되지 않은 string의 처음 substring의 길이를 반환한다. ( 달리 말하자면 stopset의 문자 셋의 멤버인, string안의 첫 번째 문자가 있는 offset을 반환한다. )
 
예를 들어
strcspn ("hello, world", " \t\n,.;!?")
) 5
 
hello다음에 뒤의 문자 셋에 포함된 ','가 나왔으니까, 처음부터 뒤의 문자 셋에 포함되지 않는 길이는 즉 hello의 길이가 되는 거죠. 그래서 반환 값은 5.

함수: char * strpbrk (const char *string, const char *stopset)

strpbrk ("string pointer break") 함수는 strcspn이 처음 substring의 길이를 반환 하는 대신에 strpbrk는 stopset의 문자 셋 멤버인 string의 첫 번째 문자의 포인터를 반환 하는 것을 제외하면 strcspn과 연관 되어있다. 만일 문자를 발견하지 못하면 널 포인터를 반환한다.
 
예를 들어
strpbrk ("hello, world", " \t\n,.;!?")
) ", world"
 
','가 나타난 곳의 위치를 포인터로 반환 하므로 그 포인터를 참조하면 ", world"가 되는 것이다.

5.8 문자열에서 토큰 찾기

프로그램이 보통의 string을 토큰으로 분리하는 것처럼 어구나 어휘분석과 같은 그런 간단한 종류의 일을 하는 것은 꽤 일반적이다. 당신은 헤더파일 'string.h'에 정의된 strtok함수로 이 일을 할 수가 있다.

함수: char * strtok (char *newstring, const char *delimiters)

string은 호출된 일련의 strtok함수를 통해 토큰들로 분리되어 질 수 있다. 분리시킬 문자열을 오직 첫 번째 호출에서 newstring 인수로 인식된다. strtok함수는 내부적 상황 정보를 맞추기 위해 이것을 사용한다. 같은 문자열에서 부가적으로 토큰을 얻기 위해 다음 호출을 할 때는 newstring인수로 널 포인터를 주어서 지정시킨다. 당신이 strtok를 호출한 뒤에 어떤 라이브러리 함수도 strtok를 호출하지 않았음을 보장한다.delimiters인수는 추출될 토큰을 둘러싸고 있는 구획문자의 셋을 지정한 문자열이다.

이 문자열 셋에서 첫 번째 문자들은 버려진다. 이 구획문자 셋의 멤버가 아닌 첫 번째 문자는 다음 토큰의 시작을 표시한다. 토큰의 끝은 구획문자 셋의 멤버인 다음 문자를 찾음으로서 발견된다. newstring인수로 쓰여진 원래의 문자열 안의 이 문자는 널 문자로 다시 쓰여지고 newstring안의 토큰의 시작점을 가리키는 포인터를 반환한다. ( 제가 이해한 바에 따르면 구획문자를 찾아서 토큰을 얻으면 그 구획문자는 널 문자로 대체시켜 버린다는 말인 것 같은데... 아마 맞을걸요...? )

strtok의 다음 호출에서 다음 문자의 시작점은 전의 토큰의 끝으로 표시된 점을 하나 지난 지점이다. 구획문자의 셋들을 일련의 strtok 호출에서 모든 호출이 모두 같은 것은 아님을 기억하라. (즉, 구획문자열들은 strtok를 호출할 때마다 달라도 된다는 얘기)

만일 newstring 문자열의 끝에 도달되었거나 문자열의 나머지가 오직 구획문자로만 구성되어 있다면 strtok는 널 포인터를 반환한다.

 

주의: strtok는 파싱하는 문자열을 변화시킨다, 그러므로 당신은 항상 strtok로 파싱하기 전에 임시 버퍼에 문자열을 복사해 놓아야 한다. 만약 당신이 당신 프로그램의 다른 부분에서 온 문자열을 수정하도록 strtok에 허용하면 당신이 스스로 문제를 자처하는 것이다; 그 문자열은 strtok에 의해 변형되어 파싱하는 동안 그 데이터가 다른 목적으로 사용되어 질 수도 있다.

심지어 상수일지도 모르는 문자열에 당신이 명령을 내리면 strtok는 그것을 수정하려 시도할 것이고 당신의 프로그램은 결국 ROM(read only memery)에 쓰기를 시도하므로 심각한 에러신호를 얻을 것이다. 21.2.1절 [Program Error Signals] 참조.

이것은 일반적인 원칙의 특별한 경우이다: 만일 프로그램의 일부분이 어떤 데이터 구조체를 수정하려는 목적을 가지고 있지 않다면 그것을 임시적으로 수정하려 할 때 에러가 발생하는 경향이 있다. strtok함수는 재진입하지 않는다. 21.4.6절 [Nonereentrancy] 재 진입하는 이유나 위치의 중요성에 대한 논의를 위해, 여기에 strtok의 간단한 사용 예가 있다.

#include <string.h>
#include <stddef.h>
. . .
 
char string[] = "words separated by spaces -- and, punctuation!";
const char delimiters[] = " .,;:!-";
char *token;
. . .
/* 여기서 구획문자는 공백(" ") 과 콜론, 세미콜론, 느낌표, 대시("-") 이네요... */
token = strtok (string, delimiters); /* token => "words" */
token = strtok (NULL, delimiters); /* token => "separated" */
token = strtok (NULL, delimiters); /* token => "by" */
token = strtok (NULL, delimiters); /* token => "spaces" */
token = strtok (NULL, delimiters); /* token => "and" */
token = strtok (NULL, delimiters); /* token => "punctuation" */
token = strtok (NULL, delimiters); /* token => NULL */
/* strtok는 구획문자가 나오면 그것을 토큰으로 잘라서 반환 시키네요.... */

'Programming > Unix Programing' 카테고리의 다른 글

7. 스트림의 입출력  (0) 2007.12.22
6. 입출력 개괄  (0) 2007.12.22
5. 문자열과 배열 유틸리티  (0) 2007.12.22
4. 문자 처리  (0) 2007.12.22
3. 메모리 점유  (0) 2007.12.22
2. 에러 보고  (0) 2007.12.22
Comment 0 Trackback 0
Top

4. 문자 처리

목차 이전 : 3. 메모리 할당 다음 : 5. 문자열과 배열 유틸리티


4 문자 다루기

문자와 문자열들을 가지고 일을 하는 프로그램들은 종종 한 문자가 알파벳인지 숫자인지 또는 공백인지, 등등을 분류할 필요와 각 문자열에 원하는 변환을 시킬 필요가 있다. 'ctype.h'안에 있는 함수들이 이와 같은 목적을 위한 함수들을 제공한다. 문자조합과 지역을 선택한 후에 특별한 문자 코드들의 부류로 변화 시킬 수 있는데, 이 모든 함수들은 현재의 지역에 의해 영향을 받는다.( 더 정밀히 말하면 LC_CTYPE 분류의 문자분류를 위해 선택되어진 현재의 위치에 의해 영향을 받는다.; 19.3절[Locale Categories] 를 참조하라.)


4.1 문자들의 분류

이 절은 문자들 분류를 위한 라이브러리 함수들을 설명한다. 예를 들면 isalpha는 한 문자가 알파벳 문자들인지 테스트 하기 위한 함수이다. 이 함수는 테스트 하기 위한 문자를 하나의 인수로 취하여 만약 그것이 알파벳이면 영이 아닌 정수를 리턴하고 다른 경우는 영을 리턴 한다. 당신을 이것을 다음과 같이 사용할 수 있다.

if (isalpha (c))
printf ("The character `%c' is alphabetic.\n", c);
/* 만약 c가 알파벳이면 "이 문자는 알파벳이다" 라는걸 프린트하겠죠...? */

이 절안의 각각의 함수들을 문자들이 특별한 부류 안에 속하는지를 테스트 하기 위한 함수들이다. 각각은 'is'로 함수이름이 시작된다. 그들 함수는 테스트 하기 위한 문자를 하나의 인수로 취하고 boolean값으로 취급되는 정수를 리턴 한다. 그 문자인수는 int형으로 간주되고 실제 문자 대신에 상수값 EOF로 되어질 것이다. 어느 주어진 문자의 속성은 지역들에 따라서 변화할 수 있다. 지역들에 대한 더 다양한 정보는 19장[Locales] 참조하라.

다음은 'ctype.h'의 헤더파일에 정의된 함수들이다.

함수 int islower (int c)

만약 c가 소문자이면 참값을 리턴 한다.

함수 int isupper (int c)

만약 c 가 대문자이면 참 값을 리턴 한다.

함수 int isalpha (int c)

만약 c 가 영문자이면 참값을 리턴 한다. 문자가 대문자이건 소문자이건 isalpha는 모두 참값을 리턴 한다. 어떤 지역들에서 isalpha에 의해 그것이 소문자의 경우든 대문자의 경우든지 참값이 리턴 되도록 더해진 문자들이 있을 수 있다. 하지만 표준C언어에서는 그렇게 더해진 문자들이 없다.

함수 int isdigit (int c)

만약 c 가 십진수('0'에서 '9')이면 참값을 리턴 한다.

함수 int isalnum (int c)

만약 c 가 영숫자이면 ( 문자이거나 숫자 ) 참값을 리턴 한다.; isalpha나 isdigit 에 의해 참값이 리턴 되는 문자는 isalnum에서도 참값을 리턴 한다.

함수 int isxdigit (int c)

만약 c 가 16진법의로 사용된 문자이면 참값을 리턴 한다. 16진수는 일반적 10진수인 '0'에서 '9'와 'A'에서 'F" 까지와 'a'에서 'f'까지의 문자를 포함한다.

함수 int ispunct (int c)

만약 c 가 구두문자인 경우 참값을 리턴 한다. 이것은 알파벳이나 공백문자는 아니지만 인쇄할 수 있는 어느 문자를 의미한다.

함수 int isspace (int c)

만약 c 가 공백문자(white space)이면 참값을 리턴 한다. 표준"C"에서는 오직 공백문자(white space)에 참값을 리턴 한다.
' ' space : 공백
'\f' formfeed : 종이 넘김
'\n' newline : 개행
'\r' carriage return : 복귀문자
'\t' horizontal tab : 수평탭
'\v' vertical tab : 수직탭

함수 int isbalnk (int c)

만약 c 가 blank 문자이면 참값을 리턴 한다.; 이것은 space나 tab을 말한다. 이 함수는 GNU 확장이다.

함수 int isgraph (int c)

만약 c 가 그래픽 문자이면 참값을 리턴 한다.; 이것은 상형문자와 연관된 문자로 공백문자(white space)는 그래픽으로 간주되지 않는다.

함수 int isprint (int c)

만약 c 가 프린트되는 문자면 참값을 리턴 한다. 프린트되는 문자란 모든 그래픽문자에 더하여 space (' ') 문자를 포함한다.

함수 int iscntrl (int c)

만일 c 가 제어문자이면 참값을 리턴 한다. ( 그것은 프린트가 가능하지 않은 문자이다. )

함수 int isascii (int c)

만일 c 가 US/UK 문자셋 안에 정의된 7비트 부호화 되지 않은 문자이면 참값을 리턴 한다. 이 함수는 BSD와 SVID확장이다.


4.2 변환

이 절은 문자들을 원하는 목적에 맞도록 변환을 수행하는 라이브러리 함수들에 대해 설명하고 있다. 예를 들어 toupper 변환은 그것이 가능하다면 대문자로 어느 한 문자를 변환한다. 만약 그 문자가 변환할 수 없는 것이라면 변환되지 않은 문자를 리턴 한다.

이 함수들을 변환시키기 원하는 문자를 int 형으로 인자를 받아서 문자를 변환시켜 리턴 한다. 만약 주어진 인수에 적용 불가능하다면 그 인수는 변환되지 않고 리턴 된다.

 
호환성에 대한 참조 : ANSI-C 이전에는 변환되지 않은 문자를 리턴 하는 대신에 인수가 변환에 적용불가능 하다면 이 함수들은 그냥 실패만 하고 만다. 그래서 적용 성을 위해서 단지 toupper(c) 라고 만 하는것보단 islower(c) ? toupper(c) : c 라고 쓰는 것이 필요하다.

이 함수들을 헤더파일 'ctype.h'에 정의되어 있다.

함수 int tolower (int c)

만약 c 가 대문자라면 그와 대응하는 소문자를 리턴 한다. 만약 c 가 대문자가 아니라면 c는 변화되지 않고 리턴 된다.

함수 int toupper (int c)

만약 c 가 소문자라면 그것에 대응하는 대문자를 리턴 한다. 그렇지 않으면 변화되지 않은 c를 리턴 한다.

함수 int toascii (int c)

이 함수는 c를, c의 가장 상위 비트를 클리어 함으로써 US/UK ASCII 문자셋 안에 존재하는 7비트 비부호화 문자 값으로 변환시킨다. 이 함수는 BSD와 SVID확장이다.

함수 int __tolower (int c)

이 함수는 소문자로 변환시키는 함수로 SVID에 호환성을 위해 제공되고 있다. 1.2.4절 [SVID] 를 보라.

함수 int __toupper (int c)

이 함수도 위의 __tolower(int c)처럼 SVID의 호환성을 위해 제공되는 함수로 문자를 대문자로 변환시키는 기능을 한다.


목차 이전 : 3. 메모리 할당 다음 : 5. 문자열과 배열 유틸리티

'Programming > Unix Programing' 카테고리의 다른 글

6. 입출력 개괄  (0) 2007.12.22
5. 문자열과 배열 유틸리티  (0) 2007.12.22
4. 문자 처리  (0) 2007.12.22
3. 메모리 점유  (0) 2007.12.22
2. 에러 보고  (0) 2007.12.22
1. 안내문  (0) 2007.12.22
Comment 0 Trackback 0
Top

3. 메모리 점유

목차  이전 : 2. 에러보고 다음 : 4. 문자 다루기


3 메모리 할당

GNU 체제는 프로그램 상에서 분명하게 통제하는 방식으로 메모리를 할당하는 몇 가지 방법을 제공한다. 그것들은 일반성과 유효성의 범위 내에서 변화한다.

malloc 함수는 일반적인 동적 메모리를 충분히 확보한다. 3.3 [제한 없는 할당] 참조.

다른 함수로는 obstack이 있는데, 이것은 malloc보다는 덜 일반적이지만 스택 할당에서는 훨씬 더 편리하고 유용하다. 3.4 [Obstacks] 참조.

함수 alloca는 저장소를 동적으로 할당하고 자동적으로 해제해 준다. 3.5 [자동적인 크기 변화] 참조.


3.1 동적 메모리 할당이란?

동적 메모리 할당이란 프로그램이 어떤 정보를 어디에 저장해놓고 실행될 것인가를 결정하는 기법이다. 동적 메모리는 당신이 작업하는 데이터가 메모리 블록을 몇 개나 필요로 하는지 또는 그 메모리들을 얼마동안 사용할 것인지에 따라 요구된다.

예를 들면, 당신이 입력파일에서 읽어들인 한 개의 라인을 저장하려면 하나의 블럭을 필요로 한다. 그런데, 한 개의 라인이 어느 정도의 길이로 되어야 하는지가 제한이 없으므로, 당신은 저장소를 동적으로 할당할 수밖에 없으며, 그것을 당신이 더 큰 라인을 읽어들였을 때보다도 더 크게 할당해야만 한다. 또는, 당신이 입력 데이터에서 만든 각 레코드나 정의를 위해서 한 블럭을 필요로 하기도 한다. 그런데, 당신이 사전에 그것이 얼마나 많을지를 모를 수 있으므로, 각각의 레코드나 정의가 읽혀질 때마다 새로운 블럭을 할당해야만 한다. 당신이 동적 메모리를 할당할 때에는 메모리 한 블럭을 할당할 때마다 프로그램 상에서 분명하게 표현하여야 한다. 당신은 공간을 할당하고 싶을 때마다 함수나 매크로를 호출하여야하며, 인수를 가지고 그 크기를 확정해야 한다. 당신이 그 공간을 해제하고 싶을 때에도, 다른 함수나 매크로를 호출하여 그렇게 처리하면 된다. 당신은 이러한 작업을 원할 때마다 원하는 만큼 할 수 있는 것이다.


3.2 동적 할당과 C

C언어는 C프로그램에 있는 변수들을 통해 메모리 할당을 위한 두 가지 종류를 지원한다.

정적할당은 당신이 정적변수를 정의할 때 발생되는 것이다. 각 정적변수는 정해진 크기의, 메모리 한 블록을 정의한다. 그 공간은 당신의 프로그램이 시작되어 일단 할당되면 절대 해제되지 않는다.

자동 할당은 함수의 인자나 지역변수와 같은 자동변수를 정의할 때 발생한다. 자동변수를 위한 공간은 그 정의를 포함한 곳(compound statement)에 들어가면 할당되고 나오면 해제된다. GNU C에서 자동저장의 길이가 다양하게 표현되어질 수 있지만 다른 C에서는 상수이어야만 한다.

동적 할당은 C변수들에 의해 지원되지 않는다; C에는 "다이내믹"이라는 스토리지 클래스가 없고 동적으로 할당된 공간에 저장된 값은 결코 C의 변수를 통해 할 수 없다. 오직 포인터를 통해 동적으로 할당된 공간을 참조하는 것이 유일한 방법이다. 그래서 좀 불편한데다, 동적 공간을 통해 활동하는 프로세스는 실행시간이 좀 더 필요하기 때문에 프로그래머들은 정적이나 자동변수를 통해 할 수 없을 경우에만 이 동적 할당을 사용한다.

예를 들어 만약 당신이 구조체 foobar를 위해 어떤 공간을 동적으로 할당하기 원한다면 당신은 단순히 struct foobar라고 만 변수를 선언할 수 없다. 그러나 struct foobar * 의형으로 포인터 변수를 선언하면 그 공간의 주소를 지정할 수 있다. 그러면 당신은 *와 -> 오퍼레이터를 사용해서 그 공간의 내용을 참조할 수 있다.

{
struct foobar *ptr
= (struct foobar *) malloc (sizeof (struct foobar));
ptr->name = x;
ptr->next = current_foobar;
current_foobar = ptr;
}


3.3 자유로운 할당

가장 일반적인 동적 할당을 위한 도구는 malloc인데 그것은 어느 크기의 메모리 블록도 언제든지 할당하는걸 허용하고 또한 그들을 언제든지 크게 하거나 작게 하거나 할 수 있고 언제든지 그 불럭들을 개별적으로 메모리 공간을 해제하는 것도 가능하다.

3.3.1 메모리 할당의 기초

메모리 공간을 할당하기 위해 부르는 malloc의 프로토타입은 'stdlib.h' 이다.

void * malloc (size_t size)
이 함수는 새로이 할당된 블록의 포인터를 long의 크기로 반환하고 아니면, 할당할 수 없었을 때는 널 포인터를 반환한다. 할당된 블록은 당신이 스스로 초기화해야한다.( 아니면 calloc을 사용하는데 그것은 3.3.5절의 [Allocating Cleared Space] 를 보면 있다. ) 보통 당신은 메모리 블록에 저장하기 원하는 대상의 종류대로 포인터에 그 값을 캐스트(cast) 할 것이다. 여기에 그렇게 하는 예를 보여준다. 그리고 memset이라는 라이브러리 함수를 이용하여 블록을 0으로 초기화하는 것도 보여준다.
 
struct foo *ptr;
ptr = (struct foo *) malloc (sizeof (struct foo));
/* 위 라인은 구조체로 선언된 foo의 크기만큼 malloc을 사용해서 메모리를 할당한 다음 구조체 foo의 타입으로 캐스트된 포인터(ptr)에 그 번지 값을 넘기네요.. */
if (ptr == 0) abort ();
/* 만약 할당이 실패하면.. */
memset (ptr, 0, sizeof (struct foo));
/* 위의 memset는 인자로 주어진 포인터의 번지를 받아 맨 마지막 인자로 주어진 크기만큼 0으로 메모리 블록을 초기화해요. */

당신은 캐스트(case)하지 않고 어느 포인터 변수 안에도 malloc의 결과를 저장할 수 있는데 그것은 ANSI C는 자동적으로 필요할 때 void형 포인터를 다른 어떤 형으로도 변환시키기 때문이다. 그러나 캐스트(case)는 지정명령어를 명시하거나 당신이 전통적인 C로 코딩을 원한다면 필요하다. 스트링을 위한 메모리를 할당할 때 malloc의 인수에 스트링의 길이+1을 하는 것을 잊지 마라. 이것은 필요한 공간임에도 불구하고 스트링의 길이에 포함되지 않는 널 문자로 끝나기 때문이다.

/* 보충하면 스트링의 맨 끝에 있는 널 문자를 스트링의 문자열이 끝나는 곳임을 나타내기 위해서 중요한 공간이지만 스트링의 길이를 셀 때는 포함되지 않으므로 스트링을 위해 메모리를 할당할 때는 그 널 문자를 위해 스트링의 길이 +1 만큼의 메모리를 할당받자란 얘기일걸요.*/

예를 들면:
char *ptr;
ptr = (char *) malloc (length + 1);
5.1절 [Ropresentation of Strings] 를 보면 이것에 대한 더 많은 정보가 있다.

 

3.3.2 malloc의 예제들

malloc은 원하는 메모리 블록보다 메모리 공간이 적은 경우에 널을 반환한다. 당신은 모든malloc의 값을 체크해볼 수 있기 때문에 malloc을 부르고 그 값이 널 포인터라면 에러를 사용자에게 보고할 수 있고 아니면 그 값을 오직 반환 하는 서브루틴을 쓰고자 할 때 유용하다. 이 함수는 상투적으로 xmalloc라고 부른다. 여기에 그것이 있다:

void * xmalloc (size_t size)
{
register void *value = malloc (size);
/* 위 라인은 size만큼의 메모리를 할당하려고 malloc을 사용하는 데 그 malloc의 반환 값을 *value로 받았네요. */
if (value == 0) /* value가 0이면, 즉 메모리 할당실패. */
fatal ("virtual memory exhausted");
return value;
/* 할당에 성공하면 블록의 번지가 저장된 값을 반환 */
}

여기에 malloc의 실제 사용 예가 있다. savestring이라는 함수는 널 문자로 끝나는 새로운 할당 공간에 문자들의 열을 복사하는 함수다.

char * savestring (const char *ptr, size_t len)
{
register char *value = (char *) xmalloc (len + 1);
/* 위에서 만든 xmalloc함수를 사용했군요. 그러면 할당에 성공했다면 여기서 value는 len+1의 크기를 가진 블록의 번지를 저장하고 있겠군요. */
memcpy (value, ptr, len);
/* memcpy함수를 사용해서 savestring함수가 인자로 받아온 ptr을 value로 len길이 만큼 복사했고요... */
value[len] = '\0';
/* value의 맨 끝에 널 문자를 하나 저장하면 value는 위에서 설명한대로 스트링을 저장한 블록의 주소가 되겠지요? */
return value;
}

malloc으로 할당된 메모리 블록은 당신에게 어떤 데이터 타입도 저장할 수 있고 그것과 일치시킴을 보증한다. GNU시스템에서 주소는 항상 8의 배수로 증가한다. 그러나 블록이 16개보다 많은 경우에 주소는 항상 16의 배수이다. 오직 드물게 높은 경계가 필요한 경우에는 memalign이나 valloc을 사용한다. ( 3.3.7절의 [Aligned Memory Blocks]를 보라)

무언가를 위해 사용되어진 블록의 끝에 할당된 메모리는 아마도 다른 블록으로 이미 다른 malloc에 의해 할당되어졌을 것이다. 만약 이미 당신이 요구한 것보다 더 많은 메모리 블록을 취급하길 원한다면 자칫하면 내 자신의 블록의 데이터를 파괴하거나 아니면 다른 블록의 내용을 파괴할지도 모른다. 만약 당신이 이미 한 블록을 할당받았고 거기에 더 큰공간이 필요하다면 realloc을 사용하라

 

3.3.3 malloc에 의해 할당된 메모리 해제하기

당신이 malloc으로 얻었던 메모리블럭을 더 이상 사용하지 않으면 free함수를 사용하여 어떤 블록이 그 공간을 다시 할당받을 수 있도록 메모리를 해제해야 한다. free함수의 프로토타입은 'stdlib.h'이다.

void free (void *ptr)

이 함수는 ptr에 의해 지정된 공간의 블록을 해제하는 역할을 한다.

void cfree (void *ptr)

이 함수는 free와 같은 일을 한다. 이것은 SunOS와 호환을 위하여 제공된다. 블럭의 해제 후에 그 블록의 내용은 변한다. 그래서 해제 후에 그 블럭 안에서 어떤 데이터를 발견할지( 마치 연결된 블록에서 다음 블록의 포인터처럼)아무 것도 예상할 수 없다. 당신은 그 블록을 해제하기 전에 그 블록의 밖으로 당신이 필요한 모든 것을 복사하라. 여기에 연결된 모든 블럭과 포인트된 문자열을 해제하는 적당한 방법의 예가 있다.
 
struct chain
{
struct chain *next;
char *name;
}
/* 단순 연결 리스트( Single Linked List) 구조를 가졌군요. 다시 참조해보세요..*./
 
void free_chain (struct chain *chain)
{
while (chain != 0)
{
/* chain의 값이 0이 될 때까지 즉 연결된 리스트가 메모리를 다 해제할 때까지 루프를 돌겠군요. */
struct chain *next = chain->next;
free (chain->name);
free (chain);
/* 먼저 안에 저장된 name를 먼저 해제하고(그냥 단순변수가 아니라 포인터로 지정됐으므로) 다음에서야 chain블럭을 해제하는군요. */
chain = next;
/* 그리고 다음을 가리키게 해서 위와 같은 동작의 반복을 하면 다 해제되겠지요? */
}
}

가끔, free함수는 운영체제에게 실제로 메모리를 반환할 수 있고 프로세스를 작게 만들지만 보통은 그 공간의 재사용을 위해 malloc에 의해 다시 불려진 후에 허용된다. 즉, 그 공간(이미 해제된)은 해제된 메모리 리스트를 당신의 프로그램 안에 남겨 다른 malloc에 의해 내부에서 사용된다. 프로그램의 끝에서 블록을 해제할 수 없는데 그것은 프로그램에서 쓰인 모든 공간을 그 프로세스가 끝날 때 시스템에게 되돌려 주어야 하기 때문이다.

 

3.3.5 깨끗한 공간 할당하기

함수 calloc은 메모리를 할당하고 거기에 0을 채운다. 이 함수는 'stdlib.h'에 선언되어 있다.

함수 void * calloc (size_t count, size_t eltsize)

이 함수는 각 eltsize 크기마다 count요소의 벡터를 담기에 충분한 길이의 블럭을 할당한다. 그 내용은 모두 지워지고 calloc을 반환하기 전에 0으로 채운다.
당신은 calloc을 다음과 같이 정의할 수 있다.
 
void * calloc (size_t count, size_t eltsize)
/*으힉!..size_t라는 데이터 형태도 있었남?..왕초보*/
{
size_t size = count * eltsize;
void *value = malloc (size);
if (value != 0)
memset (value, 0, size);
/* value지점을 size만큼의 0으로 채우는 함수이군*/
return value;
/*이렇게 하면 포인터 주소가 반환되남유? 고수님들?*/
}

요즘은 calloc을 자주 쓰지 않는다, 왜냐하면 더 자주 쓰이는 다른 함수들을 간단히 결합해서 써도 되기 때문이다. calloc은 아주 쓸 모 없지는 않지만 구시대의 유물이 되어가고 있다.

 

3.3.6 malloc을 위한 효율적인 조치

malloc을 가장 잘 사용하려면, malloc의 GNU 버전은 항상 적은 양의 메모리를 그 크기가 2의 배수인 블럭으로 나누는 것임을 알아야 한다. 그것은 분할된 영역을 2의 배수로 유지한다. 이것은 그 크기를 한 의 크기로 만들 수 있게 한다. 그러므로, 만약 당신이 malloc을 효율적으로 만들기 위해서 작은 블럭의 크기를 선택할 수 있는 경우라면 그것을 2의 배수로 만들어라.

일단 한 가 특정한 블럭의 크기로 나누어지게 되면, 그것은 모든 블럭이 해제되지 않는 이상 다른 크기로 재 사용될 수는 없다. 많은 프로그램에서 블럭이 다른 크기로 재 사용되는 일은 좀처럼 일어나지 않는다. 따라서, 많은 다른 목적으로 사용할 블럭들을 같은 크기로 나누어 놓음으로써 프로그램이 메모리를 효율적으로 사용토록 할 수가 있는 것이다.

한 또는 그 이상의 메모리 블럭들을 요구할 때, malloc은 다른 방법을 쓴다; 그 크기를 한 의 배수로 만들고, 그 블럭을 통합하거나 필요한 만큼 나누어서 사용할 수 있다.

두 가지의 방법을 쓰는 이유는 작은 블럭들을 가능한 한 빨리 할당하고 해제하는 것이 중요하다는 데 있다. 그러나 큰 블럭의 경우에는 프로그램이 그것을 사용하는 데 어느 정도의 시간을 소요하기 때문에 속도가 그리 중요치 않다. 또한 큰 블럭은 당연히 그 숫자에 있어서 적은 것이다. 그러므로, 큰 블럭에 있어서는 낭비된 공간을 최소화하는 데 시간을 좀더 투자하는 방법이 의미가 있는 법이다.

 

3.3.7 잘 정돈된 메모리 블럭 할당하기

GNU 체제에서 malloc과 realloc에 의해 반환되는 블럭의 주소는 항상 8의 배수이다. 만약 당신이 8보다 더 큰 2의 배수의 배수를 주소로 갖는 블럭을 원한다면, memalign이나 valloc을 사용하라. 이 함수들은 'stdio.h'에 선언되어 있다.

GNU 라이브러리를 사용해서 당신은 menalign과 valloc이 반환하는 블럭을 해제하기 위하여 free를 쓸 수 있다. free는 BSD에서는 작동하지 않으며,BSD는 그러한 블럭들을 해제하기 위한 어떤 수단도 제공하지 않는다.

함수 void * memalign (size_t size, size_t boundary )

memalign 함수는 그 주소가 boundary의 배수인 size 바이트의 블록을 할당한다. boundary는 2의 배수여야만 한다! memalign 함수는 malloc 함수를 호출하여 다소간 더 큰 블럭을 할당하고 나서, 지정된 boundary상에 있는 블럭내의 주소를 반환한다.

함수 void * valloc (size_t size)

valloc을 사용하는 것은 memalign을 쓰되 두 번째 인수의 값인 크기를 생략하는 것과 같다. 그것은 이렇게 보충된다:
 
void * valloc (size_t size)
{
return memalign (size, getpagesize ());
}

 

3.3.8 메모리 힙의 일관성 체크하기

당신은 mcheck 함수를 사용하여 동적 저장소의 일관성을 체크하는 데 malloc을 요청할 수 있다. 이 함수는 GNU 확장 함수이며,'malloc.h'에 선언되어 있다.

함수 int mcheck (void (*abortfn) (void))

mcheck을 호출하게 되면 malloc에게 가끔씩 일관성 체크를 수행하도록 한다. 일관성 체크는 malloc이 할당해 놓은 블럭의 끝에다 past라고 기록해 놓는 것과 같은 일을 할 것이다. 인수 abortfn은 불일치가 발견되었을 때 호출되는 함수이다. 당신이 널(null) 포인터를 주게 되면, abort 함수가 사용된다.malloc을 써서 이미 어떤 것을 할당한 뒤에 할당 체크를 시작하기엔 너무 늦었다. 이러한 경우에 mcheck은 아무런 도움이 되지 않는다. 그 함수는 너무 늦게 호출하게 되면 -1을 반환하며, 그렇지 않을 경우에(성공하였을 때) 0을 반환한다. 일찌감치 mcheck을 호출하여 배열하는 가장 손쉬운 방법은 당신이 프로그램을 링크할때 '-lmchek' 옵션을 사용하는 것이다; 그러면 당신은 프로그램 소스를 전혀 변경할 필요가 없다.

3.3.9 저장소 할당 hook들

GNU C 라이브러리는 적절한 hook 함수들을 명시함으로써 당신이 malloc,realloc과 free의 행위를 변경할 수 있도록 해준다. 이러한 hook들은, 예컨대, 동적 저장소 할당을 사용한 프로그램들을 디버그 하는데 도움을 준다. hook 변수들은 'malloc.h'에 선언되어 있다.

변수 ____malloc__hook

이 변수의 값은 malloc이 호출될 때마다 사용하는 함수에 대한 포인터이다. 당신은 이 함수를 malloc과 같은 모양으로 정의할 수 있다.
즉, 다음과 같다;
void *function (size_t size)

변수 ____realloc__hook

이 변수의 값은 realloc이 호출될 때마다 사용하는 함수에 대한 포인터이다. 당신은 이 함수를 realloc과 같은 모양으로 정의할 수 있다.
즉, 다음과 같다;
void *function (void *ptr, size_t size)

변수 ____free__hook

이 변수의 값은 free가 호출될 때마다 사용하는 함수에 대한 포인터이다. 당신은 이 함수를 free와 같은 모양으로 정의할 수 있다.
즉, 다음과 같다;
void function (void *ptr)

당신은 이 함수들 중의 하나의 hook로써 당신이 인스톨한 함수는 그 hook의 이전의 값이 먼저 저장되지 않은 상태에서 재차 그 함수를 호출하지는 않는다는 점을 분명하게 알아두어야 한다! 그렇지 않으면 당신의 프로그램은 무한반복에 빠져 버릴 것이다.

이제 __malloc_hook을 적절하게 사용하는 법을 예를 들어보기로 하자. 그것은 malloc이 호출될 때마다 정보를 기록해내는 함수를 인스톨한다.

static void *(*old_malloc_hook) (size_t);
/* my_malloc_hook 함수의 정의가 시작되는 건가?*/
static void *my_malloc_hook (size_t size)
{
void *result; /*포인터 result 선언해 둔 다음*/
__malloc_hook = old_malloc_hook;
result = malloc (size); /*size크기의 메모리 할당하고*/
__malloc_hook = my_malloc_hook;
printf ("malloc (%u) returns %p\n", (unsigned int) size, result);
/*size와 result의 값을 모니터로 출력한 다음*/
return result; /*result를 반환한다???..void함수가???*/
}
main ()
{
...
old_malloc_hook = __malloc_hook;
/*음..A에다 B를 대입하고 B에다 C를 대입?*/
__malloc_hook = my_malloc_hook;
/*음, 자세히 보면,my_malloc_hook이 작동하면서 새롭게 할당되는 size크기의 메모리를 result에 받아서 __malloc_hook에다 대입시키겠네여..흐흐 ..오리무중!..초보의 설움...우
잉??..__malloc_hook이 인수 size_t size없이도 작동을 하남??..아..살려줘!*/
...
}

mcheck 함수(3.3.8 [메모리 heep 일정성 체크하기] 참조)는 이러한 hook들을 인스톨하여야 작동한다.

 

3.3.10 malloc으로 할당한 저장소 통계

당신은 mstats 함수를 호출함으로써 동적 저장소 할당에 관한 정보를 얻을 수 있다. 이 함수와 그것의 연관된 데이터 형태는 'malloc.h'에 선언되어 있다. 그것들은 GNU의 확장이다.

데이터 형태 struct mstats

이 구조체 형태는 동적 저장소 할당자에 관한 정보를 반환하는데 사용된다. 그것은 다음 멤버들을 담고 있다.

size_t bytes_total

이것은 malloc에 의해 관리되는 메모리의 전체 크기의 바이트 수이다.

size_t chunks_used

이것은 사용된 메모리 덩어리의 숫자이다. (저장소 할당자는 내부적으로 운영체제로부터 메모리 덩어리를 얻어낸 다음, 그것들을 개별적인 malloc의 요구에 만족되도록 나누어준다; 3.3.6 [malloc을 위한 효율적인 조치] 참조)

size_t bytes_used

이것은 사용된 바이트 수이다.

size_t chunks_free

이것은 사용되지 않은 덩어리의 숫자이다.-- 즉, 운영체제에 의해 당신의 프로그램에 할당되었으나, 아직 사용되지 않은 메모리이다.

size_t bytes_free

이것은 사용되지 않은 바이트 숫자이다.

함수 struct mstats mstats (void)

이 함수는 struct mstats 형태의 구조에서 현재의 동적 메모리 사용에 관한 정보를 반환한다.

 

3.3.11 malloc 관련 함수 요약

malloc과 함께 작동하는 함수들을 요약해보자.

void *malloc (size_t size)

size 바이트의 한 블럭을 할당하기. 3.3.1 [기본적인 할당] 참조.

void free (void *addr)

malloc에 의해 이전에 할당되었던 블럭을 해제하기. 3.3.3 [malloc이후의 해제] 참조.

void *realloc (void *addr, size_t size)

malloc에 의해 이전에 할당되었던 블럭을 크게 또는 작게 만들어서, 새로운 지역에 그것을 복사하기.

void *calloc (size_t count, size_t eltsize)

malloc을 사용하여 count * eltsize 바이트의 블럭을 할당하여 그 내용을 제로로 채우기. 3.3.5 [깨끗한 공간 할당하기] 참조.

void *valloc (size_t size)

size 바이트의 블럭을 할당하여, 한 경계지점에서 시작하기. 3.3.7 [잘 정돈된 메모리 블럭] 참조.

void *memalign (size_t size, size_t boundary )

size 바이트의 블럭을 할당하여 boundary의 배수인 주소로 시작하기. 3.3.7 [잘 정돈된 메모리 블럭] 참조.

int mcheck (void (*abortfn) (void))

malloc으로 하여금 수시로 동적으로 할당된 메모리의 일관성을 체크하게 하여 불일치가 일어나면 abortfn을 호출한다. 3.3.8 [메모리 heep 일관성 체크하기] 참조.

void *(*__malloc_hook) (size_t size)

malloc이 호출될 때마다 사용하는 함수에 대한 포인터.

void *(*__realloc_hook) (void *ptr, size_t size)

realloc이 호출될 때마다 사용하는 함수에 대한 포인터.

void (*__free_hook) (void *ptr)

free가 호출될 때마다 사용하는 함수에 대한 포인터.

struct mstats mstats (void)

현재의 동적 메모리 사용에 관한 정보 반환하기. 3.3.10 [malloc으로 할당한 저장소 통계] 참조.


3.4 Obstacks

obstack은 object들의 스택들이 있는 메모리의 공동관리소(pool) 이다. 당신은 obstack들을 분리하여 몇 개라도 만들 수 있고 그리고 정해진 obstack에 object를 할당한다. 각 obstack안에 마지막 objedt에서 첫 번째 하나는 반드시 해제된 상태 이여야 하지만 개별적 obstack들은 다른 것에 독립적이다.

해제에 관한 하나의 제약 말고는 obstack들은 전체적으로 일반적이다; obstack은 어느 크기의 object들을 몇 개라도 저장할 수 있다. 그들은 매크로로 실행되고 그런 할당은 object가 보통 작아서, 매우 빠르다. 그리고 적당한 경계 위에 각 object를 시작하기 위해 필요한 공간이 있는데 그것은 각 대상마다 덧붙여 있다.

3.4.1 Obstacks 만들기

obstacks를 다루기 위한 유틸리티들은 헤더파일 'obstack.h'에 정의되어 있다.

struct obstack Data Type

obstack은 구조체 obstack의 타입으로 데이터 구조를 나타낸다. 이 구조는 작은 정해진 크기를 가진다; 각 레코드들은 obstack의 상황과 할당된 곳에서 공간을 어떻게 발견할 것인가를 나타낸다. 그러나 obstack은 object자체를 포함하지는 않는다. 당신은 직접적으로 그 구조체의 내용을 검색하려고 시도할 수 없다. 오직 이 장에서 설명하는 함수들을 사용하라.

당신은 struct obstack 타입으로 변수들을 선언할 수 있고 obstack들처럼 그들은 사용할 수 있다. 또한 다른 종류의 object처럼 동적으로 obstack들을 할당할 수 있다. obstack의 동적 할당은 몇 개의 다른 스택을 가진 한 변수를 가질 수 있도록 당신의 프로그램에게 허용한다. (당신은 심지어 한 obstack안에 다른 obstack구조를 할당할 수 있다. 하지만 이것은 그리 유용하지는 않다.)

당신이 사용하기 위해 obstack을 요청하기 위해서는 obstack으로 그와 같은 일을 하는 모든 함수들이 필요하다. 당신은 struct obstack * 타입의 포인터로 이것을 한다. 따라서 우리가 종종 "an obstack"라고 말할 때 엄격히 말하면 그것은 포인터와 같다.

obstack안에 object들은 청크(chunks)라고 불리는 큰 블록들 안에 꾸려져 있다. 구조체 obstack은 현재 사용되고 있는 청크들의 연결을 포인터로 구성한다.

obstack 라이브러리는 당신이 앞의 청크에 넣기 원하지 않는 object를 할당할 때마다 새로운 청크를 만든다. obstack 라이브러리가 청크를 관리하므로 당신은 청크에게 많은 주의를 쏟을 필요는 없지만 청크를 얻어서 사용하는 함수를 공급해줄 필요는 있다. 보통 당신은 직접적이거나 간접적이게 malloc을 사용하는 함수를 공급한다. 당신은 청크를 해제하기 위해서도 함수를 공급해야만 한다. 이 문제들은 다음절에 설명되어 있다.

 

3.4.2 Obstack들은 사용하기 위한 준비

당신이 obstack을 사용하려고 계획했다면 각 소스파일 안에 'obstack.h'라는 헤더파일을 포함해야만 한다. 이것은 다음과 같다.

#include <obstack.h>

또한 소스파일에 매크로 obstack_init를 사용한다면 obstack 라이브러리에 의해 불려질 매크로나 두 개의 함수를 선언하거나 정의해야만 한다. 하나는 obstack_chunk_alloc으로 모아져있는( are packed) object들에 메모리의 청크를 할당하기 위해 사용되는 것이다. 다른 하나는obstack_chunk_free로 청크에서 object들을 해제하고 청크를 반환하는데 사용한다.

보통 이들은 xmalloc 이라는 매개자를 거쳐 malloc을 사용하도록 정의되어 있다. ( 3.3장[Unconstrained Allocation] 를 참조) 이것은 밑에 있는 두 개의 매크로 정의로 한다.

#define obstack_chunk_alloc xmalloc
#define obstack_chunk_free free

당신이 obstack들을 사용하면서 얻어낸 저장공간은 실제로 malloc을 통해 할당된 것이고, obstak를 사용하는 건 메모리의 큰 블록들은 덜 자주 호출하기 때문에 좀더 빠르다.

실행시 프로그램이 obstack으로 struct obstack타입인 object를 사용하기 전에 obstack_init를 호출해서 obstack을 초기화해야만 한다.

함수 void obstack__init (struct obstack *obstack_ptr)

object의 할당을 위해 obstack_ptr을 초기화

여기에 obstack을 위해 어떻게 공간을 할당하고 그것을 초기화하는 두 가지 예가 있다.

첫 번째, 스택변수인 obstack:
static struct obstack myobstack;
....
obstack_init (&myobstack);
둘째로 obstack 그 자신이 동적으로 할당되는 것:
struct obstack *myobstack_ptr
= (struct obstack *) xmalloc (sizeof (struct obstack));
obstack_init (myobstack_ptr);

3.4.3 Obstack에서 할당

obstack에 object를 할당하기 위한 가장 직접적인 방법을 obstack_alloc을 사용하는 건데 이것은 거의 malloc과 같다.

함수 void * obstack__alloc (struct obstack *obstack_ptr, size_t size)
몇 바이트의 크기인지 초기화되지 않은 할당은 그 자신의 주소 값을 반환한다. 여기의 obstack_ptr은 안에 블록을 할당하기 위한 obstack을 지정한다.; 그것은 obstack으로 표현된 struct obstack 타입의 object의 주소이다. 각각의 obstack 함수나 매크로는 당신에게 첫 번째 인자로obstack_ptr을 지정할 것을 요청한다.
여기의 예는 정해진 obstack, 즉 변수 string_obstack안에 문자열을 할당하기 위한 함수를
보여준다.
 
/*또 역시 제가 주석을 좀 붙여 보지요.. 헤헤..*/
struct obstack string_obstack;
/* struct obstack 형으로 string_obstack변수를 선언했네요. */
char * copystring (char *string)
{
char *s = (char *) obstack_alloc (&string_obstack, strlen (string) + 1);
/* 아까 위에서 obstack_alloc이 거의 malloc 과 같다고 했지요..? 그러니까 문자열을 위해 공간을 할당받기 위해서는 길이 +1 해서 포인터를 반환 하면 s로 받지요...*/
memcpy (s, string, strlen (string));
/* 위에서 받은 포인터 s에 string 을 길이만큼 복사 */
return s; /* 그리고 반환...*/
}
 
정해진 내용을 갖은 블록을 할당하기 위해서는 obstack_copy 함수를 사용하라. 이것은 다음과 같이 정의한다.

void * obstack__copy (struct obstack *obstack_ptr, void *address, size_t size)

이것은 블록을 할당하고 그 주소에 문자열을 정해진 크기대로 복사해서 초기화하는 것이다.

void * obstack__copy0 (struct obstack *obstack_ptr, void *address, size_t size)

obstack_copy와 같지만 널 문자를 포함하는 하나의 여분의 바이트를 덧붙인다. 이 여분의 바이트는 인수크기에는 들어가지 않는다. 이 obstack_copy() 함수는 널 문자로 끝나는 문자열과 같은 문자의 열들을 obstack에 넣기에는 편리한 함수다.
 
사용 예를 보면:
char * obstack_savestring (char *addr, size_t size)
{
return obstack_copy0 (&myobstack, addr, size);
}
malloc을 사용한 앞의 stvestring 의 예와 비교해 보라 (3.3.1절의 [Basic Allocation]을 보라)

 

3.4.4 Obstack에서 Objects 해제하기

obstack안에 할당된 object를 해제하기 위해서는 obstack_free함수를 사용한다. object들의 스택인 obstack은 한 object가 해제되면 그 object이후 최근에 같은 obstack에 할당된 다른 object들도 자동적으로 해제시킨다.

함수 obstack__free (struct obstack *obstack_ptr, void *object)

만약 object가 널 포인터이면 obstack안에 할당된 모든 것이 해제된다. 그렇지 않으면 object는 obstack안에 할당된 object의 주소 이여야만 한다. 그러면 object는 해제되고 그 해제된 object이후 obstack안에 할당된 모든 것도 따라서 해제된다.
object에 널 포인터를 주면 그 결과는 obstack의 비 초기화라는 것을 알아라. obstack안에 모든 내용은 해제하지만 다음 할당을 위해 obstack을 유용하게 남기고 싶다면 obstack에 할당된 object의 첫 번째 주소를 주어 obstack_free를 호출하면 된다.
청크안에 모아진 obstack안의 object들은 재 호출하라. 청크안에 object들은 모두 해제할 때 그 obstack 라이브러리는 자동적으로 청크를 해제한다. ( 3.4.2절 [Preparing for Obstacks] 참조) 그러면 다른 obstack들이나 비 obstack 할당도 그 청크의 공간을 재사용 할 수 있다.

3.4.5 Obstack 함수와 매크로

obstack들의 사용을 위한 인터페이스는 컴파일러에 의존하는 함수나 매크로로 정의되어 진다. 모든 C 컴파일러는 ANSI C와 전통적인 C를 지원하는 obstack 도구를 가지고 있다. 하지만 당신이 GNU C 보다는 다른 컴파일러를 사용하려 한다면 많은 주의를 해야한다.

만약 당신이 오래된 비 ANSI C 컴파일러를 사용하면 모든 obstack 함수들은 매크로로 정의되어 있다. 당신은 이 매크로를 함수들처럼 호출 할 수 있지만 당신은 그것을 다른 방법으로는 사용할 수 없다.( 예를 들어, 그들의 주소를 줄 수 없다. )

매크로의 호출은 특별한 주의가 요구된다.; 즉, 첫 번째 피연산자 ( obstack 포인터)가 부작용을 포함하고 있다고 할 수 있을지도 모르는데, 그것은 그것이 여러 번 연산되기 때문이다. 예를 들어 만약 당신이 아래처럼 쓰면;

obstack_alloc (get_obstack (), 4);

당신은 여러 번 get_obstack이 호출되는걸 발견할 수 있다. 만약 당신이 obstack 포인터 인자처럼 *obstack_list_prt++을 사용하면 여러 번 증가가 발생하여 이상한 결과를 얻을 것이다.

ANSI C는 함수와 매크로 정의 둘을 다 지원한다. 함수 정의는 그것을 호출하지 않고 함수의 주소로 사용되는 것이다. 보통의 호출은 암묵적으로 매크로 정의가 사용되지만 당신은 괄호 안에 함수의 이름을 씀으로써 함수정의를 요청할 수 있다. 아래처럼;

char *x;
void *(*funcp) (); /* 매크로 사용 */
x = (char *) obstack_alloc (obptr, size); /* 함수 호출 */
x = (char *) (obstack_alloc) (obptr, size); /* 함수의 주소를 취하라 */
funcp = obstack_alloc;

이것은 표준 라이브러리 함수를 위한 ANSI C안에 있는 것과 같다. 1.3.2절[Macro Definitions] 를 보라.

 
주의 : 심지어 ANSI C에서 조차도 첫 번째 피연산자의 부작용을 피하는데 주의를 기울여야 한다. 만약 당신이 GNU C컴파일러를 사용하고 있다면 이 주의는 필요하지 않는데, 그것은 GNU C안에 다양한 언어의 확장은 오직 한번 각 인수를 실행하도록 그 매크로를 정의하는 것을 허용한다.

 

3.4.6 성장하는 대상물

obstack 덩어리 내의 저장소는 연속적으로 사용되기 때문에 어떤 대상물을 차례로 쌓아서 대상물의 끝에 한번에 하나 또는 그 이상의 바이트를 추가할 수 있다. 이 기법을 사용함에 있어 당신은 그 대상물의 끝에 다다를 때까지 그 대상물에 얼마만큼의 데이터를 넣을 수 있는지를 알 필요가 없다. 이것은 성장하는 대상물 기법이라 불린다. 성장하는 대상물에 데이터를 추가하는 특수한 함수들은 이 절에 기술되어 있다. 당신은 어떤 대상물을 성장시키기 시작할 때 아무런 특별한 조치도 할 필요가 없다. 그 대상물에 데이터를 추가시키는 함수들 중의 하나를 사용하게 되면 그것은 자동적으로 시작된다. 그러나 그 대상물이 끝날 때에는 분명하게 말해 줄 필요가 있다. 이것은 함수 obstack_finish에 의해 수행된다. 이렇게 해서 만들어진 대상물의 실제 주소는 그것이 끝나기 전에는 알 수가 없다. 그때까지는 항상 대상물이 새로운 덩어리 속으로 복사되어야 할만큼의 데이터만이 추가될 수 있을 뿐이다. obstack이 성장하는 대상물을 위해 사용되고 있을 동안에는 당신은 다른 대상물을 할당하기 위해서 그것을 사용할 수는 없다. 만약 그렇게 한다면, 이미 성장하는 대상물에 추가된 공간이 다른 대상물의 일부분으로 되어버릴 것이다.

함수 void obstack__blank (struct obstack *obstack_ptr, size_t size)

성장하는 대상물에 추가하기 위한 가장 기초적인 함수는 obstack_blank이며, 이것은 대상물을 초기화하지 않고 공간을 추가한다.

함수 void obstack__grow (struct obstack *obstack_ptr, void *data, size_t size)

한 블럭의 초기화된 공간을 사용하려면,obstack_grow를 사용하라.obstack_grow는 성장하는 대상물에 있어서는 obstack_copy와 유사하다.obstack_grow는 성장하는 대상물에 size 바이트의 데이터를 추가하고 데이터로부터 그 내용을 복사한다.

함수 void obstack__grow0 (struct obstack *obstack_ptr, void *data, size_t size)

이것은 성장하는 대상물에 있어서 obstack_copy와 유사하다. 그것은 널(null) 문자가 따라오는 데이터에서 복사한 size 바이트를 추가한다.

함수 void obstack__1grow (struct obstack *obstack_ptr, char c)

한번에 하나의 문자를 추가하려면 함수 obstack__1grow를 사용하라. 그것은 성장하는 대상물에 c를 담아둘 1 바이트를 추가한다.

함수 void * obstack__finish (struct obstack *obstack_ptr)

당신이 대상물 성장시키기를 끝마쳤을 때 함수 obstack_finish를 사용해서 그것을 닫고 그것의 마지막 주소를 반환하라. 당신이 그 대상물을 완료하면,obstack은 또다른 대상물을 할당하거나 성장시키는데 사용될 수 있게 된다. 당신이 하나의 대상물을 성장시켜서 만들게 되면, 그것이 얼마큼 길어졌는지를 나중에 알 필요가 생길 것이다. 당신은 그 대상물이 성장하는 동안에는 그 과정을 추적할 필요가 없다. 왜냐하면 당신은 함수 obstack_object_size를 사용하여 그 대상물의 완료 직전에 obstack으로부터의 길이를 알 수 있기 때문이다. 그것은 다음과 같이 선언된다.

함수 size_t obstack__object__size (struct obstack *obstack_ptr)

이 함수는 성장하는 대상물의 현재 크기를 바이트 수로 반환한다. 이 함수는 대상물이 완료되기 전에 호출하여야 함을 기억하라. 완료되고 난 다음에는 이 함수는 제로를 반환할 것이다. 만약 당신이 대상물 성장시키기를 시작한 다음에 이를 취소하고 싶다면, 당신은 이렇게 끝내고 해제하라;
obstack_free (obstack_ptr,obstack_finish (obstack_ptr));
이것은 아무런 대상물도 성장하고 있지 않을 때에는 아무런 효과도 없는 것이다. 현재의 대상물을 작아지게 만들고 싶으면 음수의 size 인수를 갖는 obstack_blank를 사용할 수 있다. 그러나 그 대상물 자체를 음수 크기로 만들려고 하지는 말아야 한다. 그렇게되면 무슨 일이 일어날는지 알 수가 없다.

 

3.4.7 특히 급속하게 성장하는 대상물

성장하는 대상물을 만드는 통상적인 함수들은 현재의 메모리 덩어리에 새로운 성장을 위한 공간이 있는지를 체크하기 위하여 부가물을 초래한다. 만약 당신이 대상물을 조금씩 단계별로 만들고자 한다면 이 부가물은 중요한 것이 될 수 있다.

당신은 체크하지 않고 대상물을 성장시키는 특수한 "급속한 성장" 함수를 사용함으로써 부가물을 줄일 수 있다. 견실한 프로그램을 만들기 위해서는 당신 자신이 체크를 수행해야만 한다. 만약 당신이 대상물에 데이터를 추가하려 할 때마다 가장 간단한 방법으로 이러한 체크를 수행하려 한다면, 당신은 아무 것도 저장하지 않아도 통상적인 성장 함수들이 이를 행할 것이다. 그러나 당신이 체크의 횟수를 줄이되 더 효과적으로 체크할 수 있도록 배열한다면, 당신은 프로그램을 빠르게 만드는 것이 된다. 함수 obstack_room은 현재의 메모리 덩어리에서 사용 가능한 공간의 양을 반환한다. 그것은 다음과 같이 선언된다:

size_t obstack__room (struct obstack *obstack_ptr)

이것은 가장 빠른 성장 함수를 사용하는 obstack에서 현재의 성장하는 대상물(또는 시작하려하는 대상물)에 안전하게 추가할 수 있는 바이트 수를 반환한다. 공간이 있음을 알 수 있는 한, 당신은 어떤 성장하는 대상물에 데이터를 추가하는 급속한 성장 함수들을 사용 할 수 있다.

void obstack__1grow__fast (struct obstack *obstack_ptr, char c)

함수 obstack_1grow_fast는 구조체 obstack의 포인터 obstack_ptr에서 성장하는 대상물에 문자 c를 담아둘 1 바이트를 추가한다.

함수 void obstack__blank__fast (struct obstack *obstack_ptr, size_t size)

함수 obstack_blank_fast는 구조체 obstack인 obstack_ptr에서 성장하는 대상물에 size 바이트를 초기화하지 않고 추가한다. 당신이 obstack_room을 사용하여 공간을 체크해서 당신이 추가하기를 원하는 만큼의 충분한 공간이 없을 때는, 급속한 성장함수들은 안전하게 사용할 수가 없다. 이러한 경우에는 그저 적합한 통상적인 성장함수만을 사용하라. 이 함수는 곧바로 대상물을 새로운 메모리 덩어리에 복사한다, 그리고 나면 다시 사용 가능한 공간이 많이 있게 될 것이다. 그러고 난 다음에, 당신이 통상적인 성장 함수를 사용할 때마다, 함수 사용 후에 obstack_room을 써서 충분한 공간이 남아있는지를 체크하라. 일단 대상물이 새로운 덩어리로 복사되고 나면 다시 충분한 공간이 생길 것이고, 그래서 프로그램은 다시 급속한 성장함수들을 사용하기 시작할 것이다.
 
예를 들어보자.
/*(1)함수명:add_string(2)인수1:구조체 obstack을 포인터로 선언(3)인수2:문자포인터ptr을 전달*/
void add_string (struct obstack *obstack, char *ptr, size_t len)
{
while (len > 0) {
/*함수 전체 내용이 결국은 while루프이군 따라서 인수 len은 양수여야만 의미가 있군*/
/* 함수 obstack_room은 그 인수로 받은 obstack 내의 사용 가능한 공간을 바이트 수로 반환하므로 그 바이트 수가 len보다 크면..흐흐..공간이 있지여?*/
if (obstack_room (obstack) > len) { /* 공간이 충분하면 급속한 성장함수 사용*/
while (len-- > 0)
/* 1 만큼씩 헤아리면서 인수로 전달받은 문자 포인터의 내용을 하나씩 obstack에 추가시킨다..그러나 이 루프가 작동을 마칠 때면 결국은 한꺼번에 포인터의 모든 내용이 추가되어 버리는 셈이군 */
obstack_1grow_fast (obstack, *ptr++);
} else {
/* 공간이 불충분하면 서서히 한 문자를 추가하다가 그것을 새로운 덩어리로 복사해서 공간을 만듦 */
obstack_1grow (obstack, *ptr++);
/* 즉, 일단 인수의 포인터의 1문자를 obstack에 추가하고 */
len--;
/* len을 1만큼 줄여서 obstack_room이 공간을 다시 평가하도록 하는군요.-->while루프--> if문장으로 복귀 */
}
} /*while루프의 끝지점*/
} /*함수 전체의 끝지점*/

 

3.4.8 obstack의 위상

하나의 obstack에서 현재의 할당 위상에 관한 정보를 제공하는 함수들이 있다. 당신은 성장하고 있는 대상물에 대해서 알아보기 위해서 그것들을 사용할 수 있다.

함수 void * obstack__base (struct obstack *obstack_ptr)

이 함수는 구조체 obstack_ptr에서 현재 성장하는 대상물의 임시 시작주소를 반환한다. 만약 당신이 그 대상물을 즉시로 종결한다면 그것은 그 주소를 가질 것이다. 반대로 당신이 그것을 더 크게 만든다면 그것은 현재의 덩어리보다 더 성장할 것이다. 그리하여 그 주소가 변할 것이다. 아무런 대상물도 성장하지 않고 있다면, 이 값은 당신이 할당한 다음 번의 대상물이 어디서부터 시작할 것인가를 알려준다.(다시 한번 생각해보면 그것은 곧 현재의 덩어리이다.)

함수 void * obstack__next__free (struct obstack *obstack_ptr)

이 함수는 구조체 obstack_ptr의 현재 덩어리에서 해제된 첫 바이트의 주소를 반환한다. 이것은 현재 성장하고 있는 대상물의 끝 부분이다. 만약 아무런 대상물도 성장하고 있지 않다면, obstack_next_free는 obstack_base와 같은 값을 반환한다.

함수 size_t obstack__object__size (struct obstack *obstack_ptr)

이 함수는 현재 성장하고 있는 대상물의 바이트에서 크기를 반환한다. 이것은 obstack_next_free (obstack_ptr) 에서 obstack_base (obstack_ptr) 을 뺀 것과 같다.
<번역자 주석>
위의 내용들을 종합해 보면, 결국 이런 내용인가?
--> 스택(stack)에 메모리를 확보하고 추가하고 해제한다?

따라서, 위의 스택에 담긴 대상물의 크기는, "시작점(해제지점) - 종료지점"이 되는 건가? 음...,스택과 그 주소를 잘 이해하고 있는 고수들만이 제대로 이해할 수 있겠군...

 

3.4.9 obstack에서의 데이터 할당

각 obstack은 할당 경계지점을 갖는다; obstack에 자동적으로 할당된 각 대상물은 설정된 경계지점의 배수로 된 주소로 시작한다. 디폴트값으로 이 경계지점은 4 바이트 수이다.

obstack의 할당 경계지점에 접근하기 위해서는 매크로 obstack_alignment_mask를 사용하라. 이 함수의 원형은 이렇게 되어있다.

매크로 int obstack__alignment__mask (struct obstack *obstack_ptr)

    그 값은 한 비트의 mask이다; 1로 된 비트는 대상물의 주소에서 대응하는 비트가 0 이어야 함을 나타낸다. mask 값은 2의 배수보다 작은 값이어야 한다; 그 결과 모든 대상물의 주소는 2의 배수의 배수로 된다. mask의 디폴트값이 3으로 되면 주소들은 4의 배수로 된다. mask의 값이 0으로 되면, 대상물이 1의 배수로 시작할 수 있다.(즉, 할당이 되지 않는다.)

    매크로 obstack_alignment_mask의 확장은 lvalue이므로, 당신은 mask를 변경 지정할 수 있다. 예를 들면 다음과 같다;

    obstack_alignment_mask (obstack_ptr) = 0;

    위와 같은 문장은 설정된 obstack에서 진행중인 할당을 없애버리는 효과를 갖는다.

    할당 mask에서 일어난 변화는 어떤 대상물이 obstack에서 할당되거나 종료되기까지는 아무런 효과도 없다. 만약 당신이 어떤 대상물을 성장시키고 있지 않을 경우라면, 당신은 obstack_finish를 호출하여 새로운 할당 mask가 즉각 효과를 갖도록 할 수 있다. 이것은 길이가 제로인 대상물을 종료하고 다음 대상물을 위한 적절한 할당을 하는 것이다.

3.4.10 obstack 덩어리

obstack은 자체의 기능으로 큰 덩어리에 공간을 할당하고 당신의 요청에 부응하여 덩어리에서 공간을 분배한다. 덩어리들은 당신이 별도의 크기를 지정하지 않는 이상 4096 바이트의 크기이다. 덩어리 크기는 대상물들을 저장하는 데에 실제로 사용되지 않는 8 바이트의 부가물을 포함한다. 설정된 크기에 관계없이, 큰 대상물을 위해서 필요하다면 더 큰 덩어리들이 할당되어질 것이다.

obstack 라이브러리는 함수 obstack_chunk_alloc을 호출하여 덩어리들을 할당하는데, 당신이 그것을 정의하여야 한다. 당신이 어떤 덩어리에 있는 대상물을 해제한 결과로서 어떤 덩어리가 더이상 필요치 않게 되면, obstack 라이브러리는 함수 obstack_chunk_free를 호출하여 그 덩어리를 해제한다.

물론 당신은 그것 또한 정의하여야 한다. 이들 둘은 obstack_init(3.4.1 [obstack 만들기] 참조) 을 사용하는 각 소스파일에서 (매크로로)정의되거나 (함수로)선언되어야만 한다. 그것들은 거의 대부분 아래와 같이 매크로로 정의된다:

#define obstack_chunk_alloc xmalloc
#define obstack_chunk_free free

이들은 (인수가 아니라) 단순히 매크로임을 주의하라. 인수를 갖는 매크로 정의는 유효치 않다! 다만 obstack_chunk_alloc 나 obstack_chunk_free는 그 자체가 함수명칭이 아닐 경우에 함수 명칭으로 확장될 필요가 있다. 실제로 obstack_chunk_alloc을 사용하는 함수는 어떤 형태로든 "실패"를 반환할 수 없다. 왜냐하면 obstack 라이브러리는 실패를 취급할 준비가 되어있지 않기 때문이다. 그러므로 malloc 그 자체는 적합하지가 않다. 만약 그 함수가 공간을 확보하지 못하게 되면, 그것은 처리를 종료하거나(22.3 [프로그램 종료] 참조.) longjmp를 사용하는 비지역적 종료를 행하게 된다.(20장 [비지역적 종료] 참조)

만약 당신이 malloc을 써서 덩어리들을 할당한다면, 덩어리 크기는 2의 배수여야만 한다. 덩어리의 디폴트 크기는 4096으로 선택되어 있다. 왜냐하면 디폴트 크기만으로도 현재로서는 작은 대상물에서 요구되어질 대부분의 전형적인 요청들을 만족시킬 수가 있으며, 아직은 그 대상물의 크기가 사용되지 않은 가장 최근의 덩어리 부분에 너무 많은 메모리가 낭비되지 않게 해도 될 만큼 작기 때문이다.

매크로 size_t obstack__chunk__size (struct obstack *obstack_ptr)

이것은 주어진 obstack의 덩어리 크기를 반환한다. 이 매크로는 lvalue로 확장되므로, 당신은 그것에 새로운 값을 할당함으로써 새로운 덩어리 크기를 규정할 수 있다. 그렇게 하게 되면 이미 할당되어있는 덩어리에는 영향이 없고, 다만 장래에 특정한 obstack을 위하여 할당될 덩어리들의 크기에만 영향을 주게 된다. 그것은 덩어리 크기를 작게 만드는 데에는 소용이 없다. 만약 당신이 그 크기가 덩어리 크기와 비슷한 많은 대상물을 할당하고 싶다면 덩어리 크기를 크게 만드는 것이 매우 효과적일 것이다.

 

3.4.11 obstack 함수 요약

obstack과 관련된 함수들을 요약해보자. 각 함수들은 첫 인수로서 obstack(구조체 포인터로서)의 주소를 갖는다.

void obstack_init (struct obstack *obstack_ptr)

obstack의 사용을 초기화한다. 3.4.1 [obstack 만들기] 참조.

void *obstack_alloc (struct obstack *obstack_ptr, size_t size)

초기화되지 않은 바이트 크기의 대상물을 할당한다. 3.4.3 [obstack 할당] 참조.

void *obstack_copy (struct obstack *obstack_ptr, void *address, size_t size)

size 바이트 크기의 대상물을 할당하고 주소에서 복사한 내용을 채운다. 3.4.3 [obstack 할당] 참조.

void *obstack_copy0 (struct obstack *obstack_ptr, void *address, size_t size)

size+1 바이트 크기의 대상물을 할당하고 주소에서 복사한 그만한 크기의 내용을 채우고 끝에 널(null) 문자를 붙인다. 3.4.3 [obstack에서의 할당] 참조.

void obstack_free (struct obstack *obstack_ptr, void *object)

대상물 해제(동시에 대상물보다 더 최근에 설정된 obstack에 할당된 모든 것을 해제) 3.4.4 [obstack 대상물 해제하기] 참조.

void obstack_blank (struct obstack *obstack_ptr, size_t size)

성장하는 대상물에 초기화되지 않은 size 바이트를 추가. 3.4.6 [성장하는 대상물] 참조.

void obstack_grow (struct obstack *obstack_ptr, void *address, size_t size)

size 바이트를 주소로부터 복사하여 성장하는 대상물에 추가. 3.4.6 [성장하는 대상물] 참조.

void obstack_grow0 (struct obstack *obstack_ptr, void *address, size_t size)

size 바이트를 주소로부터 복사하여 성장하는 대상물에 추가하고 널(null) 문자를 담고있는 다른 바이트를 덧붙인다. 3.4.6 [성장하는 대상물] 참조.

void obstack_1grow (struct obstack *obstack_ptr, char data_char)

성장하는 대상물에 data_char를 담고있는 한 바이트를 추가. 3.4.6 [ 성장하는 대상물] 참조.

void *obstack_finish (struct obstack *obstack_ptr)

성장하고 있는 대상물을 종결하고 그것의 최후주소를 반환 3.4.6[성장하는 대상물] 참조.

size_t obstack_object_size (struct obstack *obstack_ptr)

현재 성장하고있는 대상물의 현재의 크기를 구한다. 3.4.6 [성장하는 대상물] 참조.

void obstack_blank_fast (struct obstack *obstack_ptr, size_t size)

충분한 공간이 있는지의 여부를 체크하지 않고서, 성장하는 대상물에 초기화되지 않은 size 바이트를 추가. 3.4.7 [급속한 성장함수] 참조.

void obstack_1grow_fast (struct obstack *obstack_ptr, char data_char)

충분한 공간이 있는지의 여부를 체크하지 않고서, 성장하는 대상물에 data_char를 담을 한 바이트를 추가. 3.4.7 [급속한 성장함수] 참조.

size_t obstack_room (struct obstack *obstack_ptr)

현재의 대상물을 성장시키는 데 사용할 수 있는 공간의 양을 확보. 3.4.7 [급속한 성장함수] 참조.

int obstack_alignment_mask (struct obstack *obstack_ptr)

대상물의 시작점을 정돈하기 위해 사용되는 mask. 이것은 lvalue이다. 3.4.9 [obstack에서의 데이터 할당] 참조.

size_t obstack_chunk_size (struct obstack *obstack_ptr)

덩어리 할당을 위한 크기. 이것은 lvalue이다. 3.4.10 [obstack 덩어리] 참조.

void *obstack_base (struct obstack *obstack_ptr)

현재 성장하는 대상물의 임시 시작주소. 3.4.8 [obstack의 위상] 참조.

void *obstack_next_free (struct obstack *obstack_ptr)

현재의 성장하는 대상물이 끝나는 바로 다음의 주소. 3.4.8 [obstack의 위상] 참조.


3.5 다양한 크기로의 자동저장

불충분한 동적 할당을 지원하는 함수들 중의 하나인 alloca는 동적으로 할당을 하지만 자동적으로 해제되지는 않는다. alloca를 가지고 블록을 할당하는 것은 명백한 동작이다; 당신은 원하는 만큼의 많은 블록을 할당할 수 있고 실행 시에 그 크기를 계산할 수도 있다. alloca로 할당된 공간의 변수가 단지 그 함수 안에서 자동변수라고만 선언이 되었다면 그렇게 할당된 블록들은 함수를 빠져나갈 때 모두 해제된다. 하지만 다른 경우는 명백하게 그 공간을 해제하는 방법이 없다. alloca의 프로토타입은 'stdlib.h'이다. 이 함수는 BSD확장이다.

함수 void * alloca (size_t size);

alloca는 불려진 함수의 스택프레임안에 몇 바이트의 크기를 가진 블록을 할당하고 그 블록의 주소 값을 반환한다. 예측할 수 없는 결과를 얻을지도 모르는 함수들의 인수내부에 alloca를 사용하지 말라. 왜냐하면 alloca를 위한 스택의 공간은 함수인수들을 위한 그 공간의 중간에 만들어지기 때문이다. 피해야할 예는 foo (x, alloca(4), y)이다.

 

3.5.1 alloca 예

alloca의 사용 예로서 여기에 한 함수가 있다. 이 함수는 인수로 받은 두 개의 문자열을 붙여서 만든 것을 파일이름으로 해서 그 파일을 열고 그것을 파일기술자에 전달하거나 실패한 경우는 -1을 반환 하는 함수이다.

/* 역시 제가 주석을 좀..주석이 틀렸으면 저에게 왕창 욕해주셔요... */
int open2 (char *str1, char *str2, int flags, int mode)
{
/* alloca를 가지고 두 개의 문자열 길이에 각각 1을 더한 길이만큼의 공간을 char형으로 할당받고 그 포인터를 name에게 주었어요.*/
char *name = (char *) alloca (strlen (str1) + strlen (str2) + 1);
/* strcpy는 문자열을 복사하고 strcat는 문자열을 덧붙이는 함수 */
strcpy (name, str1);
strcat (name, str2);
/* 복사하고 덧붙여서 만든 name을 파일이름으로 해서 파일하나를 열고 그것을 반환 */
return open (name, flags, mode);
}
 
이건 malloc을 사용해서 어떻게 같은 결과를 얻고 해제하는지에 대한 예.
int open2 (char *str1, char *str2, int flags, int mode)
{
/* 말했듯이 malloc을 이용해서 필요한 공간을 할당하네요..*/
char *name = (char *) malloc (strlen (str1) + strlen (str2) + 1);
int desc;
if (name == 0) /* 만약 공간이 할당되지 않았으면 에러처리 */
fatal ("virtual memory exceeded");
strcpy (name, str1);
strcat (name, str2);
desc = open (name, flags, mode); /* 일시적으로 파일 핸들을 decs 에 저장 */
free (name);
return desc;
}

위에서 본 것처럼 alloca를 이용하면 더 간단하게 구현할 수 있겠지만 alloca를 사용하는 것은 많은 이득이 있는 대신에 몇 가지 손해도 얻을 수 있다는 걸 명심하라.

 

3.5.2 alloca를 이용해서 얻는 이득

여기에 malloc보다는 alloca를 선택하게 되는 이유가 있다.

alloca를 사용하면 매우 적은 공간을 소비하고 매우 빠르다. ( 그것은 alloca가 GNU C 컴파일러 상에서 open-coded 이어서..) alloca는 블록을 여러 크기로 나누지 않고 이미 있는 다른 크기의 블록을 재 사용할 수 있기 때문에 메모리 단편화의 원인이 되지 않는다. 호출된 alloca를 통해 만들어진 할당된 공간은 longjmp( 20장 [Non-Local Exits]를 보라. )로 자동적으로 해제할 수 있는 Nonlocal이 있다.

참고로 Nonlocal의 의미를 잘 몰라서 책들을 뒤져보니까 이런 게 나와 있네요.

nonlocal: 구조화된 프로그래밍 언어를 사용하여 작성된 프로그램의 어떤 블록에서 자기 블록에 정의되어 있지 않은 변수를 참조하는 것이라고요... 하지만 여기의 Nonlocal을 의미하는지는 잘 모르겠어요. 제 생각에는 얼추 비슷할 것 같은데..

이것을 구현하기 위해서, 호출에 성공하면 open함수처럼 한 디스크립터를 반환하고 실패하면 아무 것도 반환하지 않는 opne_or_report_error 이라는 함수가 있다고 가정하라. 만약 호출에 실패해서 파일이 열려지지 않으면, 에러 메시지를 프린트하고 longjmp를 사용해서 당신의 프로그램의 한 레벨을 빠져나온다. 이것을 사용해서 open2를 바꾸어보자.

subroutine:
int open2 (char *str1, char *str2, int flags, int mode)
{
char *name = (char *) alloca (strlen (str1) + strlen (str2) + 1);
strcpy (name, str1);
strcat (name, str2);
/* 아까 open2 함수에서 변화된 것은 open함수가 open_or_report_error로 대치된 거네요. */
return open_or_report_error (name, flags, mode);
}

좀더 자세히 제가 이해한대로 보충설명을 드리자면... 실제로 open_or_report_error 이라는 함수가 존재하지 않는데 그냥 있다고 가정하고 쓰면 프로그램 내에서 그것을 호출할 수 없으므로 에러가 나겠지요. 그런데 그 alloca란 놈이 일단 에러가 나면 alloca를 통해 할당된 모든 공간을 해제하고 빠져 나올 수 있기 때문에, 그리고 alloca는 명백하게 공간은 해제해주는 기능이 없으니까.. 이런 편법을 사용하게 되나봐요.

alloca를 통한 작업에서 이 방법을 사용하는 이유는 alloca를 통해 할당된 공간은 에러가 발생하면 어떤 특별한 노력 없이 할당된 공간을 해제할 수 있기 때문이다. 앞의 open2와 비교해서( malloc과 free를 사용하는 ) 이 방법을 사용하면 저장공간의 유출을 발견할 수 있을 것이다. 그러나 당신이 좀더 많은 변화를 원한다면 그렇게 하는 쉬운 방법은 아무 것도 없다.

 

3.5.3 alloca의 단점

이곳은 malloc과 비교하여 alloca의 단점을 보여준다. 만약 당신이 alloca를 가지고 시스템이 제공할 수 있는 것보다 더 많은 저장공간을 할당받길 원하면 당신은 명백한 에러 메시지를 얻을 수 없다. 대신에 당신은 무한 재귀호출에서 얻을 수 있는 것과 같은 심각한 신호를 얻을 것이다.; 아마도 segmentation 위배 ( 21.2.1절[ 프로그램 에러 신호] 참조) 어떤 비GNU 시스템들은 이 alloca를 지원하지 않아서 이식성의 측면에서는 좀 떨어진다. 그렇지만 alloca는 이런 결점에도 불구하고 유용하게 사용된다.

 

3.5.4 GNU C 변할 수 있는 크기의 배열

GNU C에서 당신은 alloca의 사용을 변화 가능한 크기를 갖는 배열을(an array of variable size) 사용해서 대체할 수 있다. 여기에 그것을 보여주는 예가 있다.

int open2 (char *str1, char *str2, int flags, int mode)
{
/* 위에서 alloca를 사용해서 할당했던 공간을 대신 크기가 'str1의 문자길이 + str2의 문자길이 + 1'인 배열을 만들었네요.*/
char name[strlen (str1) + strlen (str2) + 1];
strcpy (name, str1);
strcat (name, str2);
return open (name, flags, mode);
}

그러나 alloca는 여러 가지 이유로 변화 가능한 크기를 갖는 배열로 항상 대체할 수 있는 것은 아니다. 변화 가능한 배열에게 할당된 메모리 공간은 배열이 선언된 영역의 끝에서 해제되지만 alloca로 할당된 공간은 함수가 끝날 때까지 남아 있다.

반복을 사용했을 때 각 반복마다 더해서 블록을 할당하는 loop에서 alloca를 사용하는 것은 가능하다. 하지만 변화 가능한 크기를 갖는 배열로는 불가능하다.

 

주의 : 만약 당신dl alloca와 변화 가능한 크기를 갖는 배열을 한 함수 안에서 혼용하면, 변화 가능한 크기를 갖는 배열이 선언된 영역을 벗어날 때 그 영역이 실행되는 동안에 alloca로 할당했던 모든 블록들도 해제된다는 것을 명심하라.


3.6 재조정 할당자

어떠한 체제의 동적 메모리 할당이든 간에 부가물을 갖는다: 그것이 사용하는 공간의 양은 프로그램이 요구하는 공간의 양보다 크다. 메모리 재조정 할당자는 독자적 판단으로 필요한 만큼의 메모리 블럭을 이동시킴으로써 부가물을 최소화한다.

 

3.6.1 재조정 할당자의 개념

당신이 malloc을 써서 한 블럭을 할당하고 나면, 당신이 그 크기를 변경할 목적으로 realloc을 사용하지 않는 이상 그 블럭의 주소는 결코 변하지 않는다. 그러므로, 당신은 당신이 원하는 대로 다양한 공간에서 그 주소를 일시적으로 또는 영구적으로 안전하게 보관할 수가 있다. 그러나 당신이 재조정 할당자를 사용하려 할 때는 안전할 수가 없다. 왜냐하면, 당신이 어떤 방식으로든 메모리를 할당하려 할 때마다 모든 재조정 가능한 블럭들이 움직일 수 있기 때문이다. malloc이나 realloc을 호출하는 일조차도 재조정 가능한 블럭들을 움직일 수 있다. 각각의 재조정가능 블럭들에 대해서 당신은 그 블럭의 주소가 저장될 수 있도록 메모리 상에 핸들_포인터 지정을 만들어야 한다. 재조정 할당자는 각 블럭의 핸들의 위치를 알고 있어서 블럭을 옮길 때마다 그곳에 저장된 주소를 갱신하며, 그 결과로 핸들은 언제나 그 블럭을 가리키게 된다. 당신이 블럭의 내용에 접근하고자 할 때마다, 핸들에서 그 블럭의 갱신된 주소를 가져와야만 한다.

부호취급자로부터 재조정 할당자 함수들을 호출하는 것은 거의 분명히 부정확하다. 왜냐하면 그 부호는 언제든지 변할 수 있고 모든 블럭을 재조정할 수 있기 때문이다. 이것을 안전하게 할 수 있는 유일한 방법은 재조정 가능한 어떤 블럭의 내용도 그 부호에 접근하지 못하도록 봉쇄하는 것이다. 21.4.6 [재진입금지] 참조.

 

3.6.2 재조정 가능한 블럭의 할당과 해제

아래의 서술에서,handleptr이 핸들의 주소를 가리킨다. 모든 함수들은 'malloc.h'에 선언되어 있다. 모든 것은 GNU의 확장이다.

함수 void * r__alloc (void **handleptr, size_t size)

이 함수는 재조정 가능한 size 크기의 블럭을 할당한다. 이 함수는 그 블럭의 주소를 *handleptr에 담고 성공했을 때 널(null)이 아닌 문자를 반환한다. 만약 r_alloc이 필요한 공간을 얻지 못하면 *handleptr에 널 포인터를 저장하고 나서 널 포인터를 반환한다.

함수 void r__alloc__free (void **handleptr)

이 함수는 재조정 가능한 블럭을 해제하는 용도로 쓰인다. 이 함수는 *handleptr이 가리키는 블럭을 해제하고 더이상 어떤 할당된 블럭도 가리키고 있지 않음을 표시하기 위하여 *handleptr에 널 포인터를 담아둔다.

함수 void * r__re__alloc (void **handleptr, size_t size)

함수 r_re_alloc은 *handleptr이 가리키는 블럭의 크기를 조정하는데 size바이트만큼 크게 한다. 이 함수는 *handleptr에 크기가 변한 블럭의 주소를 저장하고 성공을 하게되면 널이 아닌 포인터를 반환한다. 만약 충분한 메모리를 사용할 수 없게 되면, 이 함수는 널 포인터를 반환하고 *handleptr을 변경하지 않는다.


3.7 메모리 사용 경고

당신은 memory_warnings를 호출함으로써 프로그램이 메모리 공간을 다 써버리지 않도록 경고를 요청할 수 있다. 이 함수는 운영체제로부터 더 많은 메모리를 요청할 때마다 메모리 사용을 체크하도록 malloc에게 알려준다. 이것은 'malloc.h'에 선언되어 있는 GNU의 확장이다.

void memory__warnings (void *start, void (*warn_func) (const char function *))

실제적인 메모리가 고갈되어가고 있음을 경고하려할 때 이 함수를 호출하라. 인수 start는 메모리 상에서 데이터 공간이 시작되는 곳을 말한다. 할당자는 이것을 사용된 마지막 주소와 대조하고 또 데이터 공간의 한계와 대조하여, 사용 가능한 메모리의 단편을 사용토록 결정한다. 만약 당신이 start에 0을 대입하게 되면 대부분의 환경에서 쓰일 수 있는 내정 값이 사용되게 된다. 경고함수를 사용할 때 malloc이 당신에게 경고를 주기 위해 호출할 수 있는 함수를 사용하라. 그것은 인수로서 문자열(경고 메시지)을 동반하여 호출된다. 보통 그 문자열은 사용자가 읽을 수 있게끔 디스플레이 된다. 경고는 메모리의 75%, 85%, 95%를 사용했을 때 주어진다. 95%가 넘었을 때는 메모리가 쓰일 때마다 경고를 얻을 수 있다.


목차  이전 : 2. 에러보고 다음 : 4. 문자 다루기

'Programming > Unix Programing' 카테고리의 다른 글

5. 문자열과 배열 유틸리티  (0) 2007.12.22
4. 문자 처리  (0) 2007.12.22
3. 메모리 점유  (0) 2007.12.22
2. 에러 보고  (0) 2007.12.22
1. 안내문  (0) 2007.12.22
Unix Programming  (0) 2007.12.22
Comment 0 Trackback 0
Top

2. 에러 보고

목차 이전 : 1. 안내문 다음 3. 메모리 할당


2 에러보고

GNU C의 많은 함수들은 에러 조건들을 조사하여 보고하며, 때로는 당신의 프로그램이 이러한 에러 조건들을 조사할 필요가 있다. 예를 들어, 당신이 입력파일을 열 때, 당신은 그 파일이 실제로 정확하게 열릴 수 있는지를 확정해야 하며, 에러메시지를 기록하고, 라이브러리 함수 호출에 실패하면 다른 적절한 조치를 취해야 한다.

이 장에서는 에러보고 도구들이 어떻게 작동하는지를 설명하겠다. 이 도구들을 사용하려면 프로그램에 헤더파일 'errno.h'를 포함하라.


2.1 에러 체크

대부분의 라이브러리 함수들은 실패할 때에 지정하는 특정한 값을 반환한다. 그 특정한 값이란 전형적으로 -1,널(null) 포인터, EOF라는 상수 등이다. 그러나 이러한 반환 값은 에러가 발생했다는 사실만을 지적해 준다. 어떤 종류의 에러가 발생했는지를 알려면, 변수 errno에 저장된 에러코드를 조사할 필요가 있다. 이 변수는 헤더파일 'errno.h'에 선언되어 있다.

변수 volatile int errno

변수 errno는 시스템 에러 숫자를 담는다. 당신은 errno의 값을 바꿀 수 있다. errno는 volatile로 선언되어 있으므로 부호취급자에 의해 바꿔질 수도 있다. 21.4 [취급자 정의하기] 참조. 그러나, 합당하게 씌어진 부호취급자는 errno의 값을 저장하기 때문에, 당신이 부호취급자를 적어야 할곳을 제외하고는 바뀔 가능성에 대해서 우려할 필요는 없는 것이다.

프로그램이 시작할 때 errno의 초기 값은 제로이다. 많은 라이브러리 함수들은 어떤 에러를 만날 때 그것을 제로가 아닌 값으로 만들도록 되어있다. 이러한 에러 조건은 각 함수에 기록되어 있다. 이 함수들이 성공했을 때에는 errno를 바꾸지 않는다; 그러므로, 당신은 호출이 실패했는지를 결정하지 않고서는 errno를 사용할 수 없다. 그것의 적절한 사용법은 각 함수에 기록되어 있다. 호출이 실패하게 되면, 당신은 errno를 검사할 수 있다.

많은 라이브러리 함수들은 다른 라이브러리 함수들을 호출하다가 실패한 결과로서 errno의 값을 제로 아닌 값으로 만들 수 있다. 당신은 어떠한 라이브러리 함수라도 그 함수가 에러를 반환 할 때에 errno의 값을 변경할 수 있음을 알아야 한다.

 
운용 노트 : ANSI C에서는 errno를 변수로서 다루기보다는 "변경 가능한 왼편 값"으로 규정하고, 그것이 매크로로 완성되는 것을 허용하고 있다. 예를 들면, 그것의 확장은 *_errno()와 같이 함수 호출을 포함할 수 있다. 사실상, 그것은 GNU 시스템 상에 존재하는 그대로이다. 비 GNU시스템 상에서 GNU 라이브러리는 특정한 시스템이 혀용 하는 모든 것을 할 수가 있다.

에러가 발생할 경우에 sqrt와 atan과 같이 완벽하게 적합한 값만을 반환하게 하는 몇몇 라이브러리 함수들도 있다. 이와 같은 함수들의 경우에는 만약 당신이 에러가 일어났는지를 체크하고자 한다면 함수를 호출하기 전에 지정된 방법으로 errno에 제로를 담아두고, 그런 다음에 그 값을 체크하게 된다. 모든 에러 코드는 부호 명칭을 갖고 있다; 그것들은 'errno.h'에 정의된 매크로이다. 그 명칭들은 'E'와 대문자 또는 구두점으로 시작된다. 당신은 이러한 명칭들을 예약된 명칭으로 생각하는 게 좋다. 1.3.3.[예약된 명칭] 참조.

에러코드의 값은 모두 양수의 정수이며 모두 별개로 구분된다. 단 하나의 예외가 있다: EWOULDBLOCK과 EAGAIN은 같은 경우이다. 이 값들은 분명한 것이므로, 당신이 switch 구문에서 라벨로 사용할 수 있다; 물론 EWOULDBLOCK과 EAGAIN을 그대로 사용할 수는 없다. 당신은 프로그램에서 이들 부호 정수의 특정한 값에 대해서 다른 가정을 해서는 안 된다.

errno의 값이 반드시 이러한 매크로들의 값과 일치할 필요는 없다. 왜냐하면, 어떤 라이브러리 함수들은 각각의 상황에 따라 다른 에러 코드를 반환할 수도 있기 때문이다. 특정한 라이브러리 함수에서만 의미를 갖도록 되어있는 값들은 이 안내서가 그 함수의 값으로 보여주는 바로 그 값이다. 비 GNU 체제에서는 대개의 경우 어떤 시스템이 유효하지 않은 포인터가 인수로서 주어질 때에는 EFAULT를 반환할 수 있다. 이것은 당신의 프로그램에서 버그의 결과로서 나타나고,GNU 체제에서는 일어나지 않기 때문에 우리는 개개의 함수를 기술할 때 EFAULT를 언급하지 않고 공간을 저장해 두었다.


2.2 에러 코드

에러코드 매크로는 헤더파일 'errno.h'에 정의되어 있다. 모든 에러코드 매크로는 정수의 상수 값으로 발전한다. 어떤 에러코드들은 GNU 체제상에서는 일어나지 않는다. 그러나 다른 체제에서 GNU의 라이브러리를 사용할 때에 발생할 수 있다.

매크로 int EPERM

허가되지 않은 작동; 파일의 소유자(또는 다른 자원)나 명령을 수행할 수 있는 특별한 권리를 가진 프로세스.

매크로 int ENOENT

그러한 파일이나 디렉토리 없음: 이것은 이미 존재하고 있을 걸로 예상한 파일이 없는 경우에 일어나는 "파일이 존재하지 않습니다"라는 에러이다.

매크로 int ESRCH

처리가 지정된 처리 ID와 합치되지 않는다.

매크로 int EINTR

가로채기 함수 호출; 발생한 비동기 신호와 호출의 방해된 종료. 이럴 경우에 당신은 다시 호출을 시도해 보라. 당신은 EINTR로 실패하기보다는, 함수들을 취급된 부호 다음에서 시작되도록 선택할 수 있다; 21.5 [가로채기 프리미티브] 참조.

매크로 int EIO

입출력 에러; 언제나 물리적인 입출력 에러에 사용됨.

매크로 int ENXIO

그런 장치나 주소가 없음. 시스템이 당신이 파일에서 설정한 장치를 사용하고자 하나 그러한 장치를 찾을 수 없었다. 이것은 장치파일이 잘못 인스톨되었거나, 물리적인 장치를 빠뜨렸거나 또는 컴퓨터와 제대로 부합되지 않았음을 의미한다.

매크로 int E2BIG

인수가 너무 길다; 실행함수에 의해서 실행되는 새로운 프로그램에 ( 23.5 [파일 실행하기] 참조) 주어진 인수가 너무 큰 메모리 공간을 사용할 때. 이러한 경우는 GNU체제에서는 생기지 않는다.

매크로 int ENOEXEC

유효하지 않은 실행파일 포맷. 이러한 조건은 실행함수에 의해 조사된다. 23.5 [파일 실행하기] 참조.

매크로 int EBADF

잘못된 파일 기록; 예를 들어, 닫혀진 파일을 기록하려고 하든지 쓰기 모드로 열려진 파일을 읽으려고 하는 경우(그 반대의 경우도)

매크로 int ECHILD

자식 프로세스(child process)가 없다. 이 에러는 자식 프로세스를 다루는 오퍼레이션을 사용했는데 다루기 위한 어느 프로세스도 존재하지 않을 때 발생한다.

매크로 int EDEADLK

교착상태 회피; 시스템 자원의 점유는 교착상태의 결과를 낳는다. 시스템이 모든 상황을 다 알아챌 거라고 보증하지 못한다. 이 에러는 당신이 운이 좋아서 시스템이 알아챈 것임을 의미한다; 시스템은 망설이고 있을 뿐이다. 8.11 [파일 교착] 참조.

매크로 int ENOMEM

이용할 메모리가 없음. 메모리 용량을 다 썼으므로 시스템이 더이상 메모리를 할당할 수 없다.

매크로 int EACCES

허용되지 않음; 파일이 시도하려는 작동을 허용하지 않는다.

매크로 int EFAULT

주소 오류; 유효하지 않은 포인터가 발견됨.

매크로 int ENOTBLK

어떤 상황에서 주어진 파일에 특별한 블록이 없는 경우. 예를 들면, 보통의 파일을 유닉스 파일 시스템에 마운트하려 하면 이 에러가 발생한다.

매크로 int EBUSY

시스템 자원 사용중; 분배될 수 없는 시스템 자원이 이미 사용중일 때, 예를 들어, 당신이 현재 마운트된 파일시스템의 루트에서 한 개의 파일을 지우려 할 때에, 이 에러를 만난다.

매크로 int EEXIST

파일이 존재: 새로운 파일로 만들겠다고 한 파일이 이미 존재한다.

매크로 int EXDEV

파일 시스템이 인지할 수 없는 영역에 부적당한 링크를 만들려고 시도할 때 이 에러메시지가 나온다. 이것은 당신이 링크를 사용할 때만 발생하지만 ( 9.3절 [Hard Links] 를 보라 ) 또, rename으로 파일을 재 명명할 때 발생하기도 한다. ( 9.6절 [Renaming Files] 를 보라)

매크로 int ENODEV

디바이스의 특별한 정렬을 하는 함수에 주어진 디바이스가 잘못된 타입이다.

매크로 int ENOTDIR

필요하다고 요청된 디렉토리가 존재하지 않을 때 발생.

매크로 int EISDIR

파일이 하나의 디렉토리이다; 쓰기(writing)위해 이 디렉토리를 열려고 시도할 때 이 에러는 발생한다.

매크로 int EINVAL

적합하지 않은 인수. 이것은 라이브러리 함수에 잘못된 인수를 주는 것과 같은 종류의 다양한 문제를 지적하는데 사용한다.

매크로 int ENFILE

지금 현재 프로세스에 너무 많은 파일이 오픈 되어서 더 이상 오픈할 수 없다. 복제된 기술자가 이 제한에 대하여 셈한다.

매크로 int ENFILE

전체 시스템에 열려져있는 너무 많은 개별파일들이 있다. 연결된 채널의 어떤 것이라도 파일 하나를 여는 것과 같이 센다.; 8.5.1절의 [연결된 채널]을 참조하라. 이 에러는 결코 GNU 시스템에서는 발생하지 않는다.

매크로 int ENOTTY

하나의 보통 파일에서 터미널 모드를 정하려 시도하는 것과 같은 부적합한 입출력 제어 오퍼레이션에 발생.

매크로 int ETXTBSY

현재 사용되고 있는 파일을 다시 읽거나 쓰기 위해 오픈 하려 시도할 때 발생 ("text fiel busy" 라고 한다.) 이것은 GNU시스템에서는 에러가 아니다; 텍스트는 필요하면 카피된다.

매크로 int EFBIG

파일이 너무 크다; 파일의 크기가 시스템이 허용하는 것 보다 더 크다.

매크로 int ENOSPC

디바이스에 공간이 남겨지지 않았다.; 파일에 쓰기 명령을 줬을 때 디스크가 가득 차서 공간이 남아있지 않으면 실패한다.

매크로 int EXPIPE

적합하지 않은 탐색 명령.( pipe에서 처럼 )

매크로 int EROFS

읽기 전용으로 된 어떤 파일시스템에 무언가를 수정하려고 시도할 때

매크로 int ENLINK

너무 많이 연결.; 하나의 단일한 파일의 링크 수가 너무 길다. 재 명명하다 이미 할 수 있는 것보다 많은 링크를 가진 한 파일을 재 명명하면 이 에러의 원인이 된다. ( 9.6절 [파일 재 명명] 참조)

매크로 int EPIPE

부러진 파이프; 다른 파이프의 끝에서 프로세스를 읽는 것은 불가능하다. 모든 라이브러리 함수는 SIGPIPE로 일반화된 에러코드를 반환 한다. 이 신호는 만약 처리되지 않거나 막아지지 않으면 그 프로그램을 멈추게 한다. 그러므로 당신의 프로그램이 처리되거나 블록된 SIGPIPE를 갖지 않으면 항상 EPIPE가 보일 것이다.

매크로 int EDOM

도메인 에러; 인수의 값이 정의된 함수를 지나 도메인에게 전달되지 않았을 때 수학적 함수에 의해 사용된다.

매크로 int ERANGE

범위 에러; 결과 값이 오버플로우나 언더플로우로 인해 표현되지 않을 때 수학적 함수에 의해 사용된다.

매크로 int EAGAIN

자원을 일시적으로 사용할 수 없다. 그 호출은 나중에 당신이 다시 시도 할 수 있도록 한다. 오직 분기점에서 이러한 이유로 EAGAIN에러 코드를 반환 한다.

매크로 int EWOULDBLOCK

비블록화 모드로 정해진 어떤 대상에 블록을 시도하려 하는 오퍼레이션에 대해 발생.
 
이식성 : 4.4BSD와 GNU에서 EWOULDBLOCK 와 EAGAIN은 같다. BSD초기 버전에서는 이것이 두 개의 독립적 코드로서 EWOULDBLOCK는 비블록화 모드로 정해진 대상에 블록을 시도하는 입출력 오퍼레이션을 지적하는데 사용하고 EAGAIN은 에러의 다른 종류에 사용했다
매크로 int EINPROGRESS
비블록화 모드로 선택된 대상에 일으킨 완전하지 않은 오퍼레이션에 대해 발생. 어떤 함수들은 항상, 블록을( connect처럼 ; 11.8.1절의 [연결]을 참조) 결코 반환하지 않아야 한다.EWOULDBLOCK 대신에 그들은 어떤 시간을 취하고( 즉 일정 시간이 지난 후) 시작되는 오퍼레이션을 지적하기 위해 EINPROGRESS를 반환 한다. 그 호출에 EALREADY가 반환 되어 완전하기 전에 그 대상을 다루려 시도할 때.

매크로 int EALREADY

한 오퍼레이션이 비블록화 모드의 선택을 가진 대상에 이미 진행중이다.

매크로 int EALREADY

소켓을 요청했을 때 지정된 소켓이 존재하지 않는다.

매크로 int EDESTADDRREQ

목적 주소가 그것이 필요한 소켓 오퍼레이션에 공급될 수 없다.

매크로 int EMSGSIZE

소켓에 보낸 메시지의 크기가 지원되는 최대 크기보다 크다.

매크로 int EPROTOTYPE

그 소켓 타입이 요청된 통신 프로토콜에서 지원하지 않는다.

매크로 int ENOPROTOOPT

당신은 소켓에 의해 사용되어지고 있는 특별한 프로토콜에서 이해할 수 없는 소켓옵션을 지정하였다. ( 11.11절의 [Socket Options]를 참조)

매크로 int EPROTONOSUPPORT

그 소켓 도메인은 요청한 통신 프로토콜을 지원하지 않는다. ( 아마도 요청된 프로토콜이 완전히 부적합하다.) 11.7.1절의 [Creating a Socket]를 보라.

매크로 int ESOCKTNOSUPPORT

그 소켓타입을 지원하지 않는다.

매크로 int EOPNOTSUPP

당신이 요청한 그 오퍼레이션을 지원하지 않는다. 어떤 소켓함수는 소켓의 모든 타입들에서 이해할 수 없고 다른 것들은 모든 통신 프로토콜을 충족시키지 못할 것이다.

매크로 int EPFNOSUPPORT

당신이 요청한 소켓통신 프로토콜 부류들은 지원하지 않는다.

매크로 int EAFNOSUPPORT

소켓을 위하여 지정된 주소의 부류들이 지원되지 않는다; 그 주소가 소켓에서 사용되는 프로토콜과 일치하지 않는 것이다. 11장[Sockets] 참조하라.

매크로 int EADDRINUSE

요청된 소켓주소가 이미 사용중이다. 11.3절 [Socket Addresses] 를 보라.

매크로 int EADDRNOTAVAIL

요청된 소켓주소가 유용하지 않다.; 예를 들어 당신이 소켓이름으로 주려고 시도한 것이 로컬 호스트 이름과 맞지 않다. 11.3절[Socket Address] 를 보라.

매크로 int ENETDOWN

소켓 오퍼레이션이 네크웍 다운이 이유가 되어 실패했다.

매크로 int ENETUNREACH

소켓 오퍼레이션이 호스트가 포함하고 있는 subnet에 도달할 수 없어서 실패했다.

매크로 int ENETRESET

원격 호스트가 파괴되었기 때문에 네트웍 연결을 다시 지정한다.

매크로 int ECONNABORTED

네트웍 연결이 실패하였다. 네트웍 연결이 단절되었는데 그 이유는 원격 시스템이 재부팅 하거나 아니면 복구할 수 없는 프로토콜 위반인 경우처럼 로컬 호스트의 제어를 벗어난 것이 이유이다.

매크로 int ENOBUFS

입출력 오퍼레이션을 위한 커널의 버퍼들이 모두 사용중이다.

매크로 int EISCONN

당신은 이미 연결된 소켓을 다시 연결하려 시도하고 있다. 11.8.1절[Connectings] 를 보라.

매크로 int ENOTCONN

그 소켓은 어느 것과도 연결할 수 없다. 당신이 첫째로 데이터의 목적지를 정하지 않고 한 소켓을 통해 데이터를 전송하려 할 때 이 에러가 발생한다.

매크로 int ESHUTDOWN

그 소켓은 이미 폐쇄되었다.

매크로 int ETIMEDOUT

정해진 타임아웃을(timeout) 갖은 소켓 오퍼레이션이 정해진 시간 동안 응답을 받지 못했다.

매크로 int ECONNREFUSED

원격 호스트가 네트웍 연결에 대한 허용을 거절하였다. (특별히 요청된 서비스가 실행되지 않기 때문에)

매크로 int ELOOP

파일이름을 탐색하려는데 너무 많은 수준의 기호연결(sysbolic links)이 있다. 이것은 종종 기호연결의 한 주기를 가리킨다.
참고로 sysbolic links는 데이터베이스 프로그램과 스프레드시트 등과 같은 프로그램들 사이에서 상호 간의 원활한 데이터 교환을 위해 사용되는 데이터 파일의 형식이래요.

매크로 int ENANETOOLONG

파일 이름이 너무 길거나( PATH_MAX보다 더; 27.6절 [Limits for Files]를 참조하라) 호스트 이름이 너무 길다.( gethostname 이나 sethostname에 있는; 26.1절 [Host Identification])

매크로 int EHOSTDOWN

요청된 네트웍 연결을 위한 원격 호스트가 다운이다.

매크로 int EHOSTUNREACH

요청된 네트웍 연결을 위한 원격 호스트에 도달할 수 없다.

매크로 int ENOTEMPTY

빈 디렉토리라고 예상했던 곳이 비어있지 않다. 특별히 이 에러는 당신이 디렉토리를 지우려 시도할 때 발생한다.

매크로 int EUSERS

파일할당 시스템이 너무 많은 유저로 인해 혼란하다.

매크로 int EDQUOT

사용자의 디스크 할당이 초과되었다.

매크로 int ESTALE

맛이간 NFS 파일 핸들. 이것은 파일 시스템이 서버 호스트 상에서 재 정렬한 것에 기인한 것으로 NFS 시스템 안의 내부적 혼란을 지적한다.

매크로 int ERENOTE

이미 NFS 마운트 파일로 지정된 한 파일을 한 원격 파일 시스템이 NFS 마운트로 만들려 시도할 때 발생. (이것은 운영체제상의 에러이지만 우리는 이 에러를 불가능한 코드로 만들고 GNU시스템 상에서 작업하기를 기대하는 것이다.)

매크로 int ENOLCK

유용한 락이 아니다. 이것은 파일 락킹 함수들에 의해 사용된다. 8.11절 [File Locks] 참조. 이 에러는 GNU 시스템에서는 결코 발생하지 않는다.

매크로 int ENOSYS

함수가 이행되지 않았다. 어떤 함수들은 정의된 옵션이나 명령들이 어떤 것에서도 지원되지 않는 것이 있다. 만약 당신이 요청한 함수에서 이런 에러를 얻는다면 그것들은 지원되지 않는 것이다.

매크로 int EBACKGROUND

GNU 시스템에서 어떤 오퍼레이션의 호출자가 터미널의 전면처리 그룹에 없을 때 서버지원 프로토콜에 이 에러가 반환된다. 사용자들은 보통 이 에러를 보지 못하는데 왜냐하면 함수들은 SIGTTIN이나 SIGTTOU신호로 해석하여 읽고 쓰기 때문이다. 24장[Job Control] 를 프로세스 그룹과 이들은 신호의 정보를 위해 보라.

매크로 int ED

경험 있는 사용자는 무엇이 잘못인지 알 것이다.

매크로 int EGREGIOUS

당신이 무엇을 했지?

매크로 int EIEIO

집에 가서 따뜻하고, 신선한 한잔의 우유를 마셔라

매크로 int EGRATUITOUS

이 에러 코드는 목적이 없다.


2.3 에러 메시지

라이브러리에는 당신의 프로그램이 라이브러리를 호출하다가 실패할 때에 흔히 일어나는 에러를 메시지로 보낼 수 있도록 하는 함수와 변수들이 있다. 함수 strerror와 perror는 특정한 에러코드로 정형화된 에러메시지를 보낸다; 변수 program_invocation_short_name은 에러가 난 프로그램의 명칭을 쉽게 찾도록 해준다.

함수 char * strerror (int errnum)
 
strerror 함수는 인수 errnum을 받아서 에러코드(2.1 [에러 체크] 참조)를 문자열을 기술한 에러메시지로 그려낸다. 반환 값은 이 문자열의 포인터이다. 인수 errnum의 값은 보통 변수 errno에서 전달된다. 당신은 함수 strerror가 반환한 문자열을 변형시켜서는 안 된다. 또한, 만약 당신이 strerror를 계속 호출하게 되면, 그 문자열은 덧씌워질 것이다. (그러나 어떠한 라이브러리 함수도 당신이 알지 못하게strerror함수를 호출하지 않음은 믿어도 좋다.)
함수 strerror는 헤더파일 'string.h'에 선언되어 있다.

함수 void perror (const char *message)

이 함수는 에러메시지를 문자열 stderr에 기록한다; 7.2 [표준 문자열] 참조. 만약에 널(null) 포인터라든지 공백 문자열인 message를 써서 perror를 호출하게 되면, perror는 errno에 해당하는 에러메시지를 기록하고, 뒤에 새로운 행을 추가한다. 그렇지 않고 널(null)이 아닌 인수 message를 쓰게 되면,perror는 에러메시지 앞에 해당 문자열을 갖다둔다. 거기에는 errno에 해당하는 에러문자열로부터 메시지를 분리하기 위해서 콜론(:)과 공백문자를 추가한다. 함수 perror는 'stdio.h'에 선언되어 있다.

strerror와 perror는 어떤 에러코드가 주어지든지 간에 같은 메시지를 만들어낸다; 규정된 텍스트는 시스템에 따라 변한다. GNU 체제에서는 메시지가 아주 짧다; 두줄 이상의 메시지나 삽입된 줄은 아예 없다. 각 에러메시지는 대문자로 시작하며 구두점은 쓰지 않는다.

 
호환성 노트 : 함수 strerror는 ANSI C의 새로운 특성이다. 과거의 많은 C언어 체제에서는 이 함수가 제공되지 않았다. 터미널로부터 입력을 읽어들이지 않는 많은 프로그램들은 어떤 시스템 호출이 실패하게 되면 종료되도록 만들어져 있다. 전통적으로, 그와 같은 프로그램으로부터 나오는 에러메시지는 디렉토리는 기술하지 않고 그 프로그램의 명칭만으로 시작한다. 당신은 그 명칭을 변수 program_invocation_short_name에서 찾아볼 수 있다; 완전한 파일 명칭은 변수 program_invocation_name에 저장된다:

변수 char *program_invocation_name;

이 변수의 값은 현재 진행중인 프로그램 실행을 시작하는데 사용되는 명칭이다. 그것은 argv[0]과 동일하다. 이것이 반드시 유효한 파일명칭을 필요로 하는 것이 아님을 주의하라; 흔히 유효한 파일명칭은 디렉토리 명칭을 담고있지 않다. 22.1 [프로그램 인수] 참조.

변수 char * program__invocation__short__name

이 변수의 값은 현재 처리중인 프로그램을 부르는 데 사용되는 명칭으로서 디렉토리 명칭이 제거된 것이다.(말하자면, 이것은 program_invocation_name에서 마지막 슬래쉬까지를 빼버린 것과 동일하다.)

라이브러리 초기화 코드는 메인 함수를 호출하기 전에 이 두 개의 변수를 구축(構築)한다.

 
운용 노트 : 이 두개의 변수는 GNU의 확장이다. 만일 당신이 GNU 이외의 라이브러리를 쓰고 싶으면, 메인 함수에서 argv[0]의 값을 저장한 다음에 당신이 손수 디렉토리의 명칭을 제거하면 된다. 우리는 메인 함수에서 명시적인 표현을 하지 않고서 그 자체에서 에러 보고용 서브루틴을 담고 있도록 하기 위하여 이와 같은 확장을 하게 되었던 것이다.

파일을 제대로 열지 못하고 실패를 하게 되는 예를 들어보기로 하자. 함수 open_sesame은 읽기 모드로 주어진 파일을 열고 성공하면 문자열을 반환한다. 라이브러리함수 fopen은 파일을 열 수 없을 때 널(null) 포인터를 반환한다. 이 같은 상황에서,open_sesame은 함수 strerror를 사용하는 적절한 에러 메시지를 만들어내고서 프로그램을 종료한다. 만약 우리가 strerror에 에러 코드를 보내기 전에 다른 어떤 라이브러리를 호출하고자 한다면, 우리는 그것을 지역 변수에 저장하여야만 한다. 왜냐하면, 그 다른 어떤 라이브러리 함수들이 동시에 errno를 덧씌울 것이기 때문이다.

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
/*으음..함수의 형태로 FILE을 선택할 수도 있었군*/
FILE * open_sesame (char *name)
/*요건 인수로 문자형 포인터를*/
{
FILE *stream;
/*우잉? 이건 파일명칭이 포인터로? 흐흐흐..교과서적인 내용*/
 
errno = 0; /*요건 뭔가 이상타? errno를 int라고 선언하지 않아도 되남?*/
 
stream = fopen (name, "r"); /*파일을 읽기 모드로 열고*/
if (stream == NULL) /*파일이 존재하지 않으면?*/
{
fprintf (stderr, "%s: Couldn't open file %s; %s\n",
program_invocation_short_name, name, strerror (errno));
/*아아! fprintf 함수는 (1) 무엇을 (2)어디에서 읽어서 (3)어딘가에 쓰는 것 3부분으로 구성되네요! 이 몸은 이런 건 외울 수가 없어요==건망증==프로그램 짜다가 필요하면 그때 보지요*/
 
exit (EXIT_FAILURE); /*난 exit와 return의 차이도 몰러, 흑*/
} else /*파일이 존재하면?*/
return stream;
}
}

목차 이전 : 1. 안내문 다음 3. 메모리 할당

'Programming > Unix Programing' 카테고리의 다른 글

5. 문자열과 배열 유틸리티  (0) 2007.12.22
4. 문자 처리  (0) 2007.12.22
3. 메모리 점유  (0) 2007.12.22
2. 에러 보고  (0) 2007.12.22
1. 안내문  (0) 2007.12.22
Unix Programming  (0) 2007.12.22
Comment 0 Trackback 0
Top

prev 1 2 3 4 next