'errcode' 는 에러코드이고, 'preg' 는 에러가 발생한 패턴버퍼이며, 'errbuf' 는
에러 버퍼이며, 'errbuf_size' 는 'errbuf' 의 크기입니다.
regerror 는 'errcode' 에 대응하는 에러 문자열의 바이트 크기(널문자까지 포함)
를 반환합니다. 'errbuf' 와 'errbuf_size' 가 0이 아니라면, 'errbuf' 에 처음
errbuf_size-1 문자의 에러 문자열을 널문자를 추가해서 돌려줍니다.
'errbuf_size' 는 'errbuf' 의 바이트 크기보다 작거나 같은 양수이어야 합니다.
여러분들은, 'regerror' 의 에러 문자열을 담아내는 데 얼마만큼 크기의'errbuf'
가 필요한지 알아보기 위해서 'errbuf' 를 NULL로, 'errbuf_size' 를 0으로 해서
호출할 수 있습니다.
6.2.5 바이트 옵셋 사용하기
---------------------------
POSIX 에서, regmatch_t 형 변수는 GNU 의 레지스터와 비슷하지만, 똑같지는 않
습니다. POSIX 에서 레지스터의 정보를 얻으려면 regexec 에, regmatch 형 변수인,
0이 아닌 'pmatch'를 넘겨줄 수 있습니다. regmatch_t 형 구조체는 다음과 같습
니다.
'reg' 의 'pmatch', pmatch[i]->rm_so 는 regs->start[i] 와 대응하고
pmatch[i]->rm_eo 는 regs->end[i] 와 대응합니다.
6.2.6 POSIX 패턴 버퍼를 Free 하기
----------------------------------
패턴 버퍼에 할당된 것을 free 하는 함수는 'regfree' 입니다.
void regfree (regex_t *preg)
'preg' 는 free 할, 할당된 패턴버퍼입니다. regfree 는 또한 'preg'의 'allocated'
와 'used' 필드를 0으로 설정합니다. 패턴 버퍼를 free 한 이후에는, 매칭 작업을
수행하기 전에 정규표현식을 해당 패턴 버퍼에 다시 컴파일해야 합니다.
6.2.7 POSIX Regex 로 egrep 만들기
----------------------------------
grep 은 기본 정규표현식을 사용하고, egrep 은 확장 정규표현식을 사용하는데,
여기서는 egrep 의 기능을 간단하게 구현해 보도록 하겠습니다.
지금까지 설명한 기능만으로도 egrep 의 기본적인 기능은 쉽게 만들 수 있습니다.
grep 류의 기본적인 기능은 '매칭' 이 아니라 '검색'이기 때문입니다.
우리가 만들'egrep' 을 'my_egrep' 이라고 부른다면, 'my_egrep' 의 기본적인
작동은 다음과 같이 하도록 합시다.
1) 특별한 옵션은 지원하지 않고, 인자는 모두 패턴이나 파일명으로 처리한다.
2) 입력파일명이 명시되지 않았을 경우에는 표준입력에서 받는다.
3) 컴파일 플래그는 'REG_EXTENDED' 를 사용하여 확장정규표현식을 지원한다.
/* POSIX Regex 테스트 프로그램 : egrep 의 기본 기능 구현
*
* Designed by Han-donghun, 1997.5.31
*
* name : my_egrep.c
*
* compile : First, you must have "regex.c" and "regex.h",
* in the current directory.
*
* To get "regex.o " , type "gcc -c regex.c"
* Finally, to compile my_egrep.c, type follow.
*
* "gcc -o my_egrep my_egrep.c regex.o"
*
* usage : my_egrep pattern [files...]
*
* This is simple "pattern search" program
* using POSIX regex, like egrep.
*/
GNU REGEX (정규표현식) 프로그래밍 강좌 (5)
-------------------------------------------
6.3 GNU Regex 함수
------------------
특별히 POSIX 나 버클리 UNIX 에 호환성을 생각하지 않아도 된다면, GNU regex
함수를 사용하는 것이 여러모로 좋을 지 모르겠습니다.
GNU regex 함수도 이전에 설명드린 POSIX 나 BSD regex 함수의 기능을 포함하
고 나머지 여러개의 복잡한 기능을 추가한 것입니다.
그럼, 하나씩 알아보도록 하겠습니다.
6.3.1 GNU 패턴 버퍼
--------------------
GNU regex 는 GNU 패턴 버퍼를 이용하여 컴파일된 정규표현식을 활용합니다.
이 패턴 버퍼는 POSIX regex 에서 설명하였으므로 건너뛰겠습니다.
6.3.2 GNU 정규표현식 컴파일
----------------------------
GNU regex 에서는 정규표현식을 검색하고 매칭하는 것을 둘다 할 수 있습니다.
GNU regex 에서도 POSIX 나 BSD regex 처럼, 먼저 정규표현식을 컴파일하여, 패턴
버퍼에 마련해 두어야 합니다. 이전과 마찬가지로 패턴버퍼는 어떤 문법으로 컴파
일되느냐에 따라 매칭이나 검색의 결과가 달라지게 마련입니다. 이러한 문법을 지
정하는 변수는 re_syntax_options 입니다. 따라서 컴파일을 하기전에 정확한 문법
을 세팅해 두는 것이 중요합니다.
GNU regex 에서 패턴을 컴파일하는 것은, re_compile_pattern 입니다.
re_compile_pattern 은 패턴버퍼를 인자로 취하는 데, 패턴 버퍼의 다음의 필드
는 초기화를 시켜주어야 합니다.
translate initialization
translate
매칭이나 검색이전에 적용되는 변환테이블을 사용한다면 그 변환테이블
에 대한 포인터로 초기화 시키줍니다. 변환테이블이 없다면 NULL로 초
기화 시켜주면 됩니다. translate 는 GNU 패턴버퍼에서 char * 형 필드
임을 상기하세요.
변환 테이블에 대한 이야기는 뒷쪽에서 설명하겠습니다.
fastmap
fastmap (re_search 로 빠른 검색에 사용됨) 을 사용하려면 그 포인터를
지정하면 되며, 필요없다면 NULL로 지정하면 됩니다. 이 또한 char * 필
드 입니다.
buffer
allocated
re_compile_pattern 으로 컴파일된 패턴에 필요한 메모리를 할당하고
자 할 경우에는 둘다 0이나 NULL로 초기화 하면 됩니다. (buffer 는
unsigned char *, allocated 는 unsignedlong 형입니다. 0이나 NULL이
나 결국에는 0입니다.) 여러분들이 이미 할당한 메모리 블록을 Regex
에 사용하려면, buffer 는 그것의 주소로, allocated 는 블록의 바이트
크기로 설정하면 됩니다.
re_compile_pattern 은 컴파일된 패턴에 필요하다면 메모리를 확장하기
위해서 realloc 를 사용합니다.
'regex' 는 정규표현식 문자열의 주소이고, 'regex_size' 는 그것의 길이입니다.
pattern_buffer 는 패턴버퍼의 주소입니다.
re_compile_pattern 이 성공적으로 해당 정규표현식을 컴파일하였다면 0(NULL)을
리턴하고, *pattern_buffer 를 컴파일된 패턴으로 설정을 합니다. 아울러 아래의
패턴버퍼 내의 필드를 세팅합니다.
buffer 컴파일된 패턴
used buffer가 가르키는 곳에서 사용중인 바이트
syntax re_syntax_options 의 현재값
re_nsub 'regex' 에서 보조표현식의 갯수
fastmap_accurate
re_compile_pattern 이 'regex' 를 컴파일 할 수 없다면, '6.2.2 POSIX 정규표현식'
에서 설명한 에러 문자열을 돌려줍니다.
6.3.3 GNU 매칭
---------------
GNU 매칭은 문자열속에서 가능한한 시작위치에서 명시된데로 매칭을 시킵니다.
한번 패턴을 패턴버퍼로 컴파일을 했다면, 문자열에서 패턴을 매칭 시킬수 있습니
다.
int re_match (struct re_pattern_buffer *pattern_buffer,
const char *string, const int size,
const int start, struct re_registers *regs)
pattern_buffer 은 컴파일된 패턴버퍼의 주소이고, string 은 매칭을 하고자 하는
문자열입니다. 이 문자열에는 NULL 이나 newline 을 포함할 수 있습니다. size 는
그 문자열의 길이이며, start 는 매칭하기를 원하는 문자열속의 인덱스(문자열 첫
문자의 인덱스는 0)입니다.
re_match 는 pattern_buffer 의 syntax 필드의 문법에 따라, 문자열 string 을,
pattern_buffer의 정규 표현식과 매칭을 시키는 역할을 합니다. 문자열과 매칭할
수 없다면 -1을 리턴하고, 내부적인 에러일 경우에는 -2를, 성공적일 경우에는
문자열과 매칭된 횟수를 돌려줍니다.
예를 들면, pattern_buffer 이 'a*'를 컴파일한 패턴버퍼라고 하고, string 이
'aaaaab'이며, 따라서 size는 6이 되고, start 는 2라고 가정한다면, re_match
는 3을 리턴합니다. 'a*' 는 문자열에서 마지막 세개의 'a'를 매칭시킬 것입니다.
start 가 0이라고 한다면, re_match 는 5를 리턴합니다. start 가 5나 6일 경우
에는 0을 반환합니다.
start 가 0에서 size 사이가 아니라면, re_match 는 -1을 반환합니다.
6.3.4 GNU 검색
---------------
검색하는 데 사용되는 함수는 re_search 입니다.
re_search 를 사용하기 전에 정규표현식을 컴파일 하셔야 겠죠?
re_search 의 정의는 다음과 같습니다.
int re_search (struct re_pattern_buffer *pattern_buffer,
const char *string, const int size,
const int start, const int range,
struct re_registers *regs)
이 인자들은 re_match 와 유사합니다. 여기서 start 와 range 는 re_match 의
start 를 대응합니다.
range 가 양수이면, re_search 는 인덱스 start 에서 최초의 매칭을 시작하며
실패할 경우 start+1 에서 검색을 하며 계속 하나씩 나아가서 start+range 까지
수행합니다. range 가 음수라면, 인덱스 start 에서 첫 매칭을 수행하며, 이후에
-1씩 위치를 반대로 옮겨서 수행합니다.
start 가 0에서 size 사이가 아니라면, re_search 는 -1을 돌려줍니다. range 가
양수일 경우에는 re_search 는, 필요하다면 range 를 조절해서 start+range-1 이
0에서 size 사이가 되도록 하여 검색이 문자열 바깥으로 나가지 못하도록 합니다.
유사하게, range 가 음수라면, re_search 는 범위를 start+range+1 이 0에서 size
사이가 되도록 필요할 경우 조절하게 됩니다.
패턴버퍼의 fastmap 필드가 NULL 이라면, re_search 는 연속적인 위치로 매칭을 시
작하며, NULL 이 아니라면, fastmap 을 사용하여 좀 더 효율적으로 검색을 수행합
니다.
매칭이 한번도 되지 않는다면, re_search 는 -1을 반환하고, 매칭이 된다면 매칭이
시작된 위치의 인덱스를 돌려주며, 내부에러일 경우에는 -2를 돌려줍니다.
6.3.5 분리된 데이터로 매칭과 검색하기
--------------------------------------
re_match_2 와 re_search_2 를 사용하면, 두개의 문자열로 나누어진 데이터를 매칭
하거나 검색할 수 있습니다.
int re_match_2 (struct re_pattern_buffer *buffer,
const char *string1, const int size1,
const char *string2, const int size2,
const int start,
struct re_registers *regs,
const int stop)
이 함수는 re_match 와, 두개 데이터의 문자열과 크기를 넘겨주고, 이후의 매칭을
원하지 않을 경우의 인덱스 stop 을 제외하면 유사합니다.
re_match 처럼, re_match_2 가 성공적으로 수행되었다면, 문자열 string 에서 매
칭된 횟수를 돌려줍니다. re_match 는 string1 과 string2 를, start 와 stop 인
자를 설정하여 regs 를 사용할 때 에는 연속된 것으로 취급합니다.
int re_search_2 (struct re_pattern_buffer *buffer,
const char *string1, const int size1,
const char *string2, const int size2,
const int start,
struct re_registers *regs,
const int stop)
이것은 re_search 함수와 유사합니다.
6.3.6 fastmap 으로 검색하기
----------------------------
몇 십만바이트 이상 되는 문자열에서 검색을 하려면 fastmap 을 사용해야 합니다.
순차적으로 연속적인 위치에서 검색을 한다면 아마도 상당한 시간이 걸릴 것입니다
. fastmap 은 내부적인 알고리즘을 유지하면서 최적의 검색을 수행합니다.
문자열 검색 시 효율을 높이기 위한 알고리즘은 많이 우리들에게 알려져 있습니다.
그들의 많은 부분들은 strstr과 같이 순차적으로 검색하는 것이 아니라 검색의 효
율을 높이기 위해서 내부의 테이블을 갖추고 현재 위치의 문자가 검색의 시작점이
될 수 있는지를 검사하며 최대한의 포인터를 건너뛰도록 설계된 경우가 있습니다.
fastmap 을 이러한 역할을 하는 테이블에 대한 포인터입니다.
즉, 여러분들의 문자셋(아스키문자 등)으로 인덱스된 하나의 배열입니다. 아스키
encoding 하에서는, 따라서, fastmap 은 256 개의 원소를 가집니다. 주어진 패턴
버퍼에 있어서 검색시 fastmap 을 사용하려고 할 때에는, 먼저 배열을 할당하고
배열의 주소를 패턴버퍼의 fastmap 에 지정해야 합니다. fastmap 은 일반적으로 사
용자가 직접 컴파일하거나 또는 re_search가 대신 할 수도 있습니다.fastmap 이
어떤 테이블을 가르키고 있다면, re_search 는, 컴파일된 패턴버퍼를 사용한 검색을
하기 이전에, 먼저 fastmap 을 자동적으로 컴파일합니다.
직접 수동으로 하려면 다음과 같이 사용하면 됩니다.
int re_compile_fastmap (struct re_pattern_buffer *pattern_buffer)
pattern_buffer 은 패턴버퍼의 주소입니다. 어떠한 문자 c 가 매칭에 있어서 시작
점이 될 수 있다면, re_compile_fastmap 은 'pattern_buffer->fastmap[c]'를 0이 아
닌 수로 지정을 합니다.
이 함수가 fastmap 을 컴파일 할 수 있다면 0을 리턴하고,내부에러일 경우에는 -2
를 리턴합니다. 예를 든다면, 패턴버퍼 pattern_buffer 가 'a|b' 를 컴파일한 패턴
을 보유하고 있다면, re_compile_fastmap 은 fastmap['a'] 와 fastmap['b'] 를 세트
한다는 것입니다. 'a' 와 'b' 는 매칭의 시작점이 될 수 있으니까요..
re_search 는 문자열의 각 원소들 중에서 fastmap 에 있는 것 중의 하나가 나올때
까지 차례로 비교합니다. 그리고 나서 그 문자에서부터 매칭을 시도합니다. 매칭이
실패할 경우에는 이러한 처리를 반복합니다. 따라서 이렇게 fastmap 을 사용할 경
우, re_search 는 매칭의 시작점이 될 수 없는 문자열의 위치에서 쓸데 없이 매칭
하려고 하는 시도를 줄임으로써 시간을 절약할 수 있는 것입니다.
fastmap 을 re_search 에서 사용하기를 원치 않을 경우에는 fastmap 필드에 NULL
(0)을 저장하면 됩니다. 물론 re_search 사용이전에 말이죠...
패턴버퍼의 fastmap 필드를 한번 초기화 했다면 다시 fastmap 을 컴파일 할 필요는
없습니다. re_search 는 fastmap 이 NULL 이면 컴파일을 하지 않으며, NULL 이 아
니라면 새로운 패턴에 새로운 fastmap 을 컴파일합니다.
6.3.7 GNU 변환 테이블
----------------------
패턴버퍼의 translate 필드를 변환 테이블로 설정하였다면, Regex 는 찾는 모든
문자열과 정규표현식에서 간단한 변환을 하기 위해 translate 로 지정된 변환
테이블을 사용합니다.
"변환테이블" 은 아스키와 같은 문자세트의 원소들로 인덱스된 배열입니다.
따라서 아스키코드에서는 변환테이블은 256개의 원소를 가집니다. 이 배열의 원소
들도 마찬가지로 여러분의 문자세트에 포함이 됩니다. Regex 함수가 문자 c를 만
났다면, 문자 c 대신 translate[c] 를 사용합니다.
for (i = 0; i < 256; i++)
case_fold[i] = i;
for (i = 'a'; i <= 'z'; i++) /* 소문자를 대문자로 변환 */
case_fold[i] = i - ('a' -'A');
pb.translate = case_fold;
이렇게 translate 에 변환 테이블의 주소를 지정하면 이후에변환테이블을 사용합
니다. 변환테이블을 사용하고 싶지 않다면 translate 에 NULL 을 넣어주시면 됩
니다. 만일, 패턴버퍼를 컴파일할 때나, fastmap 을 컴파일할 때, 패턴버퍼로 매칭
이나 검색을 수행할 때 이러한 테이블의 내용을 바꾼다면 이상한 결과를 얻을 것
입니다.
6.3.8 레지스터 사용하기
------------------------
사실 이 부분이 regex 에서 중요한 부분입니다.
지금까지는 regex 를 사용하여 어떤 문자열내에 해당 패턴(정규표현식)이 있느냐
없느냐만 따졌으나 이 레지스터를 사용하면 세부 매칭의 결과를 저장하게 됩니다.
즉, 문자열 인덱스 어디에서 어디까지 패턴과 매칭이 되었는지에 대한 정보를 확
보함으로써 나아가서는 문자열 치환 작업까지도 생각할 수 있습니다.
정규표현식에서 하나의 그룹은 전체적으로 정규표현식과 매칭되는 문자열의 하나
의 부분문자열과 매칭할 수 있습니다. 매칭작업을 수행할 때 각각의 그룹과 매칭
된 보조문자열의 시작과 끝이 기억됩니다.
이러한 검색이나 매칭시에는 GNU 매칭 및 검색 함수에 0이 아닌 'regs' 인자를 넘
겨줘야 합니다.
start 와 end 의 i번째 원소는 패턴에서의 i번째 그룹에 대한 정보를 기록합니다.
이 start 와 end 는 다양한 방법으로 할당되는 데, 이것은 패턴버퍼의
regs_allocated 필드에 의존합니다.
제일 간편하고 유용한 방법은 regex 의 매칭함수로 하여금 각각의 그룹에 대한 정
보를 기록할 공간을 충분히 할당하게 하는 것입니다. regs_allocated 가
REGS_UNALLOCATED 라면, 매칭함수는 1+re_nsub(패턴버퍼의 다른 멤버) 만큼을 할당
합니다. 여분의 원소는 -1 로 설정하고, regs_allocated 를 REGS_REALLOCATE 로
설정합니다. 이후에 다시 호출할 경우에, 필요하다면 매칭함수는 공간을 더 할당
할 수 있습니다.
re_compile_pattern 은 regs_allocated 를 REGS_UNALLOCATED 로 설정하기 때문에,
GNU 정규표현식 함수에서는 위와 같은 행동이 기본으로 되어 있습니다.
POSIX 에서는 조금 다릅니다. 호출자에 매칭함수가 채울, 고정길이의 배열을 넘겨
줘야 합니다. 따라서 regs_allocated 가 REGS_FIXED 라면, 매칭함수는 그 고정배
열을 간단하게 채웁니다.
아래의 예제는 re_registers 구조체에 기록되는 정보를 보여줍니다. ('(' 와 ')'
이 그룹오퍼레이터라고 하고, 문자열 string 에서 첫번째 문자의 인덱스를 0이라
하겠습니다.)
ㄱ. 정규표현식이 또다른 그룹을 포함하지 않는, i번째 그룹을 가지고 있다면, 함
수는 'regs->start[i]' 에 그룹과 매칭하는 보조문자열의 시작 인덱스를 저장
하고, 'regs->end[i]' 에는 보조문자열의 끝 인덱스를 저장합니다.
'regs->start[0]'과 'regs->end[0]' 에는 전체 패턴에 대한 정보가 들어갑니다.
예를 들면, 'ab' 에 대해 '((a)(b))' 를 매칭시킨다면, 다음의 결과를 얻을 것입
니다.
* 0 in `regs->start[0]' and 2 in `regs->end[0]'
* 0 in `regs->start[1]' and 2 in `regs->end[1]'
* 0 in `regs->start[2]' and 1 in `regs->end[2]'
* 1 in `regs->start[3]' and 2 in `regs->end[3]'
ㄴ. 그룹이 반복오퍼레이터 등을 사용하여 한번보다 더 많이 매칭된다면, 함수는
마지막으로 매칭된 그룹에 대한 정보를 저장합니다.
예를 들면, 'aa' 에 대해 '(a)*' 를 매칭시킨다면, 다음의 결과를 얻을 것입니다.
* 0 in `regs->start[0]' and 2 in `regs->end[0]'
* 1 in `regs->start[1]' and 2 in `regs->end[1]'
여기에서 그룹 1 은 '(a)' 이지만, 뒤의 '*' 오퍼레이터로 인해 'aa' 와는 1번
초과 매칭되므로, 마지막에 매칭되는 'a'에 대한 인덱스를 기록합니다.
ㄷ. i번째 그룹이, 어떤 성공적인 매칭에 관여하지 않는다면, 반복오퍼레이터는 0번
반복을 허용하고, 함수는 'regs->start[i]' 와 'regs->end[i]' 를 -1로 채웁니다.
예를 든다면, 'b' 에 대해 '(a)*b' 를 매칭하는 경우는 다음의 결과를 얻을 것
입니다.
* 0 in `regs->start[0]' and 1 in `regs->end[0]'
* -1 in `regs->start[1]' and -1 in `regs->end[1]'
여기에서 1번째 그룹인 '(a)' 는 매칭에 관여하지 않기 때문에 'regs->start[1]'
과 'regs->end[1]' 은 -1로 됩니다.
ㄹ. i번째 그룹이 길이가 0인 문자열을 매칭한다면, 함수는 regs->start[i] 와
regs->end[i] 를 "길이가 0인 문자열"의 인덱스로 설정합니다.
예를 든다면, 'b' 에 대해 '(a*)b' 를 매칭하는 경우는 다음의 결과를 얻을 것
입니다.
* 0 in `regs->start[0]' and 1 in `regs->end[0]'
* 0 in `regs->start[1]' and 0in `regs->end[1]'
여기에서 '(a*)b' 는 위의 '(a)*b' 와는 다릅니다. 1번째 그룹인 '(a*)' 는
'b' 의 앞부분의 빈 문자열과 매칭이 되므로 regs->start[1]과 regs->end[1] 은
둘다 '0' 이 됩니다.
ㅁ. i번째 그룹이 j번째 그룹을 포함하고, j번째 그룹은 i번째 그룹에만 포함되는
경우, 함수는 i번째 그룹의 매칭을 기록하고, regs->start[j] 와 regs->end[j]
에는 j번째 그룹과 마지막으로 매칭된 것에 대한 정보를 기록합니다.
조금 햇갈리기 쉬운 경우인데, 예를 들어보지요..
'abb' 에 대해 '((a*)b)*'를 매칭시키는 경우를 봅시다.
regs->start[0] 과 regs->end[0] 은 당연히 전체 문자열의 정보를 가지므로
0(첫번째 문자 인덱스), 3(시작인덱스 + 길이로 보는 것이 좋을 듯..) 이
됩니다.
1: ((a*)b)* abb : 1번째 그룹 첫 매칭 (0, 2)
^^^^^^^ ^^
((a*)b)* abb : 2번째 그룹 첫 매칭 (0, 1)
^^^ ^
처음의 매칭에서, 1번째 그룹은 'ab' 와 매칭되고, 2번째 그룹은 'a'와 매칭
됩니다.
^^^^^^^
2: ((a*)b)* abb : 1번째 그룹 둘째 매칭 (2, 3)
^ ^
^^^
((a*)b)* a b b : 2번째 그룹 둘째 매칭 (2, 2)
^ ^ (빈문자열)
반복 오퍼레이터의 영향으로, 두번째 매칭에서, 1번째 그룹은 'b'와 매칭되고,
2번째 그룹은 마지막 'b' 의 바로 앞의 빈문자열과 매칭됩니다.
따라서, 위의 'ㄴ' 규칙 (그룹이 반복오퍼레이터 등을 사용하여 한번보다 더 많
이 매칭된다면, 함수는 마지막으로 매칭된 그룹에 대한 정보를 저장한다.) 에 따
라, regs->start[1], regs->end[1] 에는 2, 3 이 각각 기록되며, 그룹1은 그룹
2를 포함하고 그룹2는 그룹1에만 포함되기 때문에, 마지막으로 매칭된 2번째 그
룹의 기록값인 2, 2가 각각 regs->start[2] 와 regs->end[2] 에 각각 기록됩니
다.
결과를 정리하면,
* 0 in `regs->start[0]' and 3 in `regs->end[0]'
* 2 in `regs->start[1]' and 3 in `regs->end[1]'
* 2 in `regs->start[2]' and 2 in `regs->end[2]'
'abb' 에 대해 '((a)*b)*' 를 매칭한다면, 그룹2(괄호안쪽의 '(a)') 는 마지막
매칭에 관여하지 않으므로 다음과 같은 결과를 얻습니다.
* 0 in `regs->start[0]' and 3 in `regs->end[0]'
* 2 in `regs->start[1]' and 3 in `regs->end[1]'
* 0 in `regs->start[2]' and 1 in `regs->end[2]'
조금 햇갈리는 분도 계실 것이고, 재미를 느끼는 분도 계실 것입니다. :)
ㅂ. 위와 같을 경우에, 매칭이 되지 않을 경우, 함수는 regs->start[i],
regs->end[i]와 regs->start[j], regs->end[j] 를 모두 -1 로 설정합니다.
예를 들면, 'c' 에 대해 '((a)*b)*c' 를 매칭한다면, 다음의 결과를 얻을 것
입니다.
* 0 in `regs->start[0]' and 1 in `regs->end[0]'
* -1 in `regs->start[1]' and -1 in `regs->end[1]'
* -1 in `regs->start[2]' and -1 in `regs->end[2]'
레지스터에 대한 이야기는 이걸로 마치겠습니다.
6.3.9 GNU 패턴버퍼를 free 하기
-------------------------------
패턴버퍼에서 할당된 필드를 free 하기 위해서는 이전에 설명드린 POSIX 함수
(6.2.6 의 regfree) 를 사용할 수 있습니다. POSIX 에서 사용하는 regex_t 는
GNU 의 re_pattern_buffer 와 동일합니다. 패턴버퍼를 free 한 이후에는, 어떤
검색과 매칭작업을 다시 수행하려면, 정규표현식을 패턴버퍼로 다시 컴파일하여
야 합니다.