펄 내부 API 입문
펄 내부 API 입문 (perlguts)
Perl API를 어떻게 사용하는지, 그리고 Perl 코어가 기본적으로 어떻게 동작하는지에 대한 정보를 담은 문서예요. 완전하지 않고 아마 많은 오류를 포함하고 있을 수 있어요. 질문이나 의견은 Perl 5 Porters <[email protected]>로 보내주세요.
변수 (Variables)
데이터 타입 (Datatypes)
Perl에는 Perl의 세 가지 주요 데이터 타입을 다루는 typedef가 있어요:
SV Scalar Value
AV Array Value
HV Hash Value
각 typedef에는 그 데이터 타입을 조작하는 특정 루틴들이 있어요.
"IV"란 무엇인가?
Perl은 특별한 typedef IV를 사용해요. 이는 단순한 부호 있는 정수 타입으로, 포인터를 담을 만큼 큰 것이 보장되지만 Perl이 어떻게 Configure되었는지에 따라 더 클 수도 있어요. 또한, 단순히 unsigned IV인 UV도 있어요.
Perl은 또한 (최소한) 주어진 크기의 정수를 담을 변수를 선언하기 위해 여러 특별한 typedef를 사용해요. I8, I16, I32, I64를 사용해 이름의 숫자만큼의 비트를 가진 부호 있는 정수 변수를 선언해요. 이것들은 모두 주어진 비트 수에 가장 가깝지만 그보다 작지 않은 네이티브 C 타입으로 평가돼요. 예를 들어, 많은 플랫폼에서 short는 16비트이고, 그렇다면 I16은 short로 평가돼요. 하지만 short가 정확히 16비트가 아닌 플랫폼에서는 Perl이 16비트 이상을 담는 가장 작은 타입을 사용해요.
U8, U16, U32, U64는 대응하는 부호 없는 정수 타입을 선언하기 위한 것이에요.
플랫폼이 64비트 정수를 지원하지 않는다면 I64와 U64 모두 정의되지 않아요. 가장 큰 실용적 크기를 선언하려면 IV와 UV를 사용하고, 절대 최대 unsigned를 위해서는 perlapi의 WIDEST_UTYPE을 사용하되 모든 상황에서 쓸 수는 없다는 걸 명심하세요.
숫자 상수는 C99 매크로 INT16_C, perlapi의 UINTMAX_C 등을 사용해 지정할 수 있어요.
SV 다루기 (Working with SVs)
SV는 한 명령으로 생성·로드할 수 있어요. 로드할 수 있는 값의 타입은 다섯 가지예요: 정수 값(IV), 부호 없는 정수 값(UV), double(NV), 문자열(PV), 그리고 다른 스칼라(SV). ("PV"는 "Pointer Value"를 뜻해요. 문자열만 가리킨다고 해서 잘못 이름지어졌다고 생각할 수 있지만, 다른 것을 가리킬 수도 있어요. 예를 들어 UV 배열을 가리킬 수 있어요. 하지만 비-문자열로 쓰려면 주의가 필요해요. 내부의 많은 부분이 PV는 문자열용이라고 가정하기 때문이에요. 예를 들어 종종 뒤에 NUL이 자동으로 붙어요. 비-문자열 용도는 이 문단에만 문서화되어 있어요.)
SV를 생성·초기화하는 루틴은 여러 개 있으며, 새 것이 가끔 추가돼요. 이들은 모두 perlapi의 "SV Handling"에 문서화되어 있고, 각 이름은 newSV로 시작해요. 기본적인 것들은:
SV* newSVbool(boolean_value);
SV* newSViv(IV);
SV* newSVuv(UV);
SV* newSVnv(double);
SV* newSVpv(const char*, STRLEN);
SV* newSVpvf_nocontext(const char * const pat, ...)
SV* newSVpvn(const char*, STRLEN);
SV* newSVpvs("literal string")
SV* newSVpvz(STRLEN)
SV* newSVsv(SV*);
STRLEN은 Perl이 다룰 수 있는 어떤 문자열의 크기도 나타낼 수 있을 만큼 큰 정수 타입(Size_t, 보통 config.h에서 size_t로 정의)이에요.
"literal string"은 큰따옴표로 감싼 문자열이고, 생성된 SV는 문자열 타입(PV)이며 리터럴 문자열 파라미터의 복사본을 담도록 초기화돼요. newSVpvs("")는 빈(길이 0) 문자열을 담은 SV를 생성하고 그 포인터를 반환해요.
newSVpvz는 newSVpvs("")와 비슷하지만, 메모리를 재할당하지 않고 파라미터가 지정한 바이트 수를 향후 성장을 위해 예약해요. 문자열이 결국 어떻게 될지 모르지만 최소 크기를 꽤 잘 예측할 수 있는 상황에서 유용해요.
더 복잡한 초기화가 필요한 SV의 경우 newSV(len)으로 빈 SV를 만들 수 있어요. len이 0이면 NULL 타입의 빈 SV가 반환되고, 그렇지 않으면 SvPVX로 접근 가능한 len + 1(NUL용) 바이트의 저장 공간이 할당된 PV 타입 SV가 반환돼요. 두 경우 모두 SV는 undef 값을 가져요.
SV *sv = newSV(0); /* no storage allocated */
SV *sv = newSV(10); /* 10 (+1) bytes of uninitialised storage
* allocated */
이미 존재하는 SV의 값을 바꾸려면 기본 루틴은 다음과 같아요 (더 많은 것은 perlapi의 "SV Handling"에 있어요):
void sv_setbool(SV*, bool);
void sv_setiv(SV*, IV);
void sv_setuv(SV*, UV);
void sv_setnv(SV*, double);
void sv_setpv(SV*, const char*);
void sv_setpvn(SV*, const char*, STRLEN)
void sv_setpvf(SV*, const char*, ...);
void sv_vsetpvfn(SV*, const char*, STRLEN, va_list *,
SV **, Size_t, bool *);
void sv_setsv(SV*, SV*);
sv_setpvn, newSVpvn, newSVpv로 문자열 길이를 직접 지정할 수도 있고, sv_setpv를 쓰거나 newSVpv의 두 번째 인자로 0을 지정해 Perl이 strlen으로 길이를 계산하게 할 수도 있어요. 단, Perl은 문자열이 NUL 문자로 끝나고, 그 밖에는 NUL을 포함하지 않는다고 가정하고 strlen으로 길이를 결정한다는 점에 주의하세요.
sv_setpvf의 인자는 sprintf처럼 처리되고, 그 포맷된 출력이 값이 돼요.
sv_vsetpvfn은 vsprintf와 유사하지만, 가변 인자 목록에 대한 포인터나 SV 배열의 주소·길이 중 하나를 지정할 수 있게 해 줘요. 마지막 인자는 boolean을 가리키며, 반환 시 그 boolean이 참이면 로케일 특정 정보가 문자열 포맷에 사용된 것이므로 문자열 내용을 신뢰할 수 없어요(perlsec 참조). 이 정보가 중요하지 않으면 그 포인터는 NULL일 수 있어요. 이 함수는 포맷의 길이를 지정해야 한다는 점에 유의하세요.
sv_set*() 함수들은 "매직(magic)"이 있는 값에는 동작할 만큼 일반적이지 않아요. 이 문서 뒷부분의 "Magic Virtual Tables"를 참조하세요.
문자열을 담은 모든 SV는 NUL 문자로 끝나야 해요. 그렇지 않으면 NUL-종료 문자열을 기대하는 C 함수나 시스템 콜에 문자열을 넘기는 코드에서 코어 덤프와 손상의 위험이 있어요. Perl의 자체 함수는 이런 이유로 보통 뒤에 NUL을 붙여요. 그럼에도 SV에 저장된 문자열을 C 함수나 시스템 콜에 넘길 때는 매우 조심해야 해요.
SV가 가리키는 실제 값에 접근하려면, Perl API는 스칼라 타입을 IV, UV, double, 문자열로 강제 변환하는 여러 매크로를 노출해요:
SvIV(SV*)(IV) 및SvUV(SV*)(UV)SvNV(SV*)(double)- 문자열은 조금 복잡해요:
- 바이트 문자열:
SvPVbyte(SV*, STRLEN len)또는SvPVbyte_nolen(SV*). Perl 문자열이"\xff\xff"라면 2바이트char*를 반환해요. 바이트를 나타내는 Perl 문자열에 적합해요. - UTF-8 문자열:
SvPVutf8(SV*, STRLEN len)또는SvPVutf8_nolen(SV*). Perl 문자열이"\xff\xff"라면 4바이트char*를 반환해요. 문자를 나타내는 Perl 문자열에 적합해요. - CAVEAT: 그
char*는 Perl 내부 UTF-8 변형으로 인코딩되므로, SV가 비유니코드 코드 포인트(예: 0x110000)를 포함하면 결과가 유효 UTF-8을 넘어선 확장을 포함할 수 있어요. 이 매크로 반환값의 UTF-8 유효성을 검사하는 방법은 perlapi의 "is_strict_utf8_string"을 참조하세요. SvPV(SV*, STRLEN len)또는SvPV_nolen(SV*)로 SV의 원시 내부 버퍼를 가져올 수도 있어요. 까다롭지만, Perl 문자열이"\xff\xff"라면 SV의 내부 인코딩에 따라 2바이트 또는 4바이트char*를 얻을 수 있어요. 게다가 4바이트 문자열이라면 UTF-8로 저장된 Perl"\xff\xff"에서 왔거나 원시 옥텟으로 저장된 Perl"\xc3\xbf\xc3\xbf"에서 왔을 수 있어요. 구분하려면 반드시 SV의 UTF8 비트(SvUTF8 참조)를 조회해 원본 Perl 문자열이 2문자인지(SvUTF8켜짐) 4문자인지(SvUTF8꺼짐) 알아야 해요.- 중요: SV의 UTF8 비트를 조회하지 않고
SvPV,SvPV_nolen등을 쓰는 것은 비-ASCII 입력이 허용된다면 거의 확실히 버그예요. UTF8 비트가 켜져 있으면 SvPVutf8과 마찬가지로 UTF-8 유효성 CAVEAT가 적용돼요. (자세한 내용은 "Perl 문자열을 C 라이브러리에 어떻게 전달하나요?" 참조.)
- 바이트 문자열:
SvPVbyte, SvPVutf8, SvPV에서 반환된 char*의 길이는 변수 len에 들어가요 (이들은 매크로이므로 &len을 쓰지 않아요). 데이터 길이에 관심이 없으면 SvPVbyte_nolen, SvPVutf8_nolen, SvPV_nolen을 쓰면 돼요. 이 경우 전역 변수 PL_na를 SvPVbyte/SvPVutf8/SvPV에 줄 수도 있어요. 하지만 스레드 Perl에서 PL_na는 스레드-로컬 저장소에 접근해야 하므로 꽤 비효율적일 수 있어요. 어쨌든 Perl은 NUL을 포함하고 NUL로 끝나지 않을 수도 있는 임의 데이터 문자열을 허용한다는 걸 기억하세요.
또한 C는 foo(SvPVbyte(s, len), len);을 안전하게 허용하지 않는다는 것을 기억하세요. 여러분의 컴파일러에서는 동작할 수 있지만 모든 사람에게는 안 돼요. 이런 종류의 문장을 별도의 할당으로 나누세요:
SV *s;
STRLEN len;
char *ptr;
ptr = SvPVbyte(s, len);
foo(ptr, len);
스칼라 값이 TRUE인지 알고 싶다면:
SvTRUE(SV*)
Perl이 문자열을 자동으로 늘려주긴 하지만, SV에 더 많은 메모리를 할당하도록 강제해야 한다면 매크로를 사용할 수 있어요:
SvGROW(SV*, STRLEN newlen)
이 매크로는 더 많은 메모리가 필요한지 결정해요. 필요하다면 sv_grow 함수를 호출해요. SvGROW는 SV의 할당 메모리를 늘릴 수만 있고 줄일 수는 없으며, 뒤의 NUL 바이트 공간을 자동으로 추가하지 않는다는 점에 유의하세요 (Perl의 자체 문자열 함수는 보통 SvGROW(sv, len + 1)을 해요).
기존 SV의 버퍼에 쓰고 그 값을 문자열로 설정하려면 SvPVbyte_force()나 그 변형 중 하나로 SV를 PV로 강제하세요. 이는 SV에 있는 여러 종류의 비-문자열성을 제거하면서 PV 안의 내용은 보존해요. 예를 들어 API 함수의 데이터를 추가 복사 없이 버퍼에 덧붙일 때 쓸 수 있어요:
(void)SvPVbyte_force(sv, len);
s = SvGROW(sv, len + needlen + 1);
/* something that modifies up to needlen bytes at s+len, but
modifies newlen bytes
eg. newlen = read(fd, s + len, needlen);
ignoring errors for these examples
*/
s[len + newlen] = '\0';
SvCUR_set(sv, len + newlen);
SvUTF8_off(sv);
SvSETMAGIC(sv);
이미 데이터를 메모리에 가지고 있거나 코드를 단순하게 유지하려면 sv_catpvn() 같은 sv_cat*() 변형 중 하나를 쓰면 돼요. 문자열의 아무 곳에든 삽입하려면 sv_insert()나 sv_insert_flags()를 쓰면 돼요.
SV의 기존 내용이 필요 없다면 다음과 같이 복사를 피할 수 있어요:
SvPVCLEAR(sv);
s = SvGROW(sv, needlen + 1);
/* something that modifies up to needlen bytes at s, but modifies
newlen bytes
eg. newlen = read(fd, s, needlen);
*/
s[newlen] = '\0';
SvCUR_set(sv, newlen);
SvPOK_only(sv); /* also clears SVf_UTF8 */
SvSETMAGIC(sv);
역시 이미 데이터를 메모리에 가지고 있거나 위의 복잡함을 피하려면 sv_setpvn()을 쓰면 돼요.
Newx()로 할당한 버퍼가 있고 그것을 SV의 값으로 설정하려면 sv_usepvn_flags()를 쓰면 돼요. Perl이 뒤의 NUL에 맞게 버퍼를 재할당하지 않도록 하려면 몇 가지 요구사항이 있어요:
Newx(buf, somesize+1, char);
/* ... fill in buf ... */
buf[somesize] = '\0';
sv_usepvn_flags(sv, buf, somesize, SV_SMAGIC | SV_HAS_TRAILING_NUL);
/* buf now belongs to perl, don't release it */
SV를 가지고 Perl이 어떤 종류의 데이터가 저장되어 있다고 생각하는지 알고 싶다면 다음 매크로로 SV 타입을 확인하세요:
SvIOK(SV*)
SvNOK(SV*)
SvPOK(SV*)
SV의 숫자 값을 검색하면 그 SV가 문자열로 시작했더라도 IOK나 NOK가 설정될 수 있다는 점을 알아두세요. Perl 5.36.0 이전에는 정수의 문자열 값을 검색하면 POK가 설정될 수 있었지만 이제는 그럴 수 없어요. 5.36.0부터 이를 사용해 SV의 원래 표현을 구분할 수 있고, 직렬화 도구의 삶을 더 단순하게 만들려는 의도예요:
/* references handled elsewhere */
if (SvIsBOOL(sv)) {
/* originally boolean */
...
}
else if (SvPOK(sv)) {
/* originally a string */
...
}
else if (SvNIOK(sv)) {
/* originally numeric */
...
}
else {
/* something special or undef */
}
SV에 저장된 문자열의 현재 길이를 얻고 설정하려면 다음 매크로를 사용하세요:
SvCUR(SV*)
SvCUR_set(SV*, I32 val)
SV에 저장된 문자열의 끝에 대한 포인터도 다음 매크로로 얻을 수 있어요:
SvEND(SV*)
단, 이 마지막 세 매크로는 SvPOK()가 참일 때만 유효해요.
SV*에 저장된 문자열 끝에 무언가를 덧붙이려면 다음 함수들을 사용할 수 있어요:
void sv_catpv(SV*, const char*);
void sv_catpvn(SV*, const char*, STRLEN);
void sv_catpvf(SV*, const char*, ...);
void sv_vcatpvfn(SV*, const char*, STRLEN, va_list *, SV **,
I32, bool);
void sv_catsv(SV*, SV*);
첫 함수는 strlen으로 덧붙일 문자열의 길이를 계산해요. 두 번째에서는 문자열 길이를 직접 지정해요. 세 번째 함수는 인자를 sprintf처럼 처리하고 포맷된 출력을 덧붙여요. 네 번째 함수는 vsprintf처럼 동작해요. va_list 인자 대신 SV 배열의 주소·길이를 지정할 수 있어요. 다섯 번째 함수는 첫 SV에 저장된 문자열을 두 번째 SV에 저장된 문자열로 확장해요. 또한 두 번째 SV를 문자열로 해석하도록 강제해요.
sv_cat*() 함수들은 "매직"이 있는 값에 동작할 만큼 일반적이지 않아요. "Magic Virtual Tables" 참조.
스칼라 변수의 이름을 안다면 다음으로 SV 포인터를 얻을 수 있어요:
SV* get_sv("package::varname", 0);
변수가 존재하지 않으면 NULL을 반환해요.
이 변수(또는 다른 SV)가 실제로 defined인지 알고 싶다면:
SvOK(SV*)
스칼라 undef 값은 PL_sv_undef라는 SV 인스턴스에 저장돼요. SV*가 필요할 때마다 그 주소를 사용할 수 있어요. 임의의 sv를 &PL_sv_undef와 비교하려 하면 안 돼요. 예를 들어 Perl 코드를 인터페이스할 때 다음과 같은 경우 올바르게 동작해요:
foo(undef);
하지만 이렇게 호출하면 동작하지 않아요:
$x = undef;
foo($x);
그러니 반복해서 말하지만, sv가 defined인지 확인하려면 항상 SvOK()를 사용하세요.
또한 AV나 HV에서 값으로 &PL_sv_undef를 쓸 때는 조심해야 해요 ("AVs, HVs and undefined values" 참조).
또한 PL_sv_yes와 PL_sv_no라는 두 값이 있는데, 각각 boolean TRUE와 FALSE 값을 담고 있어요. PL_sv_undef처럼 SV*가 필요할 때마다 그 주소를 사용할 수 있어요.
(SV *) 0이 &PL_sv_undef와 같다고 속지 마세요. 이 코드를 보세요:
SV* sv = (SV*) 0;
if (I-am-to-return-a-real-value) {
sv = sv_2mortal(newSViv(42));
}
sv_setsv(ST(0), sv);
이 코드는 실제 값을 반환해야 한다면 값 42를 담은 새 SV를, 그렇지 않으면 undef를 반환하려 해요. 대신 NULL 포인터를 반환했는데, 이는 어딘가에서 세그멘테이션 위반, 버스 에러, 또는 이상한 결과를 일으킬 거예요. 첫 줄의 0을 &PL_sv_undef로 바꾸면 모든 것이 잘 될 거예요.
생성한 SV를 해제하려면 SvREFCNT_dec(SV*)를 호출하세요. 보통 이 호출은 필요 없어요 ("Reference Counts and Mortality" 참조).
오프셋 (Offsets)
Perl은 sv_chop 함수로 문자열 앞에서 문자를 효율적으로 제거해요. SV와 PV 내부 어딘가를 가리키는 포인터를 주면, 그 포인터 앞의 모든 것을 버려요. 효율성은 작은 꼼수에서 나와요: 실제로 문자를 제거하는 대신, sv_chop은 OOK(offset OK) 플래그를 설정해 다른 함수들에게 오프셋 꼼수가 적용 중임을 알리고, 잘라낸 바이트 수만큼 PV 포인터(SvPVX라고 함)를 앞으로 옮기고 SvCUR과 SvLEN을 그에 맞게 조정해요. (옛 PV 포인터와 새 PV 포인터 사이의 공간 일부는 잘라낸 바이트 수를 저장하는 데 사용돼요.)
따라서 이 시점에서 우리가 할당한 버퍼의 시작은 메모리상 SvPVX(sv) - SvIV(sv)에 있고, PV 포인터는 이 할당된 저장 공간의 중간을 가리켜요.
이는 예로 가장 잘 설명돼요. 보통 copy-on-write가 치환 연산자가 이 꼼수를 쓰는 것을 막지만, copy-on-write가 불가능한 문자열을 만들 수 있다면 그 작동을 볼 수 있어요. 현재 구현에서 문자열 버퍼의 마지막 바이트는 copy-on-write 참조 카운트로 사용돼요. 버퍼가 충분히 크지 않으면 copy-on-write는 건너뛰어져요. 먼저 빈 문자열을 보세요:
% ./perl -Ilib -MDevel::Peek -le '$a=""; $a .= ""; Dump $a'
SV = PV(0x7ffb7c008a70) at 0x7ffb7c030390
REFCNT = 1
FLAGS = (POK,pPOK)
PV = 0x7ffb7bc05b50 ""\0
CUR = 0
LEN = 10
여기서 LEN이 10인 것에 주목하세요. (플랫폼에 따라 다를 수 있어요.) 문자열 길이를 10보다 하나 적게 늘리고 치환을 해 봐요:
% ./perl -Ilib -MDevel::Peek -le '$a=""; $a.="123456789"; $a=~s/.//; \
Dump($a)'
SV = PV(0x7ffa04008a70) at 0x7ffa04030390
REFCNT = 1
FLAGS = (POK,OOK,pPOK)
OFFSET = 1
PV = 0x7ffa03c05b61 ( "\1" . ) "23456789"\0
CUR = 8
LEN = 9
여기서 잘라낸 바이트 수(1)가 OFFSET으로 다음에 표시돼요. "실제" 시작과 "가짜" 시작 사이의 문자열 부분은 괄호로 표시되고, SvCUR과 SvLEN의 값은 실제가 아닌 가짜 시작을 반영해요. (문자열 버퍼의 첫 문자가 여기서 "1"이 아니라 "\1"로 바뀐 것은 현재 구현이 오프셋 수를 문자열 버퍼에 저장하기 때문이에요. 이는 변경될 수 있어요.)
오프셋 꼼수와 비슷한 것이 AV에서도 수행되어 배열 앞부분의 효율적인 shift와 splice를 가능하게 해요. AvARRAY는 Perl에서 보이는 배열의 첫 요소를 가리키는 반면, AvALLOC는 C 배열의 실제 시작을 가리켜요. 이들은 보통 같지만, shift 연산은 AvARRAY를 1 늘리고 AvFILL과 AvMAX를 줄여 수행될 수 있어요. 다시, C 배열의 실제 시작 위치는 배열을 해제할 때만 문제가 돼요. av.c의 av_shift를 보세요.
SV에 실제로 무엇이 저장되어 있나?
스칼라 타입을 결정하는 일반적인 방법은 Sv*OK 매크로를 쓰는 것임을 기억하세요. 스칼라는 숫자이자 문자열일 수 있으므로, 보통 이 매크로들은 항상 TRUE를 반환하고 Sv*V 매크로는 문자열→정수/double 또는 정수/double→문자열의 적절한 변환을 수행해요.
SV에 정수, double, 문자열 포인터가 있는지 정말 알아야 한다면 대신 다음 세 매크로를 사용할 수 있어요:
SvIOKp(SV*)
SvNOKp(SV*)
SvPOKp(SV*)
이것들은 SV에 정수, double, 문자열 포인터가 정말 저장되어 있는지 알려줘요. "p"는 private를 뜻해요.
private와 public 플래그가 달라질 수 있는 방법은 여러 가지예요. 예를 들어 perl 5.16 이전에서는 tied SV가 IV 슬롯에 유효한 기반 값을 가질 수 있어서(SvIOKp는 참) 데이터를 직접 접근하는 대신 FETCH 루틴으로 접근해야 하므로 SvIOK는 거짓이에요. (perl 5.18 이후에서는 tied 스칼라가 untied 스칼라와 같은 방식으로 플래그를 사용해요.) 또 하나는 숫자 변환이 발생해 정밀도가 손실된 경우예요: 'lossy' 값에는 private 플래그만 설정돼요. 그래서 NV가 손실과 함께 IV로 변환되면 SvIOKp, SvNOKp, SvNOK가 설정되지만 SvIOK는 설정되지 않아요.
하지만 일반적으로는 Sv*V 매크로를 쓰는 게 가장 좋아요.
읽기 전용 값 (Read-Only Values)
Perl 5.16 이전에서 copy-on-write(다음 절)는 읽기 전용 스칼라와 플래그 비트를 공유했어요. 그래서 그 버전들에서 sv_setsv 등이 "Modification of a read-only value" 오류를 일으킬지 테스트하는 유일한 방법은:
SvREADONLY(sv) && !SvIsCOW(sv)
Perl 5.18 이후에서 SvREADONLY는 읽기 전용 변수에만 적용되고, 5.20에서는 copy-on-write 스칼라도 읽기 전용일 수 있으므로 위의 확인은 틀려요. 그냥 이렇게 하면 돼요:
SvREADONLY(sv)
이 확인을 자주 해야 한다면 자신만의 매크로를 이렇게 정의하세요:
#if PERL_VERSION >= 18
# define SvTRULYREADONLY(sv) SvREADONLY(sv)
#else
# define SvTRULYREADONLY(sv) (SvREADONLY(sv) && !SvIsCOW(sv))
#endif
또는 아래의 THINKFIRST 매크로를 읽어보는 게 더 좋아요.
Copy on Write
Perl은 스칼라에 copy-on-write(COW) 메커니즘을 구현해요. 문자열 복사는 요청 시 즉시 만들어지지 않고, 두 스칼라 중 하나가 바뀔 때까지 연기돼요. 대부분 투명하지만, 여러 SV가 공유하는 문자열 버퍼를 수정하지 않도록 조심해야 해요.
SV가 copy-on-write를 사용 중인지 SvIsCOW(sv)로 테스트할 수 있어요.
sv_force_normal(sv)나 SvPV_force_nolen(sv)를 호출해 SV가 자신의 문자열 버퍼 복사본을 만들도록 강제할 수 있어요.
SV가 문자열 버퍼를 버리게 하려면 sv_force_normal_flags(sv, SV_COW_DROP_PV)를 쓰거나 그냥 sv_setsv(sv, NULL)을 쓰세요.
이 모든 함수는 읽기 전용 스칼라에서 croak해요 (자세한 내용은 이전 절).
코드가 올바르게 동작하고 COW 버퍼를 수정하지 않는지 테스트하려면, mmap(2)을 지원하는 시스템(예: Unix, Linux, BSDs, macOS)에서 perl을 -Accflags=-DPERL_DEBUG_READONLY_COW로 구성하면 버퍼 위반이 크래시로 바뀌어요. 엄청나게 느리다는 걸 알게 될 테니, Perl의 자체 테스트는 건너뛰고 싶을 거예요.
문자열 버퍼에 쓰기 전에 THINKFIRST
SV가 쓰여질 준비가 되었는지 확인하는 데 SvTHINKFIRST(sv) 매크로가 보일 가능성이 높아요(그리고 그래야 해요). 이는 SV가:
- COW 버퍼를 가리키는지
- READONLY인지
- 참조를 담고 있는지
- 관련 매직이 할당되었는지
를 확인하는 단일 결합 검사예요.
두 관련 매크로가 그 검사를 수행하고, 필요하다면 동작도 수행할 수 있어요:
SV_CHECK_THINKFIRST(sv)는sv_force_normal(sv)를 호출해 문자열 버퍼를 복사해 변경 가능한 문자열로 만들고,SV_CHECK_THINKFIRST_COW_DROP(sv)는sv_force_normal_flags(sv, SV_COW_DROP_PV)를 호출해sv가 가리키는 문자열 버퍼를 버려요. 이는 보통sv에 완전히 새 문자열을 쓰기 전에 COW 문자열을 새 버퍼로 복사하지 않기 위해 사용돼요.
AV 다루기 (Working with AVs)
AV를 생성하고 로드하는 주요하고 오래된 방법은 두 가지예요. 첫 방법은 빈 AV를 만들어요:
AV* newAV();
두 번째 방법은 AV를 만들고 SVs로 초기 채웁니다:
AV* av_make(SSize_t num, SV **ptr);
두 번째 인자는 num 개의 SV*를 담은 배열을 가리켜요. AV가 생성된 후에는 원한다면 SVs를 파괴할 수 있어요.
Perl v5.36은 채우지 않고 AV를 만들고 SV** 배열을 할당하는 두 가지 새 방법을 추가했어요. 이들은 newAV() 후 av_extend() 하는 것보다 더 효율적이에요.
/* Creates but does not initialize (Zero) the SV** array */
AV *av = newAV_alloc_x(1);
/* Creates and does initialize (Zero) the SV** array */
AV *av = newAV_alloc_xz(1);
숫자 인자는 배열 인덱스가 아니라 할당할 배열 요소 수를 말하며 0보다 커야 해요. 첫 형식은 모든 요소가 어떤 읽기 전에 초기화될 때만 사용해야 해요. 초기화되지 않은 SV*를 읽는 것 — 즉 임의 메모리 주소를 SV*로 취급하는 것 — 은 심각한 버그예요.
AV가 생성되면 다음 연산이 가능해요:
void av_push(AV*, SV*);
SV* av_pop(AV*);
SV* av_shift(AV*);
void av_unshift(AV*, SSize_t num);
av_unshift를 제외하면 모두 익숙한 연산이에요. 이 루틴은 배열 앞에 undef 값으로 num 요소를 추가해요. 그런 다음 av_store(아래 설명)로 이 새 요소들에 값을 할당해야 해요.
다른 함수들:
Size_t av_count(AV*);
SSize_t av_top_index(AV*);
SV** av_fetch(AV*, SSize_t key, I32 lval);
SV** av_store(AV*, SSize_t key, SV* val);
av_count는 배열의 요소 수(채워진 것과 섞인 빈 슬롯(undefined) 포함)를 반환해요. av_top_index는 배열의 최고 인덱스 값을 반환해요 (Perl의 $#array처럼). 배열이 비어 있으면 -1을 반환해요. 항상 av_count() - 1과 같아요. av_fetch는 인덱스 key의 값을 반환하지만, lval이 0이 아니면 그 인덱스에 undef 값을 저장해요. av_store는 인덱스 key에 값 val을 저장하고 val의 참조 카운트를 증가시키지 않아요. 따라서 호출자가 그 부분을 책임져야 하고, av_store가 NULL을 반환하면 호출자가 메모리 누수를 피하기 위해 참조 카운트를 감소시켜야 해요. av_fetch와 av_store는 둘 다 SV*가 아니라 SV**를 반환한다는 점에 유의하세요.
몇 가지 더:
void av_clear(AV*);
void av_undef(AV*);
void av_extend(AV*, SSize_t key);
av_clear는 AV* 배열의 모든 요소를 삭제하지만 배열 자체는 실제로 삭제하지 않아요. av_undef는 배열의 모든 요소와 배열 자체를 삭제해요. av_extend는 배열이 최소한 key+1 요소를 담도록 확장해요. key+1이 현재 할당된 배열 길이보다 작으면 아무것도 하지 않아요.
배열 변수의 이름을 안다면 다음으로 AV 포인터를 얻을 수 있어요:
AV* get_av("package::varname", 0);
변수가 존재하지 않으면 NULL을 반환해요.
"Understanding the Magic of Tied Hashes and Arrays"에서 tied 배열에 배열 접근 함수를 사용하는 방법에 대한 정보를 참조하세요.
새롭거나 평범한(vanilla) AV로 더 효율적 작업
Perl v5.36과 v5.38은 일부 함수의 간소화된 인라인 버전을 도입했어요:
av_store_simpleav_fetch_simpleav_push_simple
이들은 그대로 대체 가능하지만, 다음 기준을 충족하는 단순한 AV에서만 쓸 수 있어요:
- 매직이 아니어야 하고
- 읽기 전용이 아니어야 하며
- "real" AV여야 하고 (다음 절)
- av_top_index 값이 > -2여야 해요
newAV(), av_make, newAV_alloc_x, newAV_alloc_xz로 만든 AV는 생성 시점에 모두 호환돼요. 읽기 전용이나 비현실로 선언되거나, 매직이 붙거나, 달리 비정상적으로 설정되어야 비호환으로 바뀌지요.
일부 인터프리터 함수는 정상 동작의 일부로 AV에 매직을 붙일 수 있어요. 따라서 AV의 수명주기를 확신하지 않는 한, 이 새 함수들을 AV 생성 지점 근처에서만 쓰는 것이 가장 안전해요.
Real AV — 그렇지 않은 것들
표준 또는 전형적 AV를 때로 "real" AV라고 하는데, AvREAL 매크로로 확인할 수 있어요. 이 상태가 기본적으로 의미하는 것은:
- 배열의 접근 가능한 모든 요소가 초기화되었지만 비어 있거나 라이브 SV에 대한 포인터를 담고 있고,
SV*가 배열 요소에 할당되면 그 SV의 참조 카운트가 증가하며, 반대로 요소가 해제되거나 다른SV*가 할당되면 추방된 SV의 참조 카운트가 감소한다.
"Fake" AV는 특정 제한된 사용 사례를 위해서만 의도돼요. 코어 perl 밖에서의 사용은 강력히 권장되지 않아요.
예를 들어 인터프리터의 인자 스택은 AV로 구현되지만 AvREAL이 아니에요. 그 요소는 역사적으로 참조 카운트되지 않아서, 문장 실행 중 SV가 해제되었지만 그 문장에서 나중에 여전히 필요할 때 "stack-not-refcounted" 종류의 버그가 발생해요. 이 버그 종류를 고치기 위해 참조 카운트 스택으로 전환하려는 노력이 진행 중이에요: 이 문서의 "Reference-counted argument stack" 절 참조.
"Fake" AV는 av_reify로 "real" AV로 변환할 수 있어요. AV에 SVpav_REIFY 플래그가 설정되어 있으면 변환이 발생해야 하는데, AvREIFY() 매크로로 확인할 수 있어요. AV에 SV*를 저장하거나 삭제하는 것은 보통 AvREIFY() 확인과 필요 시 변환을 수반해요.
정말 더 알고 싶다면 av.h의 주석과 av.c의 함수들을 보세요.
HV 다루기 (Working with HVs)
HV를 생성하려면 다음 루틴을 사용합니다:
HV* newHV();
HV가 생성되면 다음 연산이 가능해요:
SV** hv_store(HV*, const char* key, U32 klen, SV* val, U32 hash);
SV** hv_fetch(HV*, const char* key, U32 klen, I32 lval);
klen 파라미터는 넘겨지는 키의 길이예요 (Perl이 키 길이를 측정하도록 klen 값으로 0을 넘길 수 없어요). val 인자는 저장되는 스칼라의 SV 포인터를 담고, hash는 미리 계산된 해시 값이에요 (hv_store가 계산하도록 하려면 0). lval 파라미터는 이 fetch가 실제로 store 연산의 일부인지 나타내는데, 그 경우 주어진 키로 새 undef 값이 HV에 추가되고 hv_fetch는 값이 이미 존재했던 것처럼 반환해요.
hv_store와 hv_fetch는 SV*가 아니라 SV**를 반환한다는 것을 기억하세요. 스칼라 값에 접근하려면 먼저 반환값을 역참조해야 해요. 하지만 역참조 전에 반환값이 NULL이 아닌지 확인해야 해요.
이 두 함수 중 첫 번째는 해시 테이블 항목이 존재하는지 확인하고, 두 번째는 삭제해요.
bool hv_exists(HV*, const char* key, U32 klen);
SV* hv_delete(HV*, const char* key, U32 klen, I32 flags);
flags가 G_DISCARD 플래그를 포함하지 않으면 hv_delete는 삭제된 값의 mortal 복사본을 만들고 반환해요.
그리고 더 기타 함수들:
void hv_clear(HV*);
void hv_undef(HV*);
AV 대응물과 마찬가지로, hv_clear는 해시 테이블의 모든 항목을 삭제하지만 해시 테이블 자체는 삭제하지 않아요. hv_undef는 항목과 해시 테이블 자체를 모두 삭제해요.
Perl은 실제 데이터를 HE라는 typedef의 구조체 연결 리스트에 보관해요. 이것들은 실제 키와 값 포인터(추가 관리 오버헤드 포함)를 담고 있어요. 키는 문자열 포인터이고 값은 SV*예요. 하지만 HE*가 있으면 hv_iter* 루틴으로 실제 키와 값을 얻어요.
I32 hv_iterinit(HV*);
/* Prepares starting point to traverse hash table */
HE* hv_iternext(HV*);
/* Get the next entry, and return a pointer to a
structure that has both the key and value */
char* hv_iterkey(HE* entry, I32* retlen);
/* Get the key from an HE structure and also return
the length of the key string */
SV* hv_iterval(HV*, HE* entry);
/* Return an SV pointer to the value of the HE
structure */
SV* hv_iternextsv(HV*, char** key, I32* retlen);
/* This convenience routine combines hv_iternext,
hv_iterkey, and hv_iterval. The key and retlen
arguments are return values for the key and its
length. The value is returned in the SV* argument */
해시 변수의 이름을 안다면 다음으로 HV 포인터를 얻을 수 있어요:
HV* get_hv("package::varname", 0);
변수가 존재하지 않으면 NULL을 반환해요.
해시 알고리즘은 PERL_HASH 매크로에 정의돼 있어요:
PERL_HASH(hash, key, klen)
이 매크로의 정확한 구현은 아키텍처와 perl 버전에 따라 달라지고, 반환 값이 호출마다 바뀔 수 있으므로 값은 단일 perl 프로세스 동안에만 유효해요.
"Understanding the Magic of Tied Hashes and Arrays"에서 tied 해시에 해시 접근 함수를 사용하는 방법에 대한 정보를 참조하세요.
해시 API 확장 (Hash API Extensions)
버전 5.004부터 다음 함수도 지원돼요:
HE* hv_fetch_ent (HV* tb, SV* key, I32 lval, U32 hash);
HE* hv_store_ent (HV* tb, SV* key, SV* val, U32 hash);
bool hv_exists_ent (HV* tb, SV* key, U32 hash);
SV* hv_delete_ent (HV* tb, SV* key, I32 flags, U32 hash);
SV* hv_iterkeysv (HE* entry);
이 함수들은 SV* 키를 받아서 해시 구조체를 다루는 확장 코드 작성을 단순화한다는 점에 유의하세요. 이 함수들은 또한 키를 문자열화하도록 강제하지 않고 SV* 키를 tie 함수에 넘길 수 있게 해 줘요 (이전 함수 집합과 달리).
또한 전체 해시 항목(HE*)을 반환·수용하므로 더 효율적이에요 (특정 문자열의 해시 번호를 매번 다시 계산할 필요가 없으니까). 자세한 설명은 perlapi 참조.
해시 항목의 내용에 접근하려면 항상 다음 매크로를 사용해야 해요. 이 매크로의 인자는 여러 번 평가될 수 있으므로 단순 변수여야 한다는 점에 유의하세요. 이 매크로에 대한 자세한 설명은 perlapi 참조.
HePV(HE* he, STRLEN len)
HeVAL(HE* he)
HeHASH(HE* he)
HeSVKEY(HE* he)
HeSVKEY_force(HE* he)
HeSVKEY_set(HE* he, SV* sv)
이 두 저수준 매크로는 정의되어 있지만, SV*가 아닌 키를 다룰 때만 사용해야 해요:
HeKEY(HE* he)
HeKLEN(HE* he)
hv_store와 hv_store_ent 둘 다 저장된 val의 참조 카운트를 증가시키지 않으며 그것은 호출자의 책임이라는 점에 유의하세요. 이 함수들이 NULL 값을 반환하면 호출자는 보통 메모리 누수를 피하기 위해 val의 참조 카운트를 감소시켜야 해요.
AVs, HVs 그리고 undef 값
때로는 AVs나 HVs에 undef 값을 저장해야 해요. 드문 경우일 수 있지만 까다로울 수 있어요. undef SV가 필요하면 &PL_sv_undef를 쓰는 데 익숙하기 때문이에요.
예를 들어 직관은 이 XS 코드가:
AV *av = newAV();
av_store( av, 0, &PL_sv_undef );
이 Perl 코드와 동등하다고 말해요:
my @av;
$av[0] = undef;
안타깝게도 사실이 아니에요. perl 5.18 이전에서 AV는 &PL_sv_undef를 배열 요소가 아직 초기화되지 않았음을 나타내는 마커로 사용해요. 따라서 위의 Perl 코드에서는 exists $av[0]가 참이지만 XS 코드로 생성된 배열에서는 거짓이에요. perl 5.20에서는 &PL_sv_undef 자체가 저장되므로(복사본이 아니라) &PL_sv_undef를 저장하면 읽기 전용 요소가 생성돼요.
HVs에 &PL_sv_undef를 저장할 때도 비슷한 문제가 발생할 수 있어요:
hv_store( hv, "key", 3, &PL_sv_undef, 0 );
이것은 실제로 값을 undef로 만들지만, key의 값을 수정하려 하면 다음 오류가 나요:
Modification of non-creatable hash value attempted
perl 5.8.0에서 &PL_sv_undef는 제한된 해시의 자리표시자를 표시하는 데도 사용됐어요. 이로 인해 해시를 반복하거나 hv_exists 함수로 키를 확인할 때 그런 해시 항목이 나타나지 않게 됐어요.
&PL_sv_yes나 &PL_sv_no를 AVs나 HVs에 저장할 때도 비슷한 문제가 발생할 수 있어요. 그런 요소를 수정하려 하면 다음과 같은 오류가 나요:
Modification of a read-only value attempted
짧게 말하면, 특별 변수 &PL_sv_undef, &PL_sv_yes, &PL_sv_no를 AVs와 HVs에 사용할 수 있지만, 무엇을 하고 있는지 확실히 알아야 해요.
일반적으로 AV나 HV에 undef 값을 저장하려면 &PL_sv_undef를 쓰지 말고 newSV 함수로 새 undef 값을 만들어야 해요. 예:
av_store( av, 42, newSV(0) );
hv_store( hv, "foo", 3, newSV(0), 0 );
참조 (References)
참조는 다른 데이터 타입(다른 참조 포함)을 가리키는 특별한 종류의 스칼라예요.
참조를 만들려면 다음 함수 중 하나를 사용하세요:
SV* newRV_inc((SV*) thing);
SV* newRV_noinc((SV*) thing);
thing 인자는 SV*, AV*, HV* 중 어떤 것이든 될 수 있어요. 함수들은 newRV_inc가 thing의 참조 카운트를 증가시키고 newRV_noinc는 그렇지 않다는 점을 제외하면 동일해요. 역사적 이유로 newRV는 newRV_inc의 동의어예요.
참조가 있으면 다음 매크로로 역참조할 수 있어요:
SvRV(SV*)
그런 다음 적절한 루틴을 호출하고, 필요하면 반환된 SV*를 AV*나 HV*로 캐스팅하세요.
SV가 참조인지 확인하려면 다음 매크로를 사용하세요:
SvROK(SV*)
참조가 가리키는 값의 타입을 알아내려면 다음 매크로를 쓰고 반환값을 확인하세요:
SvTYPE(SvRV(SV*))
반환될 가장 유용한 타입은:
SVt_PVAV Array
SVt_PVHV Hash
SVt_PVCV Code
SVt_PVGV Glob (possibly a file handle)
SVt_PVAV보다 작은 어떤 숫자 값이 반환되면 어떤 형태의 스칼라예요.
자세한 내용은 perlapi의 "svtype" 참조.
Blessed References와 클래스 객체
참조는 객체지향 프로그래밍을 지원하는 데도 사용돼요. Perl의 OO 용어에서 객체는 패키지(또는 클래스)에 blessed된 참조일 뿐이에요. 일단 blessed되면 프로그래머는 그 참조로 클래스의 다양한 메서드에 접근할 수 있어요.
참조는 다음 함수로 패키지에 blessed될 수 있어요:
SV* sv_bless(SV* sv, HV* stash);
sv 인자는 참조 값이어야 해요. stash 인자는 참조가 속할 클래스를 지정해요. 클래스 이름을 stash로 변환하는 방법은 "Stashes and Globs" 참조.
다음 함수는 rv를 아직 참조가 아니면 참조로 업그레이드해요. rv가 가리킬 새 SV를 만들어요. classname이 non-null이면 SV가 지정된 클래스로 blessed돼요. SV가 반환돼요.
SV* newSVrv(SV* rv, const char* classname);
다음 세 함수는 정수, unsigned 정수, 또는 double을 참조가 rv인 SV에 복사해요. classname이 non-null이면 SV가 blessed돼요.
SV* sv_setref_iv(SV* rv, const char* classname, IV iv);
SV* sv_setref_uv(SV* rv, const char* classname, UV uv);
SV* sv_setref_nv(SV* rv, const char* classname, NV iv);
다음 함수는 포인터 값(주소이지 문자열이 아닙니다!)을 참조가 rv인 SV에 복사해요. classname이 non-null이면 SV가 blessed돼요.
SV* sv_setref_pv(SV* rv, const char* classname, void* pv);
다음 함수는 문자열을 참조가 rv인 SV에 복사해요. Perl이 문자열 길이를 계산하도록 length를 0으로 설정하세요. classname이 non-null이면 SV가 blessed돼요.
SV* sv_setref_pvn(SV* rv, const char* classname, char* pv,
STRLEN length);
다음 함수는 SV가 지정된 클래스로 blessed되었는지 테스트해요. 상속 관계는 확인하지 않아요.
int sv_isa(SV* sv, const char* name);
다음 함수는 SV가 blessed 객체에 대한 참조인지 테스트해요.
int sv_isobject(SV* sv);
다음 함수는 SV가 지정된 클래스에서 파생되었는지 테스트해요. SV는 blessed 객체에 대한 참조이거나 클래스 이름을 담은 문자열일 수 있어요. UNIVERSAL::isa 기능을 구현하는 함수예요.
bool sv_derived_from(SV* sv, const char* name);
특정 클래스에서 파생된 객체가 있는지 확인하려면 다음과 같이 작성해야 해요:
if (sv_isobject(sv) && sv_derived_from(sv, class)) { ... }
새 변수 생성 (Creating New Variables)
Perl 스크립트에서 접근할 수 있는 undef 값을 갖는 새 Perl 변수를 만들려면 변수 타입에 따라 다음 루틴을 사용하세요.
SV* get_sv("package::varname", GV_ADD);
AV* get_av("package::varname", GV_ADD);
HV* get_hv("package::varname", GV_ADD);
두 번째 파라미터로 GV_ADD를 쓰는 것을 주목하세요. 이제 데이터 타입에 적합한 루틴으로 새 변수를 설정할 수 있어요.
GV_ADD 인자와 비트 OR될 수 있는 추가 매크로가 있어 특정 추가 기능을 활성화해요. 그 비트들은:
GV_ADDMULTI — 변수를 다중 정의로 표시해 다음 경고를 방지해요:
Name <varname> used only once: possible typo
GV_ADDWARN — 함수 호출 전에 변수가 존재하지 않았다면 다음 경고를 발행해요:
Had to create <varname> unexpectedly
패키지 이름을 지정하지 않으면 변수는 현재 패키지에 생성돼요.
참조 카운트와 Mortality
Perl은 참조 카운트 기반 가비지 컬렉션 메커니즘을 사용해요. SVs, AVs, HVs(이하 줄여서 xV)는 참조 카운트 1로 시작해요. xV의 참조 카운트가 0으로 떨어지면 파괴되고 그 메모리는 재사용 가능해져요. 가장 기본적인 내부 수준에서 참조 카운트는 다음 매크로로 조작할 수 있어요:
int SvREFCNT(SV* sv);
SV* SvREFCNT_inc(SV* sv);
void SvREFCNT_dec(SV* sv);
(증가·감소 매크로의 접미사 버전도 있어요. 기본 매크로의 완전한 일반성을 성능과 교환할 수 있는 상황을 위한 것이에요.)
하지만 프로그래머가 참조에 대해 생각해야 하는 방식은 벌거벗은 참조 카운트라기보다는 참조의 소유권(ownership) 측면이에요. xV에 대한 참조는 여러 엔티티 중 하나가 소유할 수 있어요: 다른 xV, Perl 인터프리터, XS 데이터 구조, 실행 중인 코드 조각, 또는 동적 스코프. xV는 일반적으로 어떤 엔티티가 자신에 대한 참조를 소유하는지 알지 못해요. 그것이 가진 참조가 몇 개인지만 알지요, 그것이 참조 카운트예요.
참조 카운트를 올바르게 유지하려면 XS 코드가 조작하는 참조를 추적하는 것이 필수적이에요. 프로그래머는 항상 참조가 어디서 왔고 누가 소유하는지 알아야 하며, 참조의 생성·파괴, 소유권 이전을 인지해야 해요. 소유권은 xV 데이터 구조에 명시적으로 표현되지 않으므로 코드가 실제로 유지해야 하는 것은 참조 카운트뿐이고, 이는 소유권에 대한 이해가 실제로 코드에 드러나지 않는다는 뜻이에요. 예를 들어 한 소유자에서 다른 소유자로 참조 소유권을 이전해도 참조 카운트는 전혀 바뀌지 않으므로 실제 코드 없이 이룰 수 있어요.
Perl 수준에서 보이는 xV는 비참조되어 파괴되어서는 안 돼요. 보통 객체는 더 이상 보이지 않을 때만 비참조되는데, 종종 그것을 보이지 않게 만든 것과 같은 수단으로 그래요. 예를 들어 Perl 참조 값(RV)은 그 피참조자에 대한 참조를 소유하므로, RV가 덮어써지면 그 참조가 파괴되고 더 이상 도달할 수 없는 피참조자가 결과적으로 파괴될 수 있어요.
많은 함수가 그 목적의 일부로 어떤 종류의 참조 조작을 가져요. 때로는 참조 소유권 측면에서 문서화되고, 때로는 (덜 도움이 되게) 참조 카운트 변경 측면에서 문서화돼요. 예를 들어 newRV_inc() 함수는 새 RV(참조 카운트 1)를 만들고 호출자가 제공한 피참조자의 참조 카운트를 증가시키는 것으로 문서화돼요. 이는 생성된 RV가 소유하는 피참조자에 대한 새 참조를 만들고, RV에 대한 유일한 참조의 소유권을 호출자에게 반환하는 것으로 가장 잘 이해돼요. newRV_noinc() 함수는 대신 피참조자의 참조 카운트를 증가시키지 않지만, RV는 어쨌든 피참조자에 대한 참조를 소유하게 돼요. 따라서 newRV_noinc()의 호출자는 피참조자에 대한 참조를 포기하는 것으로 암시되며, 데이터 구조에는 덜 하지만 개념적으로는 더 복잡한 연산이 돼요.
예를 들어 XSUB 함수에서 참조를 반환하고 싶다고 상상해 보세요. XSUB 루틴 내부에서 처음에 단일 참조(소유자는 XSUB 루틴)만 가진 SV를 만들어요. 이 참조는 루틴이 완료되기 전에 처분되어야 해요. 그렇지 않으면 누수되어 SV가 절대 파괴되지 않게 방지할 거예요. 그래서 SV를 참조하는 RV를 만들려면 SV를 newRV_noinc()에 넘기는 것이 가장 편리한데, 그 참조를 소비하기 때문이에요. 이제 XSUB 루틴은 SV에 대한 참조를 더 이상 소유하지 않지만, SV에 대한 참조를 소유하는 RV에 대한 참조는 소유해요. RV에 대한 참조 소유권은 XSUB에서 RV를 반환하는 과정으로 이전돼요.
xV의 파괴를 도와주는 편의 함수들이 있어요. 이 함수들은 "mortality" 개념을 도입해요. 많은 문서가 xV 자체가 mortal이라고 말하지만, 이는 오해의 소지가 있어요. 실제로 mortal한 것은 xV에 대한 참조이고, 단일 xV에 대한 mortal 참조가 여러 개 있을 수 있어요. 참조가 mortal하다는 것은 Perl의 많은 내부 스택 중 하나인 temps 스택이 소유한다는 뜻이며, 그 스택이 "잠시 후에" 그 참조를 파괴해요. 보통 "잠시 후"는 현재 Perl 문장의 끝이에요. 하지만 동적 스코프 주변에서는 더 복잡해져요: 서로 다른 파괴 날짜를 가진 mortal 참조 집합이 동시에 여러 개 있을 수 있어요. 내부적으로 mortal xV 참조가 언제 파괴되는지 결정하는 실제 요인은 두 매크로, SAVETMPS와 FREETMPS에 의존해요. 이 매크로에 대한 자세한 내용은 perlcall, perlxs, 아래 "Temporaries Stack" 참조.
Mortal 참조는 주로 Perl의 주요 스택에 놓이는 xV에 사용돼요. 스택은 참조 추적에 문제가 있는데, 많은 xV 참조를 담고 있지만 그것들을 소유하지 않기 때문이에요: 계산되지 않아요. 현재 스택이 참조하는 동안 xV가 파괴되어 발생하는 버그가 많은데, 스택의 계수되지 않은 참조가 xV를 살려두기에 충분하지 않기 때문이에요. 그래서 (계수되지 않은) 참조를 스택에 놓을 때, 그 xV에 대한 계수된 참조가 계수되지 않은 참조만큼 오래 지속되도록 보장하는 것이 매우 중요해요. 하지만 그 계수된 참조를 적절한 시점에 정리하고 xV의 수명을 부당하게 연장하지 않는 것도 중요해요. mortal 참조가 있다는 것은 이 요구를 충족시키는 가장 좋은 방법인 경우가 많아요, 특히 xV가 스택에 놓기 위해 특별히 생성되었고 그렇지 않으면 비참조될 경우에요.
mortal 참조를 만들려면 다음 함수를 사용하세요:
SV* sv_newmortal()
SV* sv_mortalcopy(SV*)
SV* sv_2mortal(SV*)
sv_newmortal()은 (undef 값을 가진) 유일한 참조가 mortal인 SV를 만들어요. sv_mortalcopy()는 값이 주어진 xV의 복사본이고 유일한 참조가 mortal인 xV를 만들어요. sv_2mortal()은 기존 xV 참조를 mortal화해요: 호출자에서 temps 스택으로 참조 소유권을 이전해요. sv_newmortal은 새 SV에 값을 주지 않으므로 보통 sv_setpv, sv_setiv 등으로 값을 줘야 해요:
SV *tmp = sv_newmortal();
sv_setiv(tmp, an_integer);
이것은 여러 C 문장이므로 대신 이 관용구를 보는 게 아주 흔해요:
SV *tmp = sv_2mortal(newSViv(an_integer));
mortal 루틴은 SV만을 위한 것이 아니에요. AV와 HV도 그 주소(SV*로 타입캐스팅)를 sv_2mortal이나 sv_mortalcopy 루틴에 넘겨 mortal로 만들 수 있어요.
Stashes와 Globs
stash는 패키지 내에 정의된 모든 변수를 담는 해시예요. stash의 각 키는 심볼 이름(같은 이름을 가진 모든 종류의 객체가 공유)이고, 해시 테이블의 각 값은 GV(Glob Value)예요. 이 GV는 그 이름의 다양한 객체에 대한 참조를 담는데, 다음을 포함하되 이에 국한되지 않아요:
Scalar Value
Array Value
Hash Value
I/O Handle
Format
Subroutine
main 패키지에 존재하는 항목을 보유하는 PL_defstash라는 단일 stash가 있어요. 다른 패키지의 항목에 접근하려면 패키지 이름에 "::"를 붙이세요. Foo 패키지의 항목은 PL_defstash의 stash Foo::에 있어요. Bar::Baz 패키지의 항목은 Bar::의 stash 안에 있는 stash Baz::에 있어요.
특정 패키지의 stash 포인터를 얻으려면 다음 함수를 사용하세요:
HV* gv_stashpv(const char* name, I32 flags)
HV* gv_stashsv(SV*, I32 flags)
첫 함수는 리터럴 문자열을, 두 번째는 SV에 저장된 문자열을 사용해요. stash는 그냥 해시 테이블이므로 HV*를 돌려받는다는 것을 기억하세요. flags가 GV_ADD로 설정되면 새 패키지를 만들어요.
gv_stash*v가 원하는 이름은 심볼 테이블을 원하는 패키지의 이름이에요. 기본 패키지는 main이라 불러요. 다중 중첩 패키지가 있으면 Perl 언어 자체처럼 ::로 구분된 이름을 gv_stash*v에 넘기세요.
대안으로, blessed 참조인 SV가 있다면 다음으로 stash 포인터를 찾을 수 있어요:
HV* SvSTASH(SvRV(SV*));
그런 다음 다음으로 패키지 이름 자체를 얻어요:
char* HvNAME(HV* stash);
객체를 bless하거나 재-bless해야 한다면 다음 함수를 사용할 수 있어요:
SV* sv_bless(SV*, HV* stash)
여기서 첫 인자 SV*는 참조여야 하고, 두 번째 인자는 stash예요. 반환된 SV*는 이제 다른 SV와 같은 방식으로 사용할 수 있어요.
참조와 blessing에 대한 더 많은 정보는 perlref 참조.
I/O 핸들
AV와 HV처럼 IO 객체는 입력·출력 PerlIO 객체나 opendir()의 DIR *를 담을 수 있는 또 다른 종류의 비-스칼라 SV예요.
새 IO 객체를 만들 수 있어요:
IO* newIO();
다른 SV와 달리, 새 IO 객체는 자동으로 IO::File 클래스로 blessed돼요.
IO 객체는 입력·출력 PerlIO 핸들을 담고 있어요:
PerlIO *IoIFP(IO *io);
PerlIO *IoOFP(IO *io);
보통 IO 객체가 파일에 대해 열렸다면 입력 핸들은 항상 존재하지만, 출력 핸들은 파일이 출력용으로 열린 경우에만 존재해요. 파일의 경우 둘 다 존재하면 같은 PerlIO 객체일 거예요.
소켓과 문자 장치에는 별개의 입력·출력 PerlIO 객체가 생성돼요.
IO 객체는 Perl I/O 핸들과 연관된 다른 데이터도 담고 있어요:
IV IoLINES(io); /* $. */
IV IoPAGE(io); /* $% */
IV IoPAGE_LEN(io); /* $= */
IV IoLINES_LEFT(io); /* $- */
char *IoTOP_NAME(io); /* $^ */
GV *IoTOP_GV(io); /* $^ */
char *IoFMT_NAME(io); /* $~ */
GV *IoFMT_GV(io); /* $~ */
char *IoBOTTOM_NAME(io);
GV *IoBOTTOM_GV(io);
char IoTYPE(io);
U8 IoFLAGS(io);
대부분은 formats와 관련돼 있어요.
IoFLAGs()는 플래그 조합을 담을 수 있는데, 가장 흥미로운 것은 autoflush를 위한 IOf_FLUSH ($|)와 IO::Handle의 untaint() 메서드로 설정 가능한 IOf_UNTAINT예요.
IO 객체는 디렉터리 핸들도 담을 수 있어요:
DIR *IoDIRP(io);
PerlDir_read() 등과 함께 사용하기 적합해요.
이 모든 접근자 매크로는 lvalue이고, IO 객체의 멤버를 수정하는 별도의 _set() 매크로는 없어요.
이중 타입 SV (Double-Typed SVs)
스칼라 변수는 보통 정수, double, 포인터, 참조 중 한 종류의 값만 담아요. Perl은 실제 스칼라 데이터를 저장된 타입에서 요청된 타입으로 자동 변환해요.
일부 스칼라 변수는 한 종류 이상의 스칼라 데이터를 담아요. 예를 들어 $! 변수는 errno의 숫자 값 또는 strerror나 sys_errlist[]의 문자열 버전을 담아요.
여러 데이터 값을 SV에 강제로 넣으려면 두 가지를 해야 해요: 추가 스칼라 타입을 더하는 sv_set*v 루틴을 사용하고, 그런 다음 Perl이 한 종류 이상의 데이터를 담고 있다고 믿도록 플래그를 설정해요. 플래그를 설정하는 네 매크로는:
SvIOK_on
SvNOK_on
SvPOK_on
SvROK_on
사용해야 할 특정 매크로는 먼저 호출한 sv_set*v 루틴에 따라 달라져요. 모든 sv_set*v 루틴은 설정되는 특정 데이터 타입의 비트만 켜고 나머지는 모두 끄기 때문이에요.
예를 들어 숫자와 기술 문자열 오류 값을 모두 담은 "dberror"이라는 새 Perl 변수를 만들려면 다음 코드를 사용할 수 있어요:
extern int dberror;
extern char *dberror_list;
SV* sv = get_sv("dberror", GV_ADD);
sv_setiv(sv, (IV) dberror);
sv_setpv(sv, dberror_list[dberror]);
SvIOK_on(sv);
sv_setiv와 sv_setpv의 순서가 뒤바뀌었다면 SvIOK_on 대신 매크로 SvPOK_on을 호출해야 해요.
매직 변수 (Magic Variables)
[이 절은 아직 공사 중이에요. 여기의 모든 것은 무시하세요.] 어떤 SV든 매직일 수 있어요, 즉 정상 SV가 없는 특별한 기능을 가져요. 이 기능은 SV 구조체에 struct magic의 연결 리스트로 저장되며, MAGIC로 typedef돼요.
struct magic {
MAGIC* mg_moremagic;
MGVTBL* mg_virtual;
U16 mg_private;
char mg_type;
U8 mg_flags;
I32 mg_len;
SV* mg_obj;
char* mg_ptr;
};
이는 patchlevel 0 기준이며 언제든 바뀔 수 있어요.
매직 할당 (Assigning Magic)
Perl은 sv_magic 함수로 SV에 매직을 추가해요:
void sv_magic(SV* sv, SV* obj, int how, const char* name, I32 namlen);
sv 인자는 새 매직 기능을 획득할 SV에 대한 포인터예요.
sv가 아직 매직이 아니면 Perl은 SvUPGRADE 매크로로 sv를 타입 SVt_PVMG로 변환해요. 그런 다음 Perl은 매직 기능의 연결 리스트 앞에 새 매직을 추가해 계속해요. 같은 타입의 기존 매직 항목은 삭제돼요. 이는 재정의될 수 있고, 같은 타입의 매직 인스턴스 여러 개가 SV와 연관될 수 있다는 점에 유의하세요.
name과 namlen 인자는 문자열을 매직과 연관시키는 데 사용되며, 보통 변수의 이름이에요. namlen은 mg_len 필드에 저장되고, name이 non-null이면 namlen이 0보다 큰지 같은지에 따라 각각 savepvn 복사본 또는 name 자체가 mg_ptr 필드에 저장돼요. 특별한 경우로, (name && namlen == HEf_SVKEY)이면 name이 SV*를 담고 있는 것으로 가정되고 REFCNT가 증가된 채 그대로 저장돼요.
sv_magic 함수는 how를 사용해 어떤 사전 정의된 "Magic Virtual Table"을 mg_virtual 필드에 할당할지 결정해요. 아래 "Magic Virtual Tables" 참조. how 인자도 mg_type 필드에 저장돼요. how 값은 perl.h의 PERL_MAGIC_foo 매크로 집합에서 선택해야 해요. 이 매크로가 추가되기 전에는 Perl 내부가 문자 리터럴을 직접 사용했으므로, 예를 들어 PERL_MAGIC_uvar 대신 'U' 매직을 언급하는 옛 코드나 문서를 가끔 만날 수 있어요.
obj 인자는 MAGIC 구조체의 mg_obj 필드에 저장돼요. sv 인자와 같지 않으면 obj 객체의 참조 카운트가 증가돼요. 같거나, how 인자가 PERL_MAGIC_arylen, PERL_MAGIC_regdatum, PERL_MAGIC_regdata이거나 NULL 포인터면 obj는 참조 카운트 증가 없이 그냥 저장돼요.
SV에 매직을 추가하는 더 유연한 방법은 perlapi의 sv_magicext도 참조하세요.
HV에 매직을 추가하는 함수도 있어요:
void hv_magic(HV *hv, GV *gv, int how);
이것은 단순히 sv_magic을 호출하고 gv 인자를 SV로 강제 변환해요.
SV에서 매직을 제거하려면 sv_unmagic 함수를 호출하세요:
int sv_unmagic(SV *sv, int type);
type 인자는 SV가 처음 매직이 됐을 때의 how 값과 같아야 해요.
단, sv_unmagic은 SV에서 특정 type의 모든 매직을 제거한다는 점에 유의하세요. 매직 가상 테이블을 기반으로 type의 특정 매직만 제거하려면 대신 sv_unmagicext를 사용하세요:
int sv_unmagicext(SV *sv, int type, MGVTBL *vtbl);
Magic Virtual Tables
MAGIC 구조체의 mg_virtual 필드는 MGVTBL에 대한 포인터예요. 이는 그 변수에 적용될 수 있는 다양한 연산을 처리하는 함수 포인터 구조체로 "Magic Virtual Table"을 뜻해요.
MGVTBL은 다음 루틴 타입에 대한 다섯 개(때로는 여덟 개)의 포인터를 가져요:
int (*svt_get) (pTHX_ SV* sv, MAGIC* mg);
int (*svt_set) (pTHX_ SV* sv, MAGIC* mg);
U32 (*svt_len) (pTHX_ SV* sv, MAGIC* mg);
int (*svt_clear)(pTHX_ SV* sv, MAGIC* mg);
int (*svt_free) (pTHX_ SV* sv, MAGIC* mg);
int (*svt_copy) (pTHX_ SV *sv, MAGIC* mg, SV *nsv,
const char *name, I32 namlen);
int (*svt_dup) (pTHX_ MAGIC *mg, CLONE_PARAMS *param);
int (*svt_local)(pTHX_ SV *nsv, MAGIC *mg);
이 MGVTBL 구조체는 컴파일 타임에 perl.h에서 설정되며 현재 32가지 타입이 있어요. 이 다른 구조체들은 호출되는 함수에 따라 추가 동작을 수행하는 다양한 루틴에 대한 포인터를 담고 있어요.
Function pointer Action taken
---------------- ------------
svt_get Do something before the value of the SV is
retrieved.
svt_set Do something after the SV is assigned a value.
svt_len Report on the SV's length.
svt_clear Clear something the SV represents.
svt_free Free any extra storage associated with the SV.
svt_copy copy tied variable magic to a tied element
svt_dup duplicate a magic structure during thread cloning
svt_local copy magic to local value during 'local'
예를 들어 vtbl_sv라는 MGVTBL 구조체(PERL_MAGIC_sv의 mg_type에 해당)는 다음을 담고 있어요:
{ magic_get, magic_set, magic_len, 0, 0 }
따라서 SV가 PERL_MAGIC_sv 타입의 매직으로 판정되고 get 연산이 수행되면 magic_get 루틴이 호출돼요. 다양한 매직 타입의 모든 루틴은 magic_로 시작해요. 참고: 매직 루틴은 Perl API의 일부로 간주되지 않으며 Perl 라이브러리에서 내보내지지 않을 수 있어요.
마지막 세 슬롯은 최근 추가된 것으로, 소스 호환성을 위해 MGf_COPY, MGf_DUP, MGf_LOCAL 중 하나가 mg_flags에 설정된 경우에만 확인돼요. 이는 대부분의 코드가 vtable을 5-요소 값으로 계속 선언할 수 있다는 뜻이에요. 이 세 개는 현재 전적으로 스레딩 코드에서만 사용되며 변경 가능성이 높아요.
현재 종류의 Magic Virtual Tables는:
mg_type
(old-style char and macro) MGVTBL Type of magic
-------------------------- ------ -------------
\0 PERL_MAGIC_sv vtbl_sv Special scalar variable
# PERL_MAGIC_arylen vtbl_arylen Array length ($#ary)
% PERL_MAGIC_rhash (none) Extra data for restricted
hashes
* PERL_MAGIC_debugvar vtbl_debugvar $DB::single, signal, trace
vars
. PERL_MAGIC_pos vtbl_pos pos() lvalue
: PERL_MAGIC_symtab (none) Extra data for symbol
tables
< PERL_MAGIC_backref vtbl_backref For weak ref data
@ PERL_MAGIC_arylen_p (none) To move arylen out of XPVAV
B PERL_MAGIC_bm vtbl_regexp Boyer-Moore
(fast string search)
c PERL_MAGIC_overload_table vtbl_ovrld Holds overload table
(AMT) on stash
D PERL_MAGIC_regdata vtbl_regdata Regex match position data
(@+ and @- vars)
d PERL_MAGIC_regdatum vtbl_regdatum Regex match position data
element
E PERL_MAGIC_env vtbl_env %ENV hash
e PERL_MAGIC_envelem vtbl_envelem %ENV hash element
f PERL_MAGIC_fm vtbl_regexp Formline
('compiled' format)
g PERL_MAGIC_regex_global vtbl_mglob m//g target
H PERL_MAGIC_hints vtbl_hints %^H hash
h PERL_MAGIC_hintselem vtbl_hintselem %^H hash element
I PERL_MAGIC_isa vtbl_isa @ISA array
i PERL_MAGIC_isaelem vtbl_isaelem @ISA array element
k PERL_MAGIC_nkeys vtbl_nkeys scalar(keys()) lvalue
L PERL_MAGIC_dbfile (none) Debugger %_<filename
l PERL_MAGIC_dbline vtbl_dbline Debugger %_<filename
element
N PERL_MAGIC_shared (none) Shared between threads
n PERL_MAGIC_shared_scalar (none) Shared between threads
o PERL_MAGIC_collxfrm vtbl_collxfrm Locale transformation
P PERL_MAGIC_tied vtbl_pack Tied array or hash
p PERL_MAGIC_tiedelem vtbl_packelem Tied array or hash element
q PERL_MAGIC_tiedscalar vtbl_packelem Tied scalar or handle
r PERL_MAGIC_qr vtbl_regexp Precompiled qr// regex
S PERL_MAGIC_sig vtbl_sig %SIG hash
s PERL_MAGIC_sigelem vtbl_sigelem %SIG hash element
t PERL_MAGIC_taint vtbl_taint Taintedness
U PERL_MAGIC_uvar vtbl_uvar Available for use by
extensions
u PERL_MAGIC_uvar_elem (none) Reserved for use by
extensions
V PERL_MAGIC_vstring (none) SV was vstring literal
v PERL_MAGIC_vec vtbl_vec vec() lvalue
w PERL_MAGIC_utf8 vtbl_utf8 Cached UTF-8 information
X PERL_MAGIC_destruct vtbl_destruct destruct callback
x PERL_MAGIC_substr vtbl_substr substr() lvalue
Y PERL_MAGIC_nonelem vtbl_nonelem Array element that does not
exist
y PERL_MAGIC_defelem vtbl_defelem Shadow "foreach" iterator
variable / smart parameter
vivification
Z PERL_MAGIC_hook vtbl_hook %{^HOOK} hash
z PERL_MAGIC_hookelem vtbl_hookelem %{^HOOK} hash element
\ PERL_MAGIC_lvref vtbl_lvref Lvalue reference
constructor
] PERL_MAGIC_checkcall vtbl_checkcall Inlining/mutation of call
to this CV
^ PERL_MAGIC_extvalue (none) Value magic available for
use by extensions
~ PERL_MAGIC_ext (none) Variable magic available
for use by extensions
테이블에 대문자와 소문자가 모두 존재하면, 대문자는 보통 어떤 종류의 복합 타입(리스트나 해시)을 나타내고 소문자는 그 복합 타입의 요소를 나타내는 데 사용돼요. 일부 내부 코드는 이 대소문자 관계를 이용해요. 하지만 'v'와 'V'(vec와 v-string)는 전혀 관련이 없어요.
PERL_MAGIC_ext, PERL_MAGIC_extvalue, PERL_MAGIC_uvar 매직 타입은 확장의 사용을 위해 특별히 정의되었으며 perl 자체는 사용하지 않아요. 확장은 PERL_MAGIC_ext나 PERL_MAGIC_extvalue 매직으로 변수(보통 객체)에 사설 정보를 '붙일' 수 있어요. 이는 특히 유용한데, 정상 perl 코드가 이 사설 정보를 손상시킬 방법이 없기 때문이에요 (해시 객체의 추가 요소를 쓰는 것과 달리). PERL_MAGIC_extvalue는 (PERL_MAGIC_ext와 PERL_MAGIC_uvar와 달리) 값 매직이어서, 지역화 시 새 값은 매직이 되지 않아요.
마찬가지로 PERL_MAGIC_uvar 매직은 tie()와 매우 비슷하게, 스칼라 값이 사용되거나 변경될 때마다 C 함수를 호출하는 데 쓸 수 있어요. MAGIC의 mg_ptr 필드는 ufuncs 구조체를 가리켜요:
struct ufuncs {
I32 (*uf_val)(pTHX_ IV, SV*);
I32 (*uf_set)(pTHX_ IV, SV*);
IV uf_index;
};
SV가 읽히거나 쓰일 때 uf_val 또는 uf_set 함수가 호출되는데, uf_index가 첫 인자, SV에 대한 포인터가 두 번째 인자로 전달돼요. PERL_MAGIC_uvar 매직을 추가하는 간단한 예는 아래와 같아요. ufuncs 구조체는 sv_magic가 복사하므로 스택에 안전하게 할당할 수 있어요.
void
Umagic(sv)
SV *sv;
PREINIT:
struct ufuncs uf;
CODE:
uf.uf_val = &my_get_fn;
uf.uf_set = &my_set_fn;
uf.uf_index = 0;
sv_magic(sv, 0, PERL_MAGIC_uvar, (char*)&uf, sizeof(uf));
PERL_MAGIC_uvar를 배열에 붙이는 것은 허용되지만 효과가 없어요.
해시에는 해시 키(값은 아님)에 대한 제어를 주는 특수 훅이 있어요. 이 훅은 ufuncs 구조체의 "set" 함수가 NULL이면 PERL_MAGIC_uvar 'get' 매직을 호출해요. 이 훅은 해시가 hv_store_ent, hv_fetch_ent, hv_delete_ent, hv_exists_ent 함수를 통해 SV로 지정된 키로 접근될 때마다 활성화돼요. ..._ent 접미사 없는 함수로 문자열로 키에 접근하면 훅을 우회해요. 자세한 설명은 Hash::Util::FieldHash의 "GUTS" 참조.
여러 확장이 PERL_MAGIC_ext나 PERL_MAGIC_uvar 매직을 사용할 수 있으므로 확장이 충돌을 피하기 위해 각별히 주의하는 것이 중요해요. 보통 확장과 같은 클래스로 blessed된 객체에만 매직을 사용하는 것으로 충분해요. PERL_MAGIC_ext 매직의 경우 모든 필드가 0이어도 MGVTBL을 정의하는 것이 좋은데, 개별 MAGIC 포인터를 매직 가상 테이블로 특정 매직 종류로 식별할 수 있게 하기 위해서예요. mg_findext가 이를 쉽게 해 줘요:
static MGVTBL my_vtbl = { 0, 0, 0, 0, 0, 0, 0, 0 };
MAGIC *mg;
if ((mg = mg_findext(sv, PERL_MAGIC_ext, &my_vtbl))) {
/* this is really ours, not another module's PERL_MAGIC_ext */
my_priv_data_t *priv = (my_priv_data_t *)mg->mg_ptr;
...
}
또한 앞서 설명한 sv_set*()와 sv_cat*() 함수는 대상의 'set' 매직을 호출하지 않는다는 점에 유의하세요. 이는 사용자가 이 함수들을 호출한 후 SvSETMAGIC() 매크로를 호출하거나 sv_set*_mg() 또는 sv_cat*_mg() 함수 중 하나를 사용해 수행해야 해요. 마찬가지로, 매직을 다루지 않는 함수에서 외부 소스에서 얻은 SV를 사용한다면 일반 C 코드가 'get' 매직을 호출하기 위해 SvGETMAGIC() 매크로를 호출해야 해요. 이 함수들의 설명은 perlapi 참조. 예를 들어 sv_cat*() 함수에 대한 호출은 보통 SvSETMAGIC()가 뒤따라야 하지만, 그 구현이 'get' 매직을 처리하므로 앞의 SvGETMAGIC()는 필요 없어요.
매직 찾기 (Finding Magic)
MAGIC *mg_find(SV *sv, int type); /* Finds the magic pointer of that
* type */
이 루틴은 SV에 저장된 MAGIC 구조체에 대한 포인터를 반환해요. SV에 그 매직 기능이 없으면 NULL을 반환해요. SV에 그 매직 기능의 인스턴스가 여러 개 있으면 첫 번째를 반환해요. mg_findext는 매직 타입과 매직 가상 테이블을 모두 기준으로 SV의 MAGIC 구조체를 찾는 데 쓸 수 있어요:
MAGIC *mg_findext(SV *sv, int type, MGVTBL *vtbl);
또한 mg_find나 mg_findext에 넘긴 SV가 SVt_PVMG 타입이 아니면 Perl이 코어 덤프할 수 있어요.
int mg_copy(SV* sv, SV* nsv, const char* key, STRLEN klen);
이 루틴은 sv가 어떤 종류의 매직을 가졌는지 확인해요. mg_type 필드가 대문자면 mg_obj가 nsv에 복사되지만 mg_type 필드는 소문자로 바뀌어요.
Tied Hashes와 Arrays의 매직 이해
Tied 해시와 배열은 PERL_MAGIC_tied 매직 타입의 매직 짐승이에요.
경고: 5.004 릴리스부터 배열·해시 접근 함수를 올바르게 사용하려면 몇 가지 주의 사항을 이해해야 해요. 이 중 일부는 실제로 나중 릴리스에서 고쳐질 API의 버그로 간주되며 아래에서 [MAYCHANGE]로 괄호로 묶여 있어요. 이 절의 정보를 실제로 적용한다면, 행동이 미래에 경고 없이 바뀔 수 있다는 점을 알아두세요.
perl tie 함수는 변수를 다양한 GET, SET 등 메서드를 구현하는 객체와 연관시켜요. XSUB에서 perl tie 함수의 등가물을 수행하려면 이 행동을 모방해야 해요. 아래 코드는 필요한 단계를 수행해요 — 먼저 새 해시를 만들고, 그런 다음 tie 메서드를 구현할 클래스로 bless할 두 번째 해시를 만들어요. 마지막으로 두 해시를 함께 tie하고 새 tied 해시에 대한 참조를 반환해요. 아래 코드는 MyTie 클래스의 TIEHASH 메서드를 호출하지 않는다는 점에 유의하세요 — 이 작업 방법은 "Calling Perl Routines from within C Programs" 참조.
SV*
mytie()
PREINIT:
HV *hash;
HV *stash;
SV *tie;
CODE:
hash = newHV();
tie = newRV_noinc((SV*)newHV());
stash = gv_stashpv("MyTie", GV_ADD);
sv_bless(tie, stash);
hv_magic(hash, (GV*)tie, PERL_MAGIC_tied);
SvREFCNT_dec(tie); /* hv_magic() increases tie ref count */
RETVAL = newRV_noinc(hash);
OUTPUT:
RETVAL
av_store 함수는 tied 배열 인자를 받으면 mg_copy로 배열의 매직을 "저장"될 값에 복사할 뿐이에요. 값이 실제로 배열에 저장될 필요가 없었다는 것을 나타내며 NULL을 반환할 수도 있어요. [MAYCHANGE] tied 배열에서 av_store를 호출한 후, 호출자는 TIEARRAY 객체의 perl 수준 "STORE" 메서드를 실제로 호출하기 위해 보통 mg_set(val)을 호출해야 해요. av_store가 NULL을 반환했다면 메모리 누수를 피하기 위해 SvREFCNT_dec(val) 호출도 보통 필요해요. [/MAYCHANGE]
위 단락은 hv_store와 hv_store_ent 함수를 사용한 tied 해시 접근에도 그대로 적용돼요.
av_fetch와 대응하는 해시 함수 hv_fetch, hv_fetch_ent는 실제로 mg_copy를 사용해 매직이 초기화된 undef mortal 값을 반환해요. 그렇게 반환된 값은 이미 mortal이므로 할당 해제할 필요가 없어요. [MAYCHANGE] 하지만 기본 TIE 객체의 perl 수준 "FETCH" 메서드를 실제로 호출하려면 반환된 값에 mg_get()을 호출해야 해요. 마찬가지로 sv_setsv로 적절한 값을 할당한 후 반환값에 mg_set()을 호출할 수도 있는데, 이는 TIE 객체의 "STORE" 메서드를 호출해요. [/MAYCHANGE]
[MAYCHANGE] 즉, 배열·해시 fetch/store 함수는 tied 배열·해시의 경우 실제 값을 fetch하고 store하지 않아요. 그들은 그저 mg_copy를 호출해 "저장"되거나 "fetch"되려던 값에 매직을 붙일 뿐이에요. 나중의 mg_get과 mg_set 호출이 실제로 기본 객체의 TIE 메서드를 호출하는 작업을 해요. 따라서 매직 메커니즘은 현재 일종의 지연 접근을 배열과 해시에 구현해요.
현재 (perl 5.004 기준) 해시·배열 접근 함수를 사용하려면 사용자가 "정상" 해시·배열에서 작동하는지, 아니면 tied 변형에서 작동하는지 인지해야 해요. 미래 버전에서는 tied와 정상 데이터 타입 모두에 더 투명한 접근을 제공하도록 API가 바뀔 수 있어요. [/MAYCHANGE]
TIEARRAY와 TIEHASH 인터페이스는 균일한 해시·배열 구문을 사용하면서 몇 가지 perl 메서드 호출을 호출하게 만드는 순수한 설탕이라는 것을 잘 이해하는 게 좋아요. 이 설탕을 사용하면 오버헤드가 발생해요 (FETCH/STORE 연산당 보통 추가 opcode 2~4개에, 메서드 호출에 필요한 모든 mortal 변수 생성까지). 이 오버헤드는 TIE 메서드 자체가 실질적이면 비교적 작지만, 단 몇 문장이면 오버헤드가 무시할 수 없을 거예요.
변경 로컬라이징 (Localizing changes)
Perl에는 매우 편리한 구조가 있어요:
{
local $var = 2;
...
}
이 구조는 대략 다음과 동등해요:
{
my $oldvar = $var;
$var = 2;
...
$var = $oldvar;
}
가장 큰 차이는 첫 구조가 goto, return, die/eval 등 블록을 어떻게 나가든 간에 $var의 초기 값을 복원한다는 점이에요. 또한 조금 더 효율적이에요.
C에서 Perl API를 통해 비슷한 작업을 하는 방법이 있어요: pseudo-block을 만들고 블록 끝에서 일부 변경이 자동으로 취소되도록 (명시적으로 또는 die()를 통한 비-로컬 나가기로) 배열해요. block-같은 구조는 ENTER/LEAVE 매크로 쌍으로 만들어져요 (perlcall의 "Returning a Scalar" 참조). 이런 구조는 어떤 중요한 지역화 작업을 위해 특별히 만들어지거나, 기존 것(둘러싸는 Perl 서브루틴/블록의 경계나 TMPs 해제를 위한 기존 쌍)을 사용할 수 있어요. (두 번째 경우 추가 지역화의 오버헤드는 거의 무시할 수 있어야 해요.) 모든 XSUB는 자동으로 ENTER/LEAVE 쌍으로 둘러싸여 있다는 점에 유의하세요.
이런 pseudo-block 안에서는 다음 서비스가 제공돼요:
SAVEINT(int i), SAVEIV(IV i), SAVEI32(I32 i), SAVEI8(I8 i), SAVEI16(I16 i), SAVEBOOL(int i), SAVESTRLEN(STRLEN i) — 이 매크로들은 둘러싸는 pseudo-block이 끝날 때 정수 변수 i의 값을 복원하도록 배열해요.
SAVESPTR(s), SAVEPPTR(p) — 이 매크로들은 포인터 s와 p의 값을 복원하도록 배열해요. s는 SV*로의 변환과 복귀를 견디는 타입의 포인터여야 하고, p는 char*로의 변환과 복귀를 견딜 수 있어야 해요.
SAVERCPV(char **ppv) — 이 매크로는 rcpv_new() 호출로 할당된 char * 변수의 값을 현재 pseudo block이 완료될 때 이전 상태로 복원하도록 배열해요. 호출 시점에 *ppv에 저장된 포인터는 refcount 증가되어 save stack에 저장돼요. 나중에 현재 pseudo-block이 완료되면 *ppv에 저장된 값은 refcount 감소되고, savestack에서 이전 값이 복원되며 이것도 refcount 감소돼요. 이는 SAVEGENERICSV()의 RCPV 등가물이에요.
SAVEGENERICSV(SV **psv) — 이 매크로는 SV * 변수의 값을 현재 pseudo block이 완료될 때 이전 상태로 복원하도록 배열해요. 호출 시점에 *psv에 저장된 포인터는 refcount 증가되어 save stack에 저장돼요. 나중에 현재 pseudo-block이 완료되면 *psv에 저장된 값은 refcount 감소되고, savestack에서 이전 값이 복원되며 이것도 refcount 감소돼요. 이는 local $sv의 C 등가물이에요.
SAVEFREESV(SV *sv) — sv의 refcount는 pseudo-block이 끝날 때 감소돼요. 이는 지연된 SvREFCNT_dec를 위한 메커니즘이라는 점에서 sv_2mortal과 비슷해요. 하지만 sv_2mortal은 sv의 수명을 다음 문장의 시작까지 연장하는 반면, SAVEFREESV는 둘러싸는 스코프의 끝까지 연장해요. 이 수명들은 크게 다를 수 있어요. SAVEMORTALIZESV와도 비교하세요.
SAVEMORTALIZESV(SV *sv) — SAVEFREESV와 같지만, 참조 카운트를 감소시키는 대신 현재 스코프 끝에서 sv를 mortalize해요. 이는 보통 현재 살아있는 스코프를 호출한 문장이 실행을 마칠 때까지 sv를 살려두는 효과가 있어요.
SAVEFREEOP(OP *op) — OP *는 pseudo-block이 끝날 때 op_free()됩니다.
SAVEFREEPV(p) — p가 가리키는 메모리 청크는 현재 pseudo-block이 끝날 때 Safefree()됩니다.
SAVEFREERCPV(char *pv) — rcpv_new() 호출로 생성된 char *가 현재 pseudo-block이 끝날 때 rcpv_free()되도록 보장해요. 이는 SAVEFREESV()의 RCPV 등가물이에요.
SAVECLEARSV(SV *sv) — pseudo-block이 끝날 때 sv에 해당하는 현재 scratchpad의 슬롯을 지워요.
SAVEDELETE(HV *hv, char *key, I32 length) — pseudo-block이 끝날 때 hv의 키 key가 삭제돼요. key가 가리키는 문자열은 Safefree()됩니다. 짧은 수명 저장소에 key가 있다면 대응하는 문자열을 다음과 같이 재할당할 수 있어요:
SAVEDELETE(PL_defstash, savepv(tmpbuf), strlen(tmpbuf));
SAVEDESTRUCTOR(DESTRUCTORFUNC_NOCONTEXT_t f, void *p) — pseudo-block이 끝날 때 함수 f가 유일한 인자 p(NULL일 수 있음)로 호출돼요.
SAVEDESTRUCTOR_X(DESTRUCTORFUNC_t f, void *p) — pseudo-block이 끝날 때 함수 f가 (있으면) 암시적 컨텍스트 인자와 p(NULL일 수 있음)로 호출돼요. 현재 pseudo-block의 끝은 현재 문장의 끝보다 훨씬 나중일 수 있다는 점에 유의하세요. MORTALSVFUNC_X() 매크로를 살펴보세요.
MORTALSVFUNC_X(SVFUNC_t f, SV *sv) — 현재 문장의 끝에서 함수 f가 (있으면) 암시적 컨텍스트 인자와 sv(NULL일 수 있음)로 호출돼요. 파괴자 함수의 파라미터 인자는 관련된 SAVEDESTRUCTOR_X()와 달리 NULL이거나 SV*여야 한다는 점을 알아두세요. 현재 문장의 끝은 현재 pseudo-block의 끝보다 훨씬 빨리 올 수 있어요. SAVEDESTRUCTOR_X() 매크로를 살펴보세요.
MORTALDESTRUCTOR_SV(SV *coderef, SV *args) — 현재 문장의 끝에서 coderef에 담긴 Perl 함수가 args로 제공된 인자(있으면)로 호출돼요. args 파라미터가 어떻게 처리되는지에 대한 자세한 내용은 mortal_destructor_sv() 문서를 참조하세요. 현재 문장의 끝은 현재 pseudo-block의 끝보다 훨씬 빨리 올 수 있어요. 현재 pseudo block의 끝에서 perl 함수를 호출하려면 SAVEDESTRUCTOR_X() API를 사용해야 하는데, Perl 함수를 호출할 C 래퍼를 만들어야 해요.
SAVESTACK_POS() — Perl 내부 스택의 현재 오프셋(SP 참조)이 pseudo-block이 끝날 때 복원돼요.
다음 API 목록은 함수를 담고 있으므로 수정 가능한 데이터에 대한 포인터를 명시적으로 제공해야 해요 (C 포인터나 Perlish GV *). 위 매크로가 int를 받는 곳에 유사 함수는 int *를 받아요. 위의 다른 매크로는 구현 함수가 있지만, 매크로 자체를 쓰는 게 아마 가장 좋아요.
SV* save_scalar(GV *gv) — Perl 코드 local $gv와 동등해요.
AV* save_ary(GV *gv), HV* save_hash(GV *gv) — save_scalar과 비슷하지만 @gv와 %gv를 로컬라이즈해요.
void save_item(SV *item) — SV의 현재 값을 복제해요. 현재 ENTER/LEAVE pseudo-block에서 빠져나갈 때 SV의 값이 저장된 값을 사용해 복원돼요. 매직은 처리하지 않아요. 매직이 영향받으면 save_scalar을 쓰세요.
SV* save_svref(SV **sptr) — save_scalar과 비슷하지만 SV *를 복원해요.
void save_aptr(AV **aptr), void save_hptr(HV **hptr) — save_svref과 비슷하지만 AV *와 HV *를 로컬라이즈해요.
Alias 모듈은 호출자 스코프 내에서 기본 타입의 지역화를 구현해요. 둘러싸는 스코프에서 무언가를 로컬라이즈하는 방법에 관심이 있는 사람도 그걸 살펴봐야 해요.
서브루틴 (Subroutines)
XSUBs와 인자 스택
XSUB 메커니즘은 Perl 프로그램이 C 서브루틴에 접근하는 간단한 방법이에요. XSUB 루틴은 Perl 프로그램의 인자를 담고 있는 스택과 Perl 데이터 구조를 C 등가물로 매핑하는 방법을 가져요.
스택 인자는 ST(n) 매크로로 접근할 수 있으며, n번째 스택 인자를 반환해요. 인자 0은 Perl 서브루틴 호출에서 넘겨진 첫 인자예요. 이 인자들은 SV*이며 SV*가 사용되는 곳 어디든 쓸 수 있어요.
대부분의 경우 C 루틴의 출력은 RETVAL과 OUTPUT 지시어로 처리할 수 있어요. 하지만 인자 스택이 모든 반환값을 처리하기에 충분히 길지 않은 경우도 있어요. 예시는 POSIX tzname() 호출인데, 인자를 받지 않지만 현지 시간대의 표준·여름 시간 약어 두 개를 반환해요.
이 상황을 처리하려면 PPCODE 지시어를 사용하고 다음 매크로로 스택을 확장해요:
EXTEND(SP, num);
SP는 스택 포인터의 로컬 복사본을 나타내는 매크로이고, num은 스택이 확장되어야 할 요소 수예요.
이제 스택에 공간이 있으니 PUSHs 매크로로 값을 스택에 밀어 넣을 수 있어요. 밀어 넣은 값은 종종 "mortal"이어야 해요 ("Reference Counts and Mortality"):
PUSHs(sv_2mortal(newSViv(an_integer)))
PUSHs(sv_2mortal(newSVuv(an_unsigned_integer)))
PUSHs(sv_2mortal(newSVnv(a_double)))
PUSHs(sv_2mortal(newSVpv("Some String",0)))
/* Although the last example is better written as the more
* efficient: */
PUSHs(newSVpvs_flags("Some String", SVs_TEMP))
이제 tzname을 호출하는 Perl 프로그램의 두 값은 다음과 같이 할당돼요:
($standard_abbrev, $summer_abbrev) = POSIX::tzname;
스택에 값을 밀어 넣는 대체(그리고 아마 더 간단한) 방법은 매크로를 사용하는 것이에요:
XPUSHs(SV*)
이 매크로는 필요하면 자동으로 스택을 조정해요. 따라서 스택을 확장하기 위해 EXTEND를 호출할 필요가 없어요.
이 문서의 이전 버전의 제안에도 불구하고 (X)PUSH[iunp] 매크로는 여러 결과를 반환하는 XSUB에 적합하지 않아요. 그런 용도에는 위의 (X)PUSHs 매크로를 고수하거나 새 m(X)PUSH[iunp] 매크로를 사용하세요; "Putting a C value on Perl stack" 참조.
자세한 내용은 perlxs와 perlxstut를 참조하세요.
XSUBs로 Autoloading
AUTOLOAD 루틴이 XSUB이면 Perl 서브루틴과 마찬가지로 Perl은 자동 로드된 서브루틴의 정규화된 이름을 XSUB 패키지의 $AUTOLOAD 변수에 넣어요.
또한 XSUB 자체의 특정 필드에도 같은 정보를 넣어요:
HV *stash = CvSTASH(cv);
const char *subname = SvPVX(cv);
STRLEN name_length = SvCUR(cv); /* in bytes */
U32 is_utf8 = SvUTF8(cv);
SvPVX(cv)는 패키지를 포함하지 않은 서브 이름 자체만 담아요. UNIVERSAL이나 그 슈퍼클래스 중 하나의 AUTOLOAD 루틴의 경우, 존재하지 않는 패키지에 메서드 호출을 하는 동안 CvSTASH(cv)가 NULL을 반환해요.
참고: $AUTOLOAD 설정은 5.6.1에서 동작을 멈췄는데, XS AUTOLOAD 서브를 전혀 지원하지 않았어요. Perl 5.8.0은 XSUB 자체의 필드 사용을 도입했어요. Perl 5.16.0은 $AUTOLOAD 설정을 복원했어요. 5.8-5.14를 지원해야 한다면 XSUB의 필드를 사용하세요.
C 프로그램에서 Perl 루틴 호출
C 프로그램에서 Perl 서브루틴을 호출하는 데 사용할 수 있는 루틴이 네 개 있어요:
I32 call_sv(SV*, I32);
I32 call_pv(const char*, I32);
I32 call_method(const char*, I32);
I32 call_argv(const char*, I32, char**);
가장 자주 사용되는 루틴은 call_sv예요. SV* 인자는 호출될 Perl 서브루틴의 이름이나 서브루틴에 대한 참조를 담고 있어요. 두 번째 인자는 서브루틴이 호출되는 컨텍스트, 서브루틴에 인자가 넘겨지는지 여부, 오류가 어떻게 잡혀야 하는지, 반환값을 어떻게 처리할지를 제어하는 플래그로 구성돼요.
네 루틴 모두 Perl 스택에서 서브루틴이 반환한 인자 수를 반환해요.
이 루틴들은 Perl v5.6.0 이전에는 perl_call_sv 등으로 불렸지만, 이제 그 이름은 deprecated이며 호환성을 위해 같은 이름의 매크로가 제공돼요.
이 루틴들(call_argv 제외)을 사용할 때 프로그래머는 Perl 스택을 조작해야 해요. 다음 매크로와 함수가 포함돼요:
dSP
SP
PUSHMARK()
PUTBACK
SPAGAIN
ENTER
SAVETMPS
FREETMPS
LEAVE
XPUSH*()
POP*()
C에서 Perl로의 호출 규칙에 대한 자세한 설명은 perlcall 참조.
C 값을 Perl 스택에 놓기
많은 opcode (내부 perl 스택 머신의 기본 연산)가 SV*를 스택에 놓아요. 하지만 최적화로 대응하는 SV는 (보통) 매번 재생성되지 않아요. opcode는 특별히 할당된 SV(target들)를 재사용해요 (결과적으로 지속적으로 free/생성되지 않아요).
각 target은 한 번만 생성되고 (단 아래 "Scratchpads and recursion" 참조), opcode가 스택에 정수, double, 문자열을 놓아야 할 때 그 target의 대응 부분을 설정하고 target을 스택에 놓아요.
이 target을 스택에 놓는 매크로는 PUSHTARG이고, 일부 opcode에서 직접 사용되며 더 많은 곳에서 (X)PUSH[iunp]를 통해 간접적으로 사용돼요.
target이 재사용되므로 여러 값을 스택에 밀어 넣을 때 조심해야 해요. 다음 코드는 생각한 대로 동작하지 않아요:
XPUSHi(10);
XPUSHi(20);
이것은 "TARG를 10으로 설정하고, TARG에 대한 포인터를 스택에 밀어 넣고; TARG를 20으로 설정하고, TARG에 대한 포인터를 스택에 밀어 넣는다"로 번역돼요. 연산이 끝나면 스택은 10과 20을 담지 않고, 사실 20으로 설정한 TARG에 대한 포인터 두 개를 담아요.
여러 다른 값을 밀어 넣어야 한다면 (X)PUSHs 매크로를 사용하거나 TARG를 사용하지 않는 새 m(X)PUSH[iunp] 매크로를 사용해야 해요. (X)PUSHs 매크로는 그냥 SV*를 스택에 밀어 넣는데, "XSUBs and the Argument Stack"에서 언급했듯 종종 "mortal"이어야 해요. 새 m(X)PUSH[iunp] 매크로는 ((X)PUSHmortal로) 새 mortal을 만들어 스택에 밀어 넣고(mXPUSH[iunp] 매크로의 경우 필요하면 확장) 그런 다음 값을 설정해 이 작업을 조금 더 쉽게 해요. 따라서 위 예시를 "고치기" 위해 이렇게 쓰는 대신:
XPUSHs(sv_2mortal(newSViv(10)))
XPUSHs(sv_2mortal(newSViv(20)))
그냥 이렇게 쓸 수 있어요:
mXPUSHi(10)
mXPUSHi(20)
관련 참고로, (X)PUSH[iunp]를 실제로 쓴다면 변수 선언에 dTARG가 필요해서 *PUSH* 매크로가 로컬 변수 TARG를 사용할 수 있게 해야 해요. perlapi의 "dTARGET" 및 dXSTARG 참조.
TARG에 주의하세요: OP나 dXSTARG용 XSUB에 대한 각 비-재귀 호출은 매 호출마다 TARG에 같은 SV를 사용하므로, 이전 OP/XSUB 호출의 결과가 여전히 TARG에 있을 수 있어 SV가 원래 그대로라고 가정할 수 없어요.
PV의 캐시된 숫자 버전이나 캐시된 UTF-8 길이 같은 다른 상태도 설정될 수 있어요. 직접 SV를 조작해 최적화하는 대신 정상 sv_set*() API를 사용하면 이런 문제를 피할 수 있어요.
OA_TARGLEX 최적화를 구현하는 OP의 경우 TARG가 다른 면에서 특별할 수 있고, 참조가 있을 수도, blessed되거나 tied되거나 다른 방식으로 매직일 수도 있어요.
참조를 반환하는 데 TARG를 사용하면 안 돼요. 그 참조는 OP나 XSUB가 다음에 비-재귀적으로 실행될 때까지, 또는 perl이 종료될 때까지 살아 있기 때문이에요.
Scratchpad
문제는 opcode의 target인 SV들이 언제 생성되는지 남아 있어요. 답은 현재 유닛(서브루틴 또는 파일, 서브루틴 밖의 문장 opcode의 경우)이 컴파일될 때 생성된다는 거예요. 이 기간 동안 특별한 익명 Perl 배열이 생성되는데, 현재 유닛의 scratchpad라고 불러요.
scratchpad는 현재 유닛의 lexical이며 opcode의 target인 SV들을 보관해요. 이 문서의 이전 버전은 플래그를 보고 SV가 scratchpad에 살고 있다고 추론할 수 있다고 말했어요: lexical은 SVs_PADMY가 설정되고, target은 SVs_PADTMP가 설정된다고. 하지만 이것은 완전히 사실이 된 적이 없어요. SVs_PADMY는 더 이상 어떤 pad에도 살지 않는 변수에 설정될 수 있어요. target은 SVs_PADTMP가 설정되지만, 어떤 pad에도 살지 않았지만 target처럼 행동하는 변수에도 설정될 수 있어요. perl 5.21.5부터 SVs_PADMY 플래그는 더 이상 사용되지 않고 0으로 정의돼요. SvPADMY()는 이제 SVs_PADTMP가 없는 모든 것에 대해 참을 반환해요.
OP와 target 사이의 대응은 1:1이 아니에요. 유닛의 컴파일 트리의 다른 OP들이, 임시 변수의 기대 수명과 충돌하지 않는다면 같은 target을 사용할 수 있어요.
Scratchpad와 재귀
실제로 컴파일된 유닛이 scratchpad AV에 대한 포인터를 담고 있다는 것은 100% 사실이 아니에요. 사실 (초기에) 1-요소 AV에 대한 포인터를 담고 있고, 이 요소가 scratchpad AV예요. 왜 추가 간접 레벨이 필요할까요?
답은 재귀와 아마 스레드예요. 둘 다 같은 서브루틴으로 들어가는 여러 실행 포인터를 만들 수 있어요. 서브루틴-자식이 서브루틴-부모를 위한 temporaries를 덮어쓰지 않으려면(그 수명은 자식을 호출한 호출을 덮음) 부모와 자식은 다른 scratchpad를 가져야 해요. (그리고 lexical은 어쨌든 별개여야 해요!)
따라서 각 서브루틴은 scratchpad 배열(길이 1)과 함께 태어나요. 서브루틴에 들어갈 때마다 재귀의 현재 깊이가 이 배열의 길이보다 크지 않은지 확인하고, 크면 새 scratchpad를 만들어 배열에 넣어요.
이 scratchpad의 target들은 undef지만 이미 올바른 플래그로 표시돼 있어요.
메모리 할당 (Memory Allocation)
할당 (Allocation)
Perl API 함수와 함께 사용하려는 모든 메모리는 이 절에서 설명하는 매크로로 조작해야 해요. 이 매크로들은 perl 내부에서 사용되는 실제 malloc 구현 간의 차이에 필요한 투명성을 제공해요. Newx, Renew, Safefree와 libc 등가물 malloc, realloc, free 사이에서 포인터를 절대 넘기지 마세요. 같은 메모리 풀이나 할당자가 아니에요. 즉시 또는 지연된 SEGV가 발생하거나, 미묘한 메모리 누수나 미묘한 힙 손상이 발생해요.
다음 세 매크로가 초기 메모리 할당에 사용돼요:
Newx(pointer, number, type);
Newxc(pointer, number, type, cast);
Newxz(pointer, number, type);
첫 인자 pointer는 새로 할당된 메모리를 가리킬 변수의 이름이어야 해요.
두 번째·세 번째 인자 number와 type은 지정된 타입의 데이터 구조체를 몇 개 할당할지 지정해요. type 인자는 sizeof에 넘겨져요. Newxc의 마지막 인자 cast는 pointer 인자가 type 인자와 다를 때 사용해야 해요.
Newx와 Newxc 매크로와 달리 Newxz 매크로는 memzero를 호출해 새로 할당된 메모리를 모두 0으로 만들어요.
재할당 (Reallocation)
Renew(pointer, number, type);
Renewc(pointer, number, type, cast);
Safefree(pointer)
이 세 매크로는 메모리 버퍼 크기를 바꾸거나 더 이상 필요 없는 메모리 조각을 해제하는 데 사용돼요. Renew와 Renewc의 인자는 "magic cookie" 인자가 필요 없다는 점을 제외하면 New와 Newc와 일치해요.
이동 (Moving)
Move(source, dest, number, type);
Copy(source, dest, number, type);
Zero(dest, number, type);
이 세 매크로는 이전에 할당된 메모리를 이동, 복사, 또는 0으로 만들기 위해 사용돼요. source와 dest 인자는 소스와 대상 시작점을 가리켜요. Perl은 type 데이터 구조체 크기(sizeof 함수 사용)의 number 인스턴스를 이동, 복사, 0화해요.
PerlIO
Perl의 가장 최근 개발 릴리스는 Perl의 "정상" 표준 I/O 제품군에 대한 의존을 제거하고 다른 stdio 구현이 사용되도록 실험해 왔어요. 이는 Perl이 컴파일된 어떤 stdio 구현이든 호출하는 새 추상화 계층을 만드는 것을 수반해요. 모든 XSUB는 이제 PerlIO 추상화 계층의 함수를 사용해야 하며 어떤 종류의 stdio가 사용되고 있는지 가정하지 않아야 해요.
PerlIO 추상화에 대한 완전한 설명은 perlapio 참조.
컴파일된 코드 (Compiled code)
코드 트리 (Code tree)
여기서 Perl이 코드를 변환하는 내부 형태를 설명해요. 간단한 예로 시작해요:
$a = $b + $c;
이것은 이와 비슷한 트리로 변환돼요:
assign-to
/ \
+ $a
/ \
$b $c
(하지만 조금 더 복잡해요.) 이 트리는 Perl이 코드를 파싱한 방식을 반영하지만 실행 순서와는 아무 관련이 없어요. 트리의 노드들을 통과하는 추가 "스레드"가 있어 노드의 실행 순서를 보여줘요. 위의 단순화된 예에서 이렇게 보여요:
$b ---> $c ---> + ---> $a ---> assign-to
하지만 $a = $b + $c의 실제 컴파일 트리에서는 다르답니다: 일부 노드는 최적화되어 사라집니다. 결과적으로 실제 트리가 단순화된 예보다 더 많은 노드를 포함하지만, 실행 순서는 우리 예시와 같아요.
트리 검사 (Examining the tree)
perl을 디버깅용으로 컴파일했다면(보통 Configure 커맨드라인의 -DDEBUGGING으로), Perl 커맨드라인에 -Dx를 지정해 컴파일된 트리를 검사할 수 있어요. 출력은 노드당 여러 줄이고, $b+$c에 대해서는 이렇게 보여요:
5 TYPE = add ===> 6
TARG = 1
FLAGS = (SCALAR,KIDS)
{
TYPE = null ===> (4)
(was rv2sv)
FLAGS = (SCALAR,KIDS)
{
3 TYPE = gvsv ===> 4
FLAGS = (SCALAR)
GV = main::b
}
}
{
TYPE = null ===> (5)
(was rv2sv)
FLAGS = (SCALAR,KIDS)
{
4 TYPE = gvsv ===> 5
FLAGS = (SCALAR)
GV = main::c
}
}
이 트리는 5개의 노드를 가지며(TYPE 지정자마다 하나), 그중 3개만 최적화되지 않았어요(왼쪽 열의 숫자마다 하나). 주어진 노드의 직접 자식은 같은 들여쓰기 수준의 {} 쌍에 해당하므로, 이 목록은 다음 트리에 해당해요:
add
/ \
null null
| |
gvsv gvsv
실행 순서는 ===> 표시로 나타나므로 3 4 5 6이에요(노드 6은 위 목록에 포함되지 않음), 즉 gvsv gvsv add whatever.
이 각 노드는 op, Perl 코어 내부의 기본 연산을 나타내요. 각 연산을 구현하는 코드는 pp.c* 파일에서 찾을 수 있어요. gvsv 타입의 op를 구현하는 함수는 pp_gvsv이고, 이렇게 계속돼요. 위 트리가 보여주듯 다른 op들은 자식 수가 달라요: 예상대로 add는 이진 연산자이므로 두 자식을 가져요. 다양한 자식 수를 수용하기 위해 다양한 종류의 op 데이터 구조체가 있고, 그것들은 서로 다른 방식으로 연결돼요.
가장 간단한 종류의 op 구조체는 OP예요: 자식이 없어요. 단항 연산자 UNOP는 자식이 하나이고 op_first 필드가 그것을 가리켜요. 이진 연산자(BINOP)는 op_first와 op_last 필드를 모두 가져요. 가장 복잡한 종류의 op는 LISTOP이며 임의의 수의 자식을 가져요. 이 경우 첫 자식은 op_first가, 마지막 자식은 op_last가 가리켜요. 그 사이의 자식은 첫 자식에서 마지막까지 OpSIBLING 포인터를 반복적으로 따라가며 찾을 수 있어요 (아래 참조).
다른 op 종류도 있어요: PMOP는 정규식을 보유하고 자식이 없으며, LOOP는 자식이 있거나 없을 수 있어요. op_children 필드가 0이 아니면 LISTOP처럼 행동해요. 복잡하게도, UNOP가 최적화 후 실제로 null op이면 ("Compile pass 2: context propagation" 참조) 여전히 이전 타입에 따라 자식을 가져요.
마지막으로 LOGOP, 논리 op가 있어요. LISTOP처럼 하나 이상의 자식을 가지지만 op_last 필드가 없어요: 그래서 op_first를 따라간 다음 OpSIBLING 체인 자체를 따라가 마지막 자식을 찾아야 해요. 그 대신 op_other 필드가 있는데, 아래 설명하는 op_next 필드와 비슷하고 대체 실행 경로를 나타내요. and, or, ? 같은 연산자가 LOGOP이에요. 일반적으로 op_other는 LOGOP의 직접 자식 중 어떤 것도 가리키지 않을 수 있다는 점에 유의하세요.
버전 5.21.2부터 실험적 define -DPERL_OP_PARENT로 빌드된 perl은 각 op에 추가 boolean 플래그 op_moresib를 추가해요. 설정되지 않으면 이 op가 OpSIBLING 체인의 마지막 op임을 나타내요. 이는 마지막 형제의 op_sibling 필드를 해제해 부모 op를 다시 가리키게 해요. 이 빌드에서 그 필드는 결합 역할을 반영하도록 op_sibparent로 이름이 바뀌어요. OpSIBLING(o) 매크로는 이 특별한 행동을 감싸고 마지막 형제에서 항상 NULL을 반환해요. 이 빌드에서 op_parent(o) 함수로 어떤 op의 부모도 찾을 수 있어요. 따라서 앞으로 호환하려면 op_sibling을 직접 접근하는 대신 항상 OpSIBLING(o) 매크로를 사용해야 해요.
트리를 검사하는 또 다른 방법은 B::Concise 같은 컴파일러 백엔드 모듈을 사용하는 것이에요.
컴파일 패스 1: check 루틴
트리는 yacc 코드가 인식하는 구성을 공급하는 동안 컴파일러가 만들기 시작해요. yacc가 bottom-up으로 동작하므로 perl 컴파일의 첫 패스도 그렇게 해요.
이 패스가 perl 개발자에게 흥미로운 이유는 이 패스에서 일부 최적화가 수행될 수 있기 때문이에요. 이것은 소위 "check 루틴"에 의한 최적화예요. 노드 이름과 대응하는 check 루틴의 대응은 opcode.pl에 설명돼 있어요 (이 파일을 수정하면 make regen_headers를 실행하는 것을 잊지 마세요).
check 루틴은 노드가 실행-순서 스레드를 제외하고 완전히 구성되었을 때 호출돼요. 이 시점에는 현재 구성 중인 노드로의 역링크가 없으므로 최상위 노드에 어떤 연산이든 할 수 있어요, 그것을 해제하거나 그 위/아래에 새 노드를 만드는 것도 포함해요.
check 루틴은 트리에 삽입되어야 할 노드를 반환해요 (최상위 노드가 수정되지 않았다면 check 루틴은 그 인자를 반환해요).
관례상 check 루틴은 ck_* 이름을 가져요. 보통 new*OP 서브루틴(또는 convert)에서 호출돼요 (이것들은 다시 perly.y에서 호출돼요).
컴파일 패스 1a: 상수 폴딩
check 루틴이 호출된 직후 반환된 노드가 컴파일-시간 실행 가능한지 확인돼요. 가능하면(값이 상수로 판단되면) 즉시 실행되고, 대응하는 하위 트리의 "반환 값"을 가진 constant 노드가 대신 대체돼요. 하위 트리는 삭제돼요.
상수 폴딩이 수행되지 않았다면 실행-순서 스레드가 생성돼요.
컴파일 패스 2: 컨텍스트 전파
컴파일 트리의 일부에 대한 컨텍스트가 알려지면 트리 아래로 전파돼요. 이 시점에서 컨텍스트는 5개 값을 가질 수 있어요(런타임 컨텍스트의 2개 대신): void, boolean, scalar, list, lvalue. 패스 1과 달리 이 패스는 위에서 아래로 처리돼요: 노드의 컨텍스트가 그 자식들의 컨텍스트를 결정해요.
추가 컨텍스트 의존 최적화가 이때 수행돼요. 이 시점에서 컴파일 트리는 역참조("스레드" 포인터를 통해)를 포함하므로 노드를 지금 free()할 수 없어요. 이 단계에서 최적화되어 사라진 노드를 허용하기 위해 그런 노드는 free() 대신 null()화돼요 (즉 타입이 OP_NULL로 바뀜).
컴파일 패스 3: peephole 최적화
서브루틴(또는 eval이나 파일)의 컴파일 트리가 생성된 후 코드에 대한 추가 패스가 수행돼요. 이 패스는 top-down도 bottom-up도 아니고 실행 순서로 돼요 (조건문에 대한 추가 복잡함 포함). 이 단계에서 수행되는 최적화는 패스 2와 같은 제한을 받아요.
Peephole 최적화는 전역 변수 PL_peepp가 가리키는 함수를 호출해 수행돼요. 기본적으로 PL_peepp는 그냥 전역 변수 PL_rpeepp가 가리키는 함수를 호출해요. 기본적으로 그것은 실행-순서 op 체인을 따라 몇 가지 기본 op 수정과 최적화를 수행하고, 각 op 측 체인(조건문에서 발생)에 대해 PL_rpeepp를 재귀 호출해요. 확장은 서브루틴별 또는 재귀 단계에 훅하여 추가 최적화나 수정을 제공할 수 있어요. 예:
static peep_t prev_peepp;
static void my_peep(pTHX_ OP *o)
{
/* custom per-subroutine optimisation goes here */
prev_peepp(aTHX_ o);
/* custom per-subroutine optimisation may also go here */
}
BOOT:
prev_peepp = PL_peepp;
PL_peepp = my_peep;
static peep_t prev_rpeepp;
static void my_rpeep(pTHX_ OP *first)
{
OP *o = first, *t = first;
for(; o = o->op_next, t = t->op_next) {
/* custom per-op optimisation goes here */
o = o->op_next;
if (!o || o == t) break;
/* custom per-op optimisation goes AND here */
}
prev_rpeepp(aTHX_ orig_o);
}
BOOT:
prev_rpeepp = PL_rpeepp;
PL_rpeepp = my_rpeep;
플러그 가능한 runops
컴파일 트리는 runops 함수에서 실행돼요. run.c와 dump.c에 두 개의 runops 함수가 있어요. Perl_runops_debug는 DEBUGGING과 함께 사용되고 Perl_runops_standard는 그 외에 사용돼요. 컴파일 트리 실행을 세밀하게 제어하려면 자신만의 runops 함수를 제공할 수 있어요.
기존 runops 함수 중 하나를 복사해서 필요에 맞게 바꾸는 게 아마 최선이에요. 그런 다음 XS 파일의 BOOT 섹션에 다음 줄을 추가하세요:
PL_runops = my_runops;
이 함수는 프로그램을 가능한 한 빨리 실행하도록 최대한 효율적이어야 해요.
컴파일-시간 스코프 훅
perl 5.14부터 Perl_blockhook_register로 컴파일-시간 어휘 스코프 메커니즘에 훅할 수 있어요. 이렇게 사용해요:
static void my_start_hook(pTHX_ int full);
static BHK my_hooks;
BOOT:
BhkENTRY_set(&my_hooks, bhk_start, my_start_hook);
Perl_blockhook_register(aTHX_ &my_hooks);
이것은 모든 어휘 스코프 컴파일이 시작될 때 my_start_hook이 호출되도록 배열해요. 사용 가능한 훅은:
void bhk_start(pTHX_ int full) — 새 어휘 스코프를 시작한 직후 호출돼요. if ($x) { ... } 같은 Perl 코드는 두 스코프를 만들고, 첫 번째는 (에서 시작하며 full == 1, 두 번째는 {에서 시작하며 full == 0이에요. 둘 다 }에서 끝나므로 start와 pre/post_end 호출이 일치해요. 이 훅이 save stack에 밀어 넣은 것은 스코프가 끝나기 직전(pre_와 post_end 훅 사이)에 꺼내져요.
void bhk_pre_end(pTHX_ OP **o) — 어휘 스코프 끝에서, 스택을 되감기 직전에 호출돼요. o는 스코프를 나타내는 optree의 루트예요. 필요하면 OP를 교체할 수 있도록 이중 포인터예요.
void bhk_post_end(pTHX_ OP **o) — 어휘 스코프 끝에서, 스택을 되감은 직후에 호출돼요. o는 위와 같아요. save stack에 문자열 eval을 호출하는 무언가가 있으면 pre_와 post_end 호출이 중첩될 수 있다는 점에 유의하세요.
void bhk_eval(pTHX_ OP *const o) — eval STRING, do FILE, require, use 컴파일을 시작하기 직전, eval이 설정된 후에 호출돼요. o는 eval을 요청한 OP이며 보통 OP_ENTEREVAL, OP_DOFILE, OP_REQUIRE일 거예요.
훅 함수가 있으면 그것을 넣을 BHK 구조체가 필요해요. 일단 등록되면 해제할 방법이 없으므로 정적으로 할당하는 게 가장 좋아요. 함수 포인터는 BhkENTRY_set 매크로로 이 구조체에 넣어야 하는데, 이 매크로는 어떤 항목이 유효한지 나타내는 플래그도 설정해요. 어떤 이유로 BHK를 동적으로 할당해야 한다면 시작 전에 0으로 초기화하세요.
일단 등록되면 이 훅을 끌 메커니즘이 없으므로 필요하면 직접 해야 해요. %^H의 항목이 아마 가장 좋은데, 효과가 어휘적으로 스코프되기 때문이에요. 하지만 BhkDISABLE과 BhkENABLE 매크로로 항목을 임시로 켜고 끄는 것도 가능해요. 또한 일반적으로 확장이 로드되기 전에 최소한 하나의 스코프가 열렸을 것이므로, 일치하는 start가 없는 pre/post_end 쌍을 보게 될 것이라는 점도 알아야 해요.
dump 함수로 내부 데이터 구조체 검사
디버깅을 돕기 위해 소스 파일 dump.c에는 내부 데이터 구조체의 포맷된 출력을 생성하는 여러 함수가 있어요.
가장 흔히 사용되는 함수는 Perl_sv_dump이며 SVs, AVs, HVs, CVs를 덤프하는 데 사용돼요. Devel::Peek 모듈은 Perl 공간에서 디버깅 출력을 생성하기 위해 sv_dump을 호출하므로, 그 모듈 사용자는 그 형식에 이미 익숙할 거예요.
Perl_op_dump은 OP 구조체나 그 파생물을 덤프하는 데 사용할 수 있고 perl -Dx와 비슷한 출력을 생성해요. 사실 Perl_dump_eval은 -Dx와 정확히 동일하게 평가되는 코드의 주 루트를 덤프해요.
다른 유용한 함수는 GV를 op 트리로 바꾸는 Perl_dump_sub, 다음과 같이 패키지의 모든 서브루틴에 Perl_dump_sub을 호출하는 Perl_dump_packsubs가 있어요. (다행히도 이것들은 모두 xsub이므로 op 트리가 없어요):
(gdb) print Perl_dump_packsubs(PL_defstash)
SUB attributes::bootstrap = (xsub 0x811fedc 0)
SUB UNIVERSAL::can = (xsub 0x811f50c 0)
SUB UNIVERSAL::isa = (xsub 0x811f304 0)
SUB UNIVERSAL::VERSION = (xsub 0x811f7ac 0)
SUB DynaLoader::boot_DynaLoader = (xsub 0x805b188 0)
그리고 stash의 모든 서브루틴과 주 루트의 op 트리를 덤프하는 Perl_dump_all도 있어요.
인터프리터 실행 추적
소스 코드 전반에 걸쳐 다음과 같은 문장을 보게 될 거예요:
DEBUG_L(PerlIO_printf(Perl_debug_log, "%s\n", msg));
DEBUG_l(deb("Entering new RUNOPS level: depth=%d\n", depth));
DEBUG_r(if (!PL_colorset) reginitcolors());
DEBUG_y(sv_dump(sv));
각 문장은 가장 바깥 괄호 쌍 안의 코드를 다음 특정 상황에서만 실행해요:
perl이 -DDEBUGGING으로 컴파일되어야 해요. — 그렇지 않으면 각 문장은 아무것도 없는 것으로 컴파일돼요.
$^D 변수에 적절한 비트가 설정되어야 해요. — 실행하려면 DEBUG_y는 y 비트가 필요해요. 마찬가지로 L, l, r 비트가 있어요.
각 비트는 인터프리터 실행의 다른 측면을 위한 거예요. L은 로케일 처리, l은 컨텍스트(루프) 스택 처리, r은 정규식, y는 tr///용이에요.
가능한 모든 비트와 그 의미는 perlrun의 "-Dletters" 또는 perl.h 소스의 DEBUG_L_FLAG 같은 곳에 나열돼 있어요.
이것들은 보통 인터프리터 실행을 시작한 커맨드라인에서 -D... 옵션으로 설정하지만, Perl 프로그램이 이 변수의 값을 마음대로 바꿀 수도 있어요.
예를 들어 perl이 -DEBUGGING으로 컴파일되고 시작 시 -Dy 옵션을 전달해 켰다면, 실행이 C 언어 소스 DEBUG_y(sv_dump(sv)); 문장을 만날 때마다 sv_dump이 호출돼요. (sv_dump이 무엇을 하는지는 perlapi의 "sv_dump" 참조.)
이런 줄들은 켰을 때 문제를 찾는 데 도움이 되도록 C 코드에 전략적으로 배치되지만, -DDEBUGGING 없이는 공간을 전혀 차지하지 않아요.
다른 설정 비트에 대한 추가 장황함을 요청하는 v 수정자 비트도 있어요. 이런 문장에서:
DEBUG_Lv(PerlIO_printf(Perl_debug_log, "Entering function(%s);", ...))
PerlIO_printf는 perl이 -DDEBUGGING으로 컴파일되었고 L과 v 비트가 모두 활성화된 경우(예: -DLv 또는 -DvL...)에만 실행돼요. v의 영향을 받는 주요 비트는 몇 개뿐이고, 현재 추가 장황함 레벨은 하나뿐이에요. -DLvvv와 -LDv는 정확히 같은 일을 해요.
이 중 가장 흔히 쓰이는 형태는 이와 같아요:
DEBUG_L(PerlIO_printf(Perl_debug_log, "%s", msg));
위에서 설명한 대로 활성화되면, 이 문장은 Perl 프로그램 실행 중 문장을 만날 때마다 C 언어 문자 문자열 msg의 내용을 stderr에 표시하게 해요. Perl_debug_log 다음에는 포맷 문자열, 그리고 그 포맷에 대한 인자들이 와야 해요, 마치 printf()처럼.
내부로 deb()는 같은 일을 하지만, 이 C 문장이 실행되게 한 궁극적 원인인 Perl 소스 코드 줄을 자동으로 기록해요. 이것은 Perl 코드를 대응하는 C 변환과 연관시키는 쉬운 방법이에요. perlapi의 "deb" 참조. 소스에서 그것의 사용은 실제보다 더 널리 퍼져 있어야 해요. 패치를 환영해요!
이 문장 대부분은 본질적으로 소스에 영구히 배치되어, 필요 시(아마 먼 미래에) 켜지게 돼요. 하지만 개발 중이나 특별 상황을 처리할 때 임시 배치를 위해 예약된 비트가 하나 있어요. 필요한 것이 무엇이든 DEBUG_U("Unofficial") 문장을 추가할 수 있어요. 하지만 코드가 디버깅된 후에는 제거하거나 다른 비트를 쓰도록 변환해야 해요.
DEBUG_U_TEST와 DEBUG_Uv_TEST 매크로를 사용해 출력이 생성될지 더 세밀하게 조정하는 더 복잡한 조건을 작성할 수 있어요. 대응 종류의 디버그 문장이 표시될 때에만 참을 반환해요. 각 비트에 대한 대응 매크로가 있어요.
여러 인터프리터와 동시성이 지원되는 방법
배경과 MULTIPLICITY
Perl 인터프리터는 닫힌 상자로 간주할 수 있어요: 코드를 주입하거나 일을 시키는 API가 있지만, 자기 자신을 위한 함수도 있어요. 이것은 객체 냄새가 많이 나고, 여러 인터프리터를 가질 수 있도록 Perl을 빌드하는 방법이 있어요. 하나의 인터프리터는 C 구조체로 또는 스레드-특정 구조체 안에서 표현돼요. 이 구조체들은 모든 컨텍스트, 그 인터프리터의 상태를 담고 있어요.
주요 Perl 빌드 방식(flavor)을 제어하는 매크로는 MULTIPLICITY예요. MULTIPLICITY 빌드는 모든 인터프리터 상태를 묶는 C 구조체를 가지며, 이것이 다양한 perl 함수에 "숨겨진" 첫 인자로 전달돼요. MULTIPLICITY는 다중 스레드 perl을 가능하게 해요 (ithreads 스레딩 모델, 매크로 USE_ITHREADS와 관련).
PERL_IMPLICIT_CONTEXT는 MULTIPLICITY의 레거시 동의어예요.
비-const 데이터가 있는지 보려면 BSD(또는 GNU) 호환 nm을 사용할 수 있어요:
nm libperl.a | grep -v ' [TURtr] '
이것이 D나 d 심볼(또는 아마 C나 c)을 표시하면 비-const 데이터가 있는 거예요. grep이 제거한 심볼은 다음과 같아요: Tt는 text, 즉 코드, Rr는 읽기 전용(const) 데이터, U는
테스트 t/porting/libperl.t는 libperl.a에 대해 이런 종류의 심볼 정합성 검사를 해요.
이 모든 것은 당연히 Perl 내부 함수가 첫 인자로 어떤 종류의 구조체를 받는 서브루틴이거나, 첫 인자로 아무것도 받지 않는 서브루틴이어야 하는 방법을 요구해요. 이 두 가지 매우 다른 인터프리터 구성 방식을 가능하게 하기 위해 Perl 소스는 (다른 많은 상황에서 그렇듯) 매크로와 서브루틴 명명 규칙을 많이 사용해요.
첫 문제: 어떤 함수가 공개 API 함수이고 어떤 것이 사설일지 결정하기. 이름이 S_로 시작하는 모든 함수는 사설이에요 ("S"를 "secret" 또는 "static"으로 생각하세요). 다른 모든 함수는 "Perl_"로 시작하지만, 함수가 "Perl_"로 시작한다고 API의 일부라는 뜻은 아니에요. ("Internal Functions" 참조.) 함수가 API의 일부인지 확신하는 가장 쉬운 방법은 perlapi에서 그 항목을 찾는 것이에요. perlapi에 존재하면 API의 일부예요. 존재하지 않는데 있어야 한다고 생각하면(즉 확장에 필요하면), https://github.com/Perl/perl5/issues 에 왜 있어야 한다고 생각하는지 설명하는 이슈를 제출하세요.
두 번째 문제: 같은 서브루틴 선언과 호출이 첫 인자로 구조체를 넘기거나 아무것도 넘기지 않을 수 있도록 구문이 있어야 해요. 이를 해결하기 위해 서브루틴은 특정 방식으로 이름지어지고 선언돼요. 다음은 Perl 내부에서 사용되는 static 함수의 전형적인 시작이에요:
static void
S_incline(pTHX_ char *s)
공개 함수(즉 내부 API의 일부지만 반드시 확장에서 사용이 허가된 것은 아님)는 이렇게 시작해요:
void
Perl_sv_setiv(pTHX_ SV* dsv, IV num)
pTHX_는 (perl.h의) 인터프리터 컨텍스트 세부를 숨기는 여러 매크로 중 하나예요. THX는 경우에 따라 "thread", "this", "thingy"를 뜻해요. (그리고 조지 루카스는 관련이 없어요. :-) 첫 문자는 prototype용 'p', argument용 'a', declaration용 'd'일 수 있어서 pTHX, aTHX, dTHX와 그 변형이 있어요.
Perl이 MULTIPLICITY를 설정하는 옵션 없이 빌드되면 인터프리터 컨텍스트를 담는 첫 인자가 없어요. pTHX_ 매크로의 뒤 밑줄은 컨텍스트 인자 뒤에 다른 인자가 따라오므로 매크로 확장에 쉼표가 필요함을 나타내요. MULTIPLICITY가 정의되지 않으면 pTHX_는 무시되고 서브루틴은 추가 인자를 받도록 프로토타입되지 않아요. 뒤 밑줄 없는 매크로 형태는 추가 명시적 인자가 없을 때 사용돼요.
코어 함수가 다른 함수를 호출할 때 컨텍스트를 넘겨야 해요. 이것은 보통 매크로를 통해 숨겨져요. sv_setiv를 생각해 보세요. 이것은 이렇게 확장돼요:
#ifdef MULTIPLICITY
#define sv_setiv(a,b) Perl_sv_setiv(aTHX_ a, b)
/* can't do this for vararg functions, see below */
#else
#define sv_setiv Perl_sv_setiv
#endif
이것은 잘 동작하며, XS 작성자가 즐겁게 이렇게 쓸 수 있다는 것을 의미해요:
sv_setiv(foo, bar);
그리고 Perl이 컴파일될 수 있는 모든 모드에서 여전히 동작해요.
하지만 varargs 함수에서는 이렇게 깔끔하게 동작하지 않아요. 매크로는 인자 수가 미리 알려져 있다는 것을 암시하기 때문이에요. 대신 완전히 풀어서 첫 인자로 aTHX_를 넘기거나(Perl 코어는 Perl_warner 같은 함수에서 이렇게 하는 경향이 있어요), 컨텍스트-프리 버전을 사용해야 해요.
Perl_warner의 컨텍스트-프리 버전은 Perl_warner_nocontext라고 불리며 추가 인자를 받지 않아요. 대신 dTHX;를 해 스레드-로컬 저장소에서 컨텍스트를 얻어요. 우리는 #define warner Perl_warner_nocontext를 해서 확장이 성능 희생을 대가로 소스 호환성을 얻도록 해요. (인자 넘기는 게 스레드-로컬 저장소에서 가져오는 것보다 쌉니다.)
Perl 헤더/소스를 볼 때 [pad]THXx는 무시해도 돼요. 그것들은 엄격히 코어 내부 전용이에요. 확장과 embedder는 [pad]THX만 알면 돼요.
그래서 dTHR는 어떻게 됐나요?
dTHR는 더 오래된 스레드 모델을 지원하기 위해 perl 5.005에서 도입됐어요. 오래된 스레드 모델은 이제 컨텍스트 포인터를 전달하기 위해 THX 메커니즘을 사용하므로 dTHR은 더 이상 유용하지 않아요. Perl 5.6.0 이상은 이전 소스 호환성을 위해 여전히 갖고 있지만 no-op으로 정의돼요.
이것을 확장에서 어떻게 사용하나요?
perlclib의 "Dealing with embedded perls and threads"도 참조하세요.
Perl이 MULTIPLICITY로 빌드되면 Perl API의 어떤 함수라도 호출하는 확장은 어떻게든 초기 컨텍스트 인자를 넘겨야 해요. 까다로운 점은 Perl이 MULTIPLICITY를 활성화하지 않고 빌드되었을 때도 확장이 여전히 컴파일되도록 작성해야 한다는 거예요.
이를 위한 방법이 세 가지 있어요. 첫 번째는, 확장과의 소스 호환성을 유지하기 위한 것이며 기본값이기도 한, 쉽지만 비효율적인 방법이에요: XSUB.h가 #include될 때마다 aTHX와 aTHX_ 매크로를 컨텍스트를 반환할 함수를 호출하도록 재정의해요. 그래서 확장의 이런 것:
sv_setiv(sv, num);
MULTIPLICITY가 적용 중일 때 이렇게 번역돼요:
Perl_sv_setiv(Perl_get_context(), sv, num);
그렇지 않으면 이렇게요:
Perl_sv_setiv(sv, num);
이것을 얻기 위해 확장에서 새로 할 일은 없어요. Perl 라이브러리가 Perl_get_context()를 제공하므로 모두 그냥 동작해요.
두 번째, 더 효율적인 방법은 Foo.xs에 다음 템플릿을 사용하는 것이에요:
#define PERL_NO_GET_CONTEXT /* we want efficiency */
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
static void my_private_function(int arg1, int arg2);
static void
my_private_function(int arg1, int arg2)
{
dTHX; /* fetch context */
... call many Perl API functions ...
}
[... etc ...]
MODULE = Foo PACKAGE = Foo
/* typical XSUB */
void
my_xsub(arg)
int arg
CODE:
my_private_function(arg, 10);
확장을 쓰는 일반적인 방식에서 유일한 두 가지 변경은 Perl 헤더를 포함하기 전에 #define PERL_NO_GET_CONTEXT를 추가하고, Perl API를 호출할 모든 함수 시작에 dTHX; 선언을 추가하는 것뿐이라는 점에 유의하세요. (C 컴파일러가 그 함수들에서 선언되지 않은 식별자가 있다고 불평할 테니 어떤 함수에 필요한지 알게 될 거예요.) XSUB 자체에는 변경이 필요 없어요. XS() 매크로가 필요하면 암시적 컨텍스트를 넘기도록 올바르게 정의되기 때문이에요.
세 번째, 더욱 효율적인 방법은 Perl 내부에서 하는 방식을 흉내 내는 것이에요:
#define PERL_NO_GET_CONTEXT /* we want efficiency */
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
/* pTHX_ only needed for functions that call Perl API */
static void my_private_function(pTHX_ int arg1, int arg2);
static void
my_private_function(pTHX_ int arg1, int arg2)
{
/* dTHX; not needed here, because THX is an argument */
... call Perl API functions ...
}
[... etc ...]
MODULE = Foo PACKAGE = Foo
/* typical XSUB */
void
my_xsub(arg)
int arg
CODE:
my_private_function(aTHX_ arg, 10);
이 구현은 항상 추가 인자로 전달되므로 함수 호출로 컨텍스트를 가져올 필요가 전혀 없어요. 단순성이나 효율성의 필요에 따라 이전 두 접근을 자유롭게 섞을 수 있어요.
pTHX 뒤에 쉼표를 직접 추가하지 마세요 — 항상 명시적 인자를 받는 함수에는 밑줄이 있는 매크로 형태를, 명시적 인자가 없는 함수에는 인자 없는 형태를 사용하세요.
여러 스레드에서 perl을 호출하면 특별한 일을 해야 하나요?
한 스레드에서 인터프리터를 만들고 다른 스레드에서 호출한다면, 각 스레드에서 perl 자신의 Thread Local Storage(TLS) 슬롯이 올바르게 초기화되도록 해야 해요.
perl_alloc과 perl_clone API 함수는 방금 만든 인터프리터로 TLS 슬롯을 자동으로 설정하므로, 인터프리터가 항상 만든 같은 스레드에서 접근되고 그 스레드가 이후 다른 인터프리터를 만들거나 호출하지 않았다면 특별히 할 일이 없어요. 그렇지 않다면 그 특정 인터프리터에서 Perl API의 어떤 함수를 호출하기 전에 스레드의 TLS 슬롯을 설정해야 해요. 이는 그 스레드에서 첫 번째로 하는 일로 PERL_SET_CONTEXT 매크로를 호출해 수행돼요:
/* do this before doing anything else with some_perl */
PERL_SET_CONTEXT(some_perl);
... other Perl API calls on some_perl go here ...
(PERL_GET_CONTEXT로 항상 현재 컨텍스트를 얻을 수 있어요.)
미래 계획과 PERL_IMPLICIT_SYS
MULTIPLICITY가 인터프리터가 자기 자신에 대해 아는 모든 것을 묶어 전달하는 방법을 제공하듯, 인터프리터가 실행 중인 환경에 대해 아는 모든 것을 묶을 계획도 있어요. 이것은 PERL_IMPLICIT_SYS 매크로로 활성화돼요. 현재는 Windows에서 USE_ITHREADS와만 동작해요.
이것은 모든 시스템 호출에 추가 포인터("host" 환경이라고 함)를 제공할 수 있게 해 줘요. 이는 모든 시스템 작업이 일곱 개의 C 구조체로 나뉜 자체 상태를 유지할 수 있게 해요. 이것들은 기본 perl 실행 파일의 일반적인 시스템 호출(win32/perllib.c 참조)에 대한 얇은 래퍼이지만, 더 야심 찬 host(예: fork() 에뮬레이션을 하는)에서는 다른 인터프리터들이 실제로 다른 "프로세스"인 척 하는 데 필요한 모든 추가 작업이 여기서 수행될 거예요.
Perl 엔진/인터프리터와 host는 직교 엔티티예요. 프로세스에 인터프리터가 하나 이상, "host"가 하나 이상 있을 수 있고, 그들 사이에 자유 연관이 있을 수 있어요.
내부 함수 (Internal Functions)
외부 세계에 노출될 Perl의 모든 내부 함수는 Perl_ 접두사가 붙어, XS 함수나 Perl이 임베디드된 프로그램에서 사용되는 함수와 충돌하지 않아요. 마찬가지로 모든 전역 변수는 PL_로 시작해요. (관례상 static 함수는 S_로 시작해요.)
Perl 코어 내부(PERL_CORE 정의됨)에서는 embed.h에 있는 많은 define 덕분에 Perl_ 접두사 있든 없든 함수에 접근할 수 있어요. 확장 코드는 PERL_CORE를 설정하면 안 된다는 점에 유의하세요. 설정하면 전체 perl 내부가 노출되고, 새 perl 릴리스마다 XS가 깨질 가능성이 높아요.
embed.h 파일은 embed.pl과 embed.fnc에서 자동 생성돼요. embed.pl은 또한 내부 함수용 프로토타입 헤더 파일을 만들고, 문서와 많은 다른 부품을 생성해요. 코어에 새 함수를 추가하거나 기존 함수를 바꿀 때 ※embed.fnc※의 테이블 데이터도 바꾸는 것이 중요해요. 그 테이블의 샘플 항목은 다음과 같아요:
Apd |SV** |av_fetch |AV* ar|I32 key|I32 lval
첫 열은 플래그 집합, 두 번째 열은 반환 타입, 세 번째 열은 이름이에요. 그 이후 열은 인자예요. 플래그는 embed.fnc 상단에 문서화돼 있어요.
embed.pl이나 embed.fnc를 편집하면 embed.h 및 기타 자동 생성 파일의 재빌드를 강제하기 위해 make regen_headers를 실행해야 해요.
IV, UV, NV의 포맷 인쇄
stdio(3) 스타일 포맷 코드 %d, %ld, %f 대신 IV, UV, NV를 인쇄한다면, 이식성을 위해 다음 매크로를 사용해야 해요:
IVdf IV in decimal
UVuf UV in decimal
UVof UV in octal
UVxf UV in hexadecimal
NVef NV %e-like
NVff NV %f-like
NVgf NV %g-like
이것들은 64비트 정수와 long double을 처리해요. 예:
printf("IV is %" IVdf "\n", iv);
IVdf는 IV에 올바른 포맷이 무엇이든 확장돼요. C++로 컴파일될 경우 표준 준수를 유지하기 위해 포맷 주위에 공백이 필요하다는 점에 유의하세요.
다양한 "long double"이 있다는 점에 유의하세요: Perl은 컴파일러가 가진 무엇이든 사용해요.
포인터 주소를 인쇄한다면 %p 또는 PTR2UV()와 결합한 UVxf를 사용하세요.
SV의 포맷 인쇄
SV의 내용은 다음과 같이 SVf 포맷으로 인쇄할 수 있어요:
Perl_croak(aTHX_ "This croaked because: %" SVf "\n", SVfARG(err_msg))
여기서 err_msg는 SV예요.
모든 스칼라 타입이 인쇄 가능한 것은 아니에요. 단순 값은 확실히 가능해요: IV, UV, NV, PV 중 하나. 또한 SV가 어떤 값에 대한 참조라면 역참조되어 값이 인쇄되거나, 그 값의 타입에 대한 정보와 주소가 표시돼요. 다른 타입의 SV를 인쇄한 결과는 정의되지 않았고 인터프리터 크래시로 이어질 가능성이 높아요. NV는 %g-류 포맷으로 인쇄돼요.
C++로 컴파일될 경우 표준 준수를 유지하기 위해 SVf 주위에 공백이 필요하다는 점에 유의하세요.
UTF-8에서 인쇄되는 어떤 파일핸들이든 좋은 결과를 얻고 Wide-character 경고를 피하려면 UTF-8을 기대해야 한다는 점에 유의하세요. 일반적인 파일핸들의 한 가지 방법은 perl을 -C 파라미터로 호출하는 것이에요. (perlrun의 "-C [number/list]" 참조.)
이것으로 두 스칼라를 연결할 수 있어요:
SV *var1 = get_sv("var1", GV_ADD);
SV *var2 = get_sv("var2", GV_ADD);
SV *var3 = newSVpvf("var1=%" SVf " and var2=%" SVf,
SVfARG(var1), SVfARG(var2));
SVf_QUOTEDPREFIX는 SVf와 비슷하지만 인쇄되는 문자 수를 제한해, 인자의 처음 PERL_QUOTEDPREFIX_LEN 문자까지만 보여주고 큰따옴표로 렌더링하며 내용을 큰따옴표 문자열 이스케이프 규칙으로 이스케이프해요. 문자열이 그보다 길면 끝 따옴표 뒤에 줄임표 "..."가 붙어요. 문자열이 클래스 이름으로 가정되는 오류 메시지를 위한 것이에요.
HvNAMEf와 HvNAMEf_QUOTEDPREFIX는 SVf와 비슷하지만 HvNAME(), HvNAMELEN(), HvNAMEUTF8() 매크로를 사용해 HvNAMEfARG()에 주어진 인자에서 문자열, 길이, utf8 플래그를 추출해요. stash HV에서 클래스 이름을 직접 문자열화하기 위한 것이에요.
문자열의 포맷 인쇄
7bit NUL-종료 문자열로 바이트만 인쇄하려면 %s를 쓰면 돼요 (전부 정말 7bit라고 가정). 하지만 값이 UTF-8로 인코딩되거나 0x7F 위의 바이트(따라서 8bit)를 포함할 가능성이 있다면 UTF8f 포맷을 사용해야 해요. 그리고 파라미터로 UTF8fARG() 매크로를 사용하세요:
chr * msg;
/* U+2018: \xE2\x80\x98 LEFT SINGLE QUOTATION MARK
U+2019: \xE2\x80\x99 RIGHT SINGLE QUOTATION MARK */
if (can_utf8)
msg = "\xE2\x80\x98Uses fancy quotes\xE2\x80\x99";
else
msg = "'Uses simple quotes'";
Perl_croak(aTHX_ "The message is: %" UTF8f "\n",
UTF8fARG(can_utf8, strlen(msg), msg));
UTF8fARG의 첫 파라미터는 boolean이에요: 문자열이 UTF-8이면 1, 문자열이 네이티브 바이트 인코딩(Latin1)이면 0. 두 번째 파라미터는 인쇄할 문자열의 바이트 수예요. 세 번째이자 마지막 파라미터는 문자열의 첫 바이트에 대한 포인터예요.
UTF-8에서 인쇄되는 어떤 파일핸들이든 좋은 결과를 얻고 Wide-character 경고를 피하려면 UTF-8을 기대해야 한다는 점에 유의하세요. 일반적인 파일핸들의 한 가지 방법은 perl을 -C 파라미터로 호출하는 것이에요.
Size_t와 SSize_t의 포맷 인쇄
가장 일반적인 방법은 그것들을 UV나 IV로 캐스팅하고 이전 절처럼 인쇄하는 것이에요.
하지만 PerlIO_printf()를 쓰고 있다면 %z 길이 수정자(for siZe)를 쓰는 게 타이핑과 시각적 혼란을 덜 해요:
PerlIO_printf("STRLEN is %zu\n", len);
이 수정자는 이식 가능하지 않으므로 사용을 PerlIO_printf()로 제한해야 해요.
Ptrdiff_t, intmax_t, short 및 기타 특수 크기 포맷 인쇄
PerlIO_printf()를 쓰고 있다면 이런 특수 상황에 대한 수정자가 있어요. perlfunc의 "size" 참조.
포인터-정수 및 정수-포인터
포인터 크기가 정수 크기와 반드시 같지 않으므로 올바르게 하려면 다음 매크로를 사용하세요.
PTR2UV(pointer)
PTR2IV(pointer)
PTR2NV(pointer)
INT2PTR(pointertotype, integer)
예:
IV iv = ...;
SV *sv = INT2PTR(SV*, iv);
그리고
AV *av = ...;
UV uv = PTR2UV(av);
또한
PTR2nat(pointer) /* pointer to integer of PTRSIZE */
PTR2ul(pointer) /* pointer to unsigned long (unsafe) */
PTR2ul()은 포인터가 unsigned long보다 클 수 있으므로 안전하지 않아요.
그리고 포인터와 같은 크기의 정수에 대한 네이티브 타입을 주는 PTRV도 있어요, 예: unsigned 또는 unsigned long.
예외 처리 (Exception Handling)
XS 모듈에서 매우 기본적인 예외 처리를 하는 매크로가 몇 개 있어요. 이 매크로들을 사용하려면 XSUB.h를 포함하기 전에 NO_XSLOCKS를 정의해야 해요:
#define NO_XSLOCKS
#include "XSUB.h"
이 매크로들은 croak할 수 있는 코드를 호출하지만 제어를 Perl에 돌려주기 전에 몇 가지 정리를 해야 할 때 사용할 수 있어요. 예:
dXCPT; /* set up necessary variables */
XCPT_TRY_START {
code_that_may_croak();
} XCPT_TRY_END
XCPT_CATCH
{
/* do cleanup here */
XCPT_RETHROW;
}
잡힌 예외는 항상 다시 던져야 한다는 점에 유의하세요. 이 매크로를 사용하면 예외를 잡고 무시하는 것은 불가능해요. 예외를 무시해야 한다면 call_* 함수를 사용해야 해요.
위 매크로를 사용하는 이점은 call_*용 추가 함수를 설정할 필요가 없고, call_*보다 빠르다는 것.
소스 문서화
내부 함수를 문서화하고 그것들에서 자동으로 참조 매뉴얼을 생성하려는 노력이 진행 중이에요 -- perlapi는 XS 작성자에게 제공되는 모든 함수를 자세히 다루는 그런 매뉴얼 중 하나예요. perlintern은 API의 일부가 아니고 내부 전용으로 여겨지는 함수들에 대한 자동 생성 매뉴얼이에요.
소스 문서화는 C 소스에 POD 주석을 넣어 생성돼요. 예:
/*
=for apidoc sv_setiv
Copies an integer into the given SV. Does not handle 'set' magic. See
L<perlapi/sv_setiv_mg>.
=cut
*/
Perl 코어에 함수를 추가하면 문서를 제공하려고 노력하세요.
역호환성 (Backwards compatibility)
Perl API는 시간이 지나며 변해요. 새 함수가 추가되거나 기존 함수의 인터페이스가 바뀌어요. Devel::PPPort 모듈은 이 변경 중 일부에 대한 호환성 코드를 제공하려고 해서, XS 작성자가 여러 버전의 Perl을 지원할 때 직접 코딩하지 않아도 돼요.
Devel::PPPort는 Perl 스크립트로도 실행할 수 있는 C 헤더 파일 ppport.h를 생성해요. ppport.h를 생성하려면:
perl -MDevel::PPPort -eDevel::PPPort::WriteFile
기존 XS 코드 확인 외에도 이 스크립트는 --api-info 커맨드라인 스위치로 다양한 API 호출에 대한 호환성 정보를 검색하는 데 쓸 수 있어요. 예:
% perl ppport.h --api-info=sv_magicext
자세한 내용은 perldoc ppport.h 참조.
유니코드 지원 (Unicode Support)
Perl 5.6.0이 Unicode 지원을 도입했어요. porter와 XS 작성자가 이 지원을 이해하고 자신이 쓰는 코드가 Unicode 데이터를 손상시키지 않도록 하는 것이 중요해요.
어쨌든 유니코드란 무엇인가?
옛날, 깨달음이 적었던 시대에는 모두 ASCII를 썼어요. 어쨌든 우리 대부분이. ASCII의 큰 문제는 미국적이라는 것이에요. 뭐, 아니에요, 그것이 실제 문제는 아니에요; 문제는 로마 알파벳을 안 쓰는 사람들에게 특히 유용하지 않다는 것이에요. 예전에는 특정 언어가 자신의 알파벳을 시퀀스의 상위 범위, 128과 255 사이에 붙였어요. 당연히 우리는 꽤 많은 완전히 ASCII가 아닌 변형을 갖게 됐고, 그것이 표준이라는 전체 요점이 사라졌어요.
더 나쁜 건, 한자나 일본어처럼 수백 또는 수천 개의 문자를 가진 언어라면 256개에 맞출 수 없어서 ASCII를 완전히 잊고, 한 문자를 가리키기 위해 숫자 쌍을 사용하는 자신의 시스템을 구축해야 했어요.
이를 고치기 위해 몇몇 사람들이 Unicode, Inc.를 결성하고 생각할 수 있는 모든 문자 이상을 담는 새 문자 집합을 만들었어요. 이 문자들을 표현하는 방법이 여러 가지 있는데, Perl이 사용하는 것은 UTF-8이라고 불러요. UTF-8은 문자를 표현하기 위해 가변 바이트 수를 사용해요. Unicode와 Perl의 Unicode 모델에 대해 더 배우려면 perlunicode 참조.
(EBCDIC 플랫폼에서 Perl은 대신 UTF-EBCDIC을 사용하는데, 이는 EBCDIC 플랫폼에 맞게 적응된 UTF-8 형태예요. 아래에서는 그냥 UTF-8에 대해 이야기해요. UTF-EBCDIC은 UTF-8과 비슷하지만 세부는 달라요. 매크로가 그 차이를 숨겨 주므로, 아래 제시된 특정 숫자와 비트 패턴이 UTF-EBCDIC에서는 다르다는 것만 기억하세요.)
UTF-8 문자열을 어떻게 인식하나요?
할 수 없어요. UTF-8 데이터가 비-UTF-8 데이터처럼 바이트에 저장되기 때문이에요. 유니코드 문자 200(0xC8, 16진수 좋아하는 사람들용) grave 액센트가 있는 대문자 E는 두 바이트 v196.172로 표현돼요. 안타깝게도 비-유니코드 문자열 chr(196).chr(172)도 그 바이트 시퀀스를 가져요. 그래서 그냥 보는 것만으로는 알 수 없어요 -- 이것이 Unicode 입력을 흥미로운 문제로 만드는 것이에요.
일반적으로 다루고 있는 것을 알거나 추측해야 해요. API 함수 is_utf8_string이 도움이 될 수 있어요. 문자열이 유효한 UTF-8 문자만 담고 있는지 알려주고, 비-UTF-8 문자열이 유효 UTF-8처럼 보일 확률은 문자열 길이가 길어질수록 아주 빨리 매우 작아져요. 문자별로 isUTF8_CHAR이 문자열의 현재 문자가 유효 UTF-8인지 알려줘요.
UTF-8은 어떻게 Unicode 문자를 표현하나요?
위에서 언급했듯 UTF-8은 문자를 저장하기 위해 가변 바이트 수를 사용해요. 값 0...127의 문자는 옛날 좋은 ASCII처럼 한 바이트에 저장돼요. 문자 128은 v194.128로 저장되고, 191까지 계속돼요 (v194.191). 이제 비트가 다 떨어졌으므로(191은 이진 10111111) 넘어가서 문자 192는 v195.128이에요. 이렇게 계속 가서 문자 2048에서 세 바이트로 이동해요. perlunicode의 "Unicode Encodings"에 어떻게 동작하는지 그림이 있어요.
UTF-8 문자열을 다루고 있다는 걸 안다고 가정하면 UTF8SKIP 매크로로 그 안의 첫 문자가 얼마나 긴지 찾을 수 있어요:
char *utf = "\305\233\340\240\201";
I32 len;
len = UTF8SKIP(utf); /* len is 2 here */
utf += len;
len = UTF8SKIP(utf); /* len is 3 here */
UTF-8 문자열에서 문자를 건너뛰는 또 다른 방법은 perlapi의 "utf8_hop_safe"를 사용하는 것이에요. 문자열과 건너뛸 문자 수를 받아요.
다중-바이트 UTF-8 문자의 모든 바이트는 높은 비트가 설정되므로, 이 문자로 특별한 일을 해야 하는지 이렇게 테스트할 수 있어요 (UTF8_IS_INVARIANT()는 바이트가 UTF-8에서도 단일 바이트로 인코딩되는지 테스트하는 매크로):
U8 *utf; /* Initialize this to point to the beginning of the
sequence to convert */
U8 *utf_end; /* Initialize this to 1 beyond the end of the sequence
pointed to by 'utf' */
UV uv; /* Returned code point; note: a UV, not a U8, not a
char */
STRLEN len; /* Returned length of character in bytes */
if (!UTF8_IS_INVARIANT(*utf)) {
/* Must treat this as UTF-8 */
if (! utf8_to_uv(utf, utf_end, &uv, &len)) {
/* handle error */
}
}
else
/* OK to treat this character as a byte */
uv = *utf;
그 예에서 문자 값을 얻기 위해 utf8_to_uv를 사용하는 것도 볼 수 있어요; UV를 UTF-8에 넣기 위한 역함수 uv_to_utf8도 있어요:
if (!UVCHR_IS_INVARIANT(uv))
/* Must treat this as UTF8 */
utf8 = uv_to_utf8(utf8, uv);
else
/* OK to treat this character as a byte */
*utf8++ = uv;
UTF-8과 비-UTF-8 문자를 대응시켜야 하는 상황이라면 위 함수들로 문자를 반드시 UV로 변환해야 해요. 이 경우 UTF-8 문자를 건너뛰면 안 돼요. 그렇게 하면 높은 비트의 비-UTF-8 문자를 대응시킬 능력을 잃을 거예요. 예를 들어 UTF-8 문자열이 v196.172를 담고 있는데 그 문자를 건너뛰면 비-UTF-8 문자열의 chr(200)을 절대 대응시킬 수 없어요. 그러니 그러지 마세요!
(위 예에서 불변 문자를 테스트할 필요는 없다는 점에 유의하세요. 함수들은 잘 형성된 UTF-8 입력에 모두 동작해요. 필요 없을 때 함수 오버헤드를 피하는 게 더 빠를 뿐이에요.)
Perl은 UTF-8 문자열을 어떻게 저장하나요?
현재 Perl은 UTF-8 문자열과 비-UTF-8 문자열을 약간 다르게 다뤄요. SV의 플래그 SVf_UTF8은 문자열이 내부적으로 UTF-8로 인코딩되어 있음을 나타내요. 그것이 없으면 바이트 값이 코드포인트 번호고 그 반대도 마찬가지예요. 이 플래그는 SV가 SvPOK이거나 SvPV나 유사 매크로로 문자열화된 직후에만 의미가 있어요. 다음 매크로로 이 플래그를 확인·조작할 수 있어요:
SvUTF8(sv)
SvUTF8_on(sv)
SvUTF8_off(sv)
이 플래그는 Perl이 문자열을 처리하는 방식에 중요한 영향을 미쳐요: UTF-8 데이터를 제대로 구별하지 않으면 정규식, length, substr 및 기타 문자열 처리 연산이 바람직하지 않은(잘못된) 결과를 가질 거예요.
문제는 예를 들어 UTF-8로 플래그되지 않은 문자열이 UTF-8일 수 있는 바이트 시퀀스를 담고 있을 때 발생해요 -- 특히 비-UTF-8과 UTF-8 문자열을 결합할 때.
SVf_UTF8 플래그가 PV 값과 분리되어 있다는 것을 절대 잊지 마세요; SV를 조작하는 동안 실수로 그것을 떨어뜨리지 않도록 확실히 해야 해요. 더 구체적으로, 이렇게 할 수 있다고 기대할 수 없어요:
SV *sv;
SV *nsv;
STRLEN len;
char *p;
p = SvPV(sv, len);
frobnicate(p);
nsv = newSVpvn(p, len);
char* 문자열이 전체 이야기를 말해 주지 않으므로, 문자열 값을 복사하는 것만으로 SV를 복사하거나 재구성할 수 없어요. (SvPV 호출 후에) 옛 SV에 UTF8 플래그가 설정되었는지 확인하고 그에 따라 행동하세요:
p = SvPV(sv, len);
is_utf8 = SvUTF8(sv);
frobnicate(p, is_utf8);
nsv = newSVpvn(p, len);
if (is_utf8)
SvUTF8_on(nsv);
위에서 frobnicate 함수는 UTF-8 데이터를 다루는지 아닌지 인지하도록 바뀌어, 문자열을 적절히 처리할 수 있어요.
SV를 XS 함수에 넘기고 SV의 데이터를 복사하는 것만으로는 UTF8 플래그를 복사하기에 충분하지 않으므로, XS 함수에 char *만 넘기는 것은 더욱 부적절해요.
완전한 일반성을 위해 DO_UTF8 매크로를 사용해 SV의 문자열이 UTF-8로 취급되어야 하는지 확인하세요. 이는 XS 함수 호출이 use bytes 스코프 내에서 이루어지는지 고려해요. 그렇다면 UTF-8 문자열을 구성하는 기본 바이트가 그것이 나타내는 문자보다 노출되어야 해요. 하지만 이 프래그마는 정말 디버깅과 아마 바이트 수준 저수준 테스트에만 사용해야 해요. 그래서 대부분의 XS 코드는 이것을 걱정할 필요가 없지만, perl 코어의 다양한 영역은 그것을 지원해야 해요.
그리고 이것이 전부가 아니에요. Perl v5.12부터 UTF-8로 인코딩되지 않은 문자열도 다양한 조건에서 Unicode로 취급될 수 있어요 (perlunicode의 "ASCII Rules versus Unicode Rules" 참조). 이것은 정말 서수 128과 255 사이의 문자에만 문제이고, 그 행동은 ASCII와 Unicode 규칙 아래에서 여러분 코드가 신경 쓰는 방식으로 달라져요 (perlunicode의 "The 'Unicode Bug'" 참조). 이를 다루는 발표된 API는 없어요 (변경 가능하므로) 하지만 pp.c의 pp_lc 코드를 보면 현재 어떻게 하는지 예시를 볼 수 있어요.
Perl 문자열을 C 라이브러리에 어떻게 전달하나요?
Perl 문자열은 개념적으로 불투명한 코드 포인트 시퀀스예요. 많은 C 라이브러리는 입력이 "고전적" C 문자열, 즉 NUL 바이트로 끝나는 옥텟 1-255의 배열일 것으로 기대해요. Perl과 C 라이브러리 사이의 인터페이스를 작성할 때 여러분의 작업은 Perl과 그 라이브러리 사이의 매핑을 정의하는 것이에요.
일반적으로 SvPVbyte와 관련 매크로가 이 작업에 잘 맞아요. 이것들은 Perl 문자열이 "바이트 문자열", 즉 Perl에 대한 원시, 디코딩되지 않은 입력이거나 이미 예를 들어 UTF-8로 사전 인코딩되었다고 가정해요.
대안으로, C 라이브러리가 UTF-8 텍스트를 기대한다면 SvPVutf8와 관련 매크로를 사용할 수 있어요. 이것은 UTF-8로 인코딩한 다음 해당 SvPVbyte-관련 매크로를 호출하는 것과 같은 효과가 있어요.
일부 C 라이브러리는 다른 인코딩(예: UTF-16LE)을 기대할 수 있어요. 그런 라이브러리에 Perl 문자열을 주려면 Perl에서 그 인코딩을 한 다음 SvPVbyte를 사용하거나, 중간 C 라이브러리를 사용해 Perl이 문자열을 저장하는 방식에서 원하는 인코딩으로 변환해야 해요.
또한 Perl 문자열의 NUL이 C 라이브러리를 혼란시키지 않도록 주의하세요. 가능하면 문자열의 길이를 C 라이브러리에 주고, 불가능하면 NUL 바이트를 포함한 문자열을 거부하는 것을 고려하세요.
SvPV, SvPV_nolen 등은 어떨까요?
3-문자 Perl 문자열 $foo = "\x64\x78\x8c"를 고려해 보세요. Perl은 이 3문자를 두 가지 방법 중 하나로 저장할 수 있어요:
- bytes: 0x64 0x78 0x8c
- UTF-8: 0x64 0x78 0xc2 0x8c
이제 $foo를 C 문자열로 이렇게 변환한다고 해 봐요:
STRLEN strlen;
char *str = SvPV(foo_sv, strlen);
이 시점에서 str은 3바이트 C 문자열을 가리키거나 4바이트 것을 가리킬 수 있어요.
일반적으로 Perl이 $foo를 어떻게 저장하든 str이 같기를 원하므로, 여기의 모호함은 바람직하지 않아요. SvPVbyte와 SvPVutf8은 예측 가능한 출력을 줘서 해결해요: C 라이브러리가 바이트 문자열을 기대하면 SvPVbyte, UTF-8을 기대하면 SvPVutf8을 사용하세요.
C 라이브러리가 우연히 두 인코딩을 모두 지원한다면, 항상 SvUTF8 조회와 함께 사용하는 SvPV가 안전하고 (약간) 더 효율적일 수 있어요.
테스트 팁: Perl의 내부 인코딩과 무관하게 일관된 처리를 보장하려면 테스트에서 utf8의 upgrade와 downgrade 함수를 사용하세요.
문자열을 UTF-8로 어떻게 변환하나요?
UTF-8과 비-UTF-8 문자열을 섞는다면 비-UTF-8 문자열을 UTF-8로 업그레이드하는 것이 필요해요. SV가 있다면 가장 쉬운 방법은:
sv_utf8_upgrade(sv);
하지만 예를 들어 이렇게 해서는 안 돼요:
if (!SvUTF8(left))
sv_utf8_upgrade(left);
이것을 이진 연산자에서 하면 연산자에 들어온 문자열 중 하나를 실제로 바꾸게 되고, 최종 사용자에게는 눈에 띄지 않아야 하지만 결함 있는 코드에서는 문제를 일으킬 수 있어요.
대신 bytes_to_utf8이 문자열 인자의 UTF-8 인코딩된 복사본을 줄 거예요. 이는 원래 SV를 해치지 않고 비교용 데이터를 준비하는 데 유용해요. 반대 방향에는 utf8_to_bytes도 있지만, 당연히 문자열이 단일 바이트로 표현될 수 없는 255 위의 문자를 포함하면 실패해요.
문자열을 어떻게 비교하나요?
perlapi의 "sv_cmp"와 "sv_cmp_flags"는 두 SV를 사전순 비교하고 UTF-8을 올바르게 처리해요. 단, Unicode는 Unicode::Collate 모듈에서 사용할 수 있는 훨씬 더 화려한 정렬 메커니즘을 지정한다는 점에 유의하세요.
두 문자열을 같음/다름으로만 비교하려면 두 문자열이 모두 UTF-8이거나 모두 UTF-8이 아닌 인코딩이어야 한다는 점을 제외하고 평소처럼 memEQ()와 memNE()를 쓰면 돼요.
두 문자열을 대소문자 구분 없이 비교하려면 foldEQ_utf8()을 사용하세요 (문자열이 같은 UTF-8일 필요는 없어요).
또 알아야 할 것이 있나요?
별로 없어요. 그냥 이것들만 기억하세요:
char *나U8 *문자열이 UTF-8인지 알 수 있는 방법은 없어요. 하지만 SV가SvPV나 유사 매크로로 문자열화된 후DO_UTF8을 호출하면 SV가 UTF-8로 취급되어야 하는지 알 수 있어요. 그리고 (그렇게 취급되지 않더라도) SV의SvUTF8플래그를 보면 실제로 UTF-8인지 알 수 있어요 (다시 문자열화한 후). 무언가 UTF-8이어야 한다면 플래그를 설정하는 것을 잊지 마세요. 플래그를 PV의 일부로 취급하세요, 비록 그렇지 않더라도 -- PV를 어딘가로 넘기면 플래그도 함께 넘기세요.- 문자열이 UTF-8이면
UTF8_IS_INVARIANT(*s)인 경우를 제외하고 항상utf8_to_uv로 값을 얻으세요. 그 경우*s를 쓸 수 있어요. - 문자 UV를 UTF-8 문자열에 쓸 때는
UVCHR_IS_INVARIANT(uv))인 경우를 제외하고 항상uv_to_utf8을 사용하세요. 그 경우*s = uv를 쓸 수 있어요. - UTF-8과 비-UTF-8 문자열을 섞는 것은 까다로워요.
bytes_to_utf8으로 UTF-8 인코딩된 새 문자열을 얻은 다음 결합하세요.
커스텀 연산자 (Custom Operators)
커스텀 연산자 지원은 자신만의 연산을 정의할 수 있게 해 주는 실험적 기능이에요. 이는 주로 Perl 코어에서 다른 언어를 위한 인터프리터를 만들 수 있게 하기 위한 것이지만, "매크로-op"(보통 함께 실행되는 여러 연산의 기능을 수행하는 연산, 예: gvsv, gvsv, add)를 만들어 최적화할 수도 있게 해 줘요.
이 기능은 새 op 타입 OP_CUSTOM으로 구현돼요. Perl 코어는 이 op 타입에 대해 특별히 "알지" 못하므로 어떤 최적화에도 관여하지 않아요. 이는 커스텀 op를 단항, 이진, 리스트 등 원하는 어떤 op 구조체로 정의할 수 있다는 뜻이기도 해요.
커스텀 연산자가 무엇을 하지 않을지 아는 것이 중요해요. 그것들은 직접 Perl에 새 구문을 추가하게 하지 않아요. 직접 새 키워드를 추가하게 하지도 않아요. 사실 Perl이 프로그램을 컴파일하는 방식을 전혀 바꾸지 않아요. Perl이 프로그램을 컴파일한 후 그 변경을 직접 해야 해요. 이것은 CHECK 블록과 B::Generate 모듈로 op 트리를 조작하거나, optimize 모듈로 커스텀 peephole 최적화기를 추가해 수행해요.
이렇게 할 때 OP_CUSTOM 타입과 자신의 PP 함수인 op_ppaddr로 연산을 만들어 일반 Perl 연산을 커스텀 연산으로 교체해요. 이것은 XS 코드로 정의되어야 하고 pp_*.c의 PP 연산처럼 보여야 해요. 여러분의 연산이 스택에서 적절한 수의 값을 가져가는지 확인하는 책임이 있고, 필요하면 스택 마커를 추가하는 책임도 있어요.
또한 Perl 인터프리터에 연산을 "등록"해서 합리적인 오류·경고 메시지를 생성할 수 있게 해야 해요. 하나의 "논리적" op 타입 OP_CUSTOM 안에 여러 커스텀 op가 있을 수 있으므로 Perl은 o->op_ppaddr 값을 사용해 어떤 커스텀 op인지 결정해요. 사용하는 각 ppaddr에 대해 XOP 구조체를 만들고, XopENTRY_set으로 커스텀 op의 속성을 설정하며, Perl_custom_op_register로 구조체를 ppaddr에 대해 등록해야 해요. 사소한 예는 이렇게 보여요:
static XOP my_xop;
static OP *my_pp(pTHX);
BOOT:
XopENTRY_set(&my_xop, xop_name, "myxop");
XopENTRY_set(&my_xop, xop_desc, "Useless custom op");
Perl_custom_op_register(aTHX_ my_pp, &my_xop);
구조체에서 사용 가능한 필드는:
xop_name — 연산에 대한 짧은 이름. 일부 오류 메시지에 포함되고 B 모듈이 $op->name으로 반환하므로 B::Concise 같은 모듈의 출력에 나타나요.
xop_desc — 연산 기능의 짧은 설명.
xop_class — 이 연산이 사용하는 다양한 *OP 구조체 중 어떤 것인지. op.h의 OA_* 상수 중 하나여야 해요, 즉:
- OA_BASEOP
- OA_UNOP
- OA_BINOP
- OA_LOGOP
- OA_LISTOP
- OA_PMOP
- OA_SVOP
- OA_PADOP
- OA_PVOP_OR_SVOP — '
PVOP'로만 해석해야 해요._OR_SVOP는 하나의 코어PVOP인OP_TRANS가 때때로SVOP일 수 있기 때문이에요. - OA_LOOP
- OA_COP
- OA_UNOP_AUX
다른 OA_* 상수는 사용하면 안 돼요.
xop_peep — 이 멤버는 Perl_cpeep_t 타입으로, void (*Perl_cpeep_t)(aTHX_ OP *o, OP *oldop)로 확장돼요. 설정되면 peephole 최적화기가 이런 타입의 연산을 만날 때 Perl_rpeep에서 이 함수가 호출돼요. o는 최적화가 필요한 OP, oldop은 이전에 최적화된 OP로 그 op_next가 o를 가리켜요.
xop_dump — 이 멤버는 void (pTHX_ const OP *, struct Perl_OpDumpContext *) 타입 함수에 대한 포인터예요. 설정되면 op의 기본 필드가 인쇄된 후 이런 타입의 커스텀 연산자를 덤프할 때 op_dump()이 호출해요. 이 함수는 디버깅에 유용한 추가 출력을 내보내기 위해 opdump_printf()를 사용할 수 있어요.
마지막 인자로 넘어오는 불투명 구조체 포인터는 opdump_printf()에 직접 전달해야 해요.
B::Generate는 이름으로 커스텀 연산 생성을 직접 지원해요.
스택 (Stacks)
위 설명이 가끔 "스택"을 언급하지만, 사실 perl 인터프리터 안에는 많은 스택-같은 데이터 구조가 있어요. 자격 없는 "스택"은 보통 값 스택을 가리켜요.
다양한 스택은 목적이 다르고 약간 다른 방식으로 동작해요. 그 차이는 아래에 기록돼요.
값 스택 (Value Stack)
이 스택은 일반 perl 코드가 작업하는 값, 보통 문장 내 표현식의 중간 값을 저장해요. 스택 자체는 SV 포인터 배열로 형성돼요.
이 스택의 밑은 SV ** 타입의 인터프리터 변수 PL_stack_base가 가리켜요.
스택의 머리는 PL_stack_sp이고 가장 최근에 밀어 넣은 항목을 가리켜요.
항목은 PUSHs() 매크로나 그 변형; XPUSHs(), mPUSHs(), mXPUSHs()와 타입 버전으로 스택에 밀어 넣어져요. 이 매크로의 비-X 버전은 스택 크기를 확인하지 않고 충분히 크다고 가정한다는 점을 주의 깊게 확인하세요. 이것들은 EXTEND 매크로 같은 적절한 스택 크기 확인과 짝을 이루어 충분히 큰지 보장해야 해요. 예:
EXTEND(SP, 4);
mPUSHi(10);
mPUSHi(20);
mPUSHi(30);
mPUSHi(40);
이것은 네 번의 별도 mXPUSHi() 호출에서 네 번의 별도 확인을 하는 것보다 약간 더 성능이 좋아요.
추가 성능 최적화로, 다양한 PUSH 매크로는 모두 인터프리터-전역 변수 PL_stack_sp보다는 로컬 변수 SP로 동작해요. 이 변수는 dSP 매크로로 선언돼요 - 보통 XSUB와 유사한 것에서 암시되므로 직접 고려할 일은 드물어요. 일단 선언되면 PUSH 매크로는 이 로컬 변수로만 동작하므로, 다른 perl 코어 함수를 호출하기 전에 PUTBACK 매크로로 로컬 SP 변수의 값을 인터프리터 변수로 되돌려야 해요. 마찬가지로 스택을 옮기거나 값을 push/pop한 이유가 있을 수 있는 perl 코어 함수를 호출한 후에는 로컬 SP 값을 인터프리터 값에서 새로 고치는 SPAGAIN 매크로를 사용해야 해요.
항목은 POPs 매크로나 그 타입 버전으로 스택에서 꺼내져요. 제거하지 않고 최상위 항목을 검사하는 TOPs 매크로도 있어요.
특히 값 스택의 SV 포인터는 참조되는 xV의 전체 참조 카운트에 기여하지 않는다는 점에 유의하세요. 새로 생성된 xV를 스택에 밀어 넣는다면 적절한 시점에 파괴되도록 배열해야 해요; 보통 mPUSH* 매크로 중 하나나 sv_2mortal()로 xV를 mortalize해요.
마크 스택 (Mark Stack)
값 스택은 개별 perl 스칼라 값을 표현식 사이의 임시 값으로 저장해요. 일부 perl 표현식은 전체 리스트에 대해 동작해요; 그 목적으로 각 리스트가 스택의 어디에서 시작하는지 알아야 해요. 이것이 마크 스택의 목적이에요.
마크 스택은 리스트가 시작되기 전 시점의 값 스택 높이인 I32 값으로 정수를 저장해요; 따라서 마크 자체는 실제로 리스트 바로 한 칸 앞의 값 스택 항목을 가리켜요. 리스트 자체는 mark + 1에서 시작해요.
이 스택의 밑은 I32 * 타입의 인터프리터 변수 PL_markstack이 가리켜요.
스택의 머리는 PL_markstack_ptr이고 가장 최근에 밀어 넣은 항목을 가리켜요.
항목은 PUSHMARK() 매크로로 스택에 밀어 넣어져요. 스택 자체가 (값) 스택 인덱스를 정수로 저장하지만, PUSHMARK 매크로는 스택 포인터를 직접 주어야 해요; PL_stack_sp 변수와 비교해 인덱스 오프셋을 계산할 거예요. 따라서 이 코드를 수행하는 것은 거의 항상:
PUSHMARK(SP);
항목은 POPMARK 매크로로 스택에서 꺼내져요. 제거 없이 최상위 항목을 검사하는 TOPMARK 매크로도 있어요. 이 매크로들은 I32 인덱스 값을 직접 반환해요. 또한 스택에 주어진 리스트에서 작업할 때 보통 사용하는 마크된 스택 슬롯을 가리키는 새 SV 이중 포인터 변수 mark를 선언하는 dMARK 매크로도 있어요.
위에서 언급했듯, mark 변수 자체는 리스트가 시작되기 전 값 스택에서 가장 최근에 밀어 넣은 값을 가리키므로 리스트 자체는 mark + 1에서 시작해요. 리스트의 값은 다음과 같은 코드로 반복할 수 있어요:
for(SV **svp = mark + 1; svp <= PL_stack_sp; svp++) {
SV *item = *svp;
...
}
특히 리스트가 이미 비어 있으면 mark가 PL_stack_sp와 같을 것이라는 점에 유의하세요.
mark 변수가 값 스택의 포인터로 변환되므로, 함수 내에서 EXTEND나 XPUSH 매크로 중 어떤 것이 호출되면 스택을 확장하기 위해 옮겨야 할 수 있어 기존 포인터가 이제 유효하지 않을 수 있으므로 각별히 주의해야 해요. 문제가 될 수 있다면 가능한 해결책은 마크 오프셋을 정수로 추적하고 나중에 스택이 옮겨진 후 마크 자체를 추적하는 것이에요.
I32 markoff = POPMARK;
...
SP **mark = PL_stack_base + markoff;
임시 스택 (Temporaries Stack)
위에서 언급했듯, 주 값 스택의 xV 참조는 xV의 참조 카운트에 기여하지 않으므로, 스택에 사는 임시 값이 언제 해제되어야 하는지 추적하는 데 다른 메커니즘이 사용돼요. 이것이 임시 스택의 작업이에요.
임시 스택은 참조 카운트가 곧 감소될 xV에 대한 포인터를 저장해요.
이 스택의 밑은 SV ** 타입의 인터프리터 변수 PL_tmps_stack이 가리켜요.
스택의 머리는 PL_tmps_ix로 인덱스되며, 가장 최근에 밀어 넣은 항목의 배열 인덱스를 저장하는 정수예요.
임시 스택에 항목을 직접 밀어 넣는 공개 API는 없어요. 대신 API 함수 sv_2mortal()이 xV를 mortalize해 그 주소를 임시 스택에 추가하는 데 사용돼요.
마찬가지로 임시 스택에서 값을 읽는 공개 API도 없어요. 대신 SAVETMPS와 FREETMPS 매크로가 사용돼요. SAVETMPS 매크로는 PL_tmps_ix의 현재 값을 PL_tmps_floor로 캡처하고 이전 값을 save stack에 저장해 임시 스택의 기본 레벨을 설정해요. 그 후 FREETMPS가 호출될 때마다 그 레벨 이후로 밀어 넣어진 모든 임시 값이 회수돼요.
이 두 매크로를 ENTER/LEAVE 쌍 안에서 쌍으로 보는 게 흔하지만, 일치시킬 필요는 없어요. 가장 최근 SAVETMPS 이후 FREETMPS를 여러 번 호출하는 것이 허용돼요; 예를 들어 리스트 요소를 반복하는 루프에서. 스코프 쌍 안에서 SAVETMPS를 여러 번 호출할 수는 있지만 유용할 것 같지 않아요. 후속 호출은 임시 바닥(floor)을 더 위로 옮겨, 기존 임시 값이 스코프 끝에서만 해제되도록 효과적으로 가둘 거예요.
Save Stack
save stack은 perl이 local 키워드와 다른 유사한 행동을 구현하는 데 사용해요; 현재 스코프를 나갈 때 수행해야 할 어떤 정리 작업이든. 이 스택에 밀어 넣은 항목은 일반적으로 스코프가 나가기, return, die, goto 또는 다른 이유로 되감길 때 복원될 일부 내부 변수나 상태의 현재 값을 캡처해요.
다른 perl 내부 스택은 모두 같은 타입의 개별 항목(보통 SV 포인터나 정수)을 저장하는 반면, save stack에 밀어 넣은 항목은 여러 필드를 가진 많은 다른 타입으로 형성돼요. 예를 들어 SAVEt_INT 타입은 복원할 int 변수의 주소와 복원할 값을 모두 저장해야 해요. 이 정보는 struct의 필드로 저장될 수 있었지만, 가장 큰 경우에 세 포인터를 저장할 만큼 커야 하므로 대부분의 작은 경우에 많은 공간을 낭비할 거예요.
대신 스택은 ANY 구조체의 가변 길이 인코딩으로 정보를 저장해요. 마지막으로 밀어 넣은 값은 UV 필드에 저장되는데, 이것은 앞선 항목들이 보유한 항목 종류를 인코딩해요; 그 개수와 타입은 저장되는 항목 종류에 따라 달라져요. 종류 필드가 마지막에 밀어 넣어지는 이유는 스택에서 항목을 되감을 때 첫 번째로 꺼내질 필드이기 때문이에요.
이 스택의 밑은 ANY * 타입의 인터프리터 변수 PL_savestack이 가리켜요.
스택의 머리는 PL_savestack_ix로 인덱스되며, 다음 항목이 밀어 넣어져야 할 배열의 인덱스를 저장하는 정수예요. (이것은 대부분의 다른 스택(가장 최근에 밀어 넣은 항목을 참조)과 다르다는 점에 유의하세요.)
항목은 다양한 SAVE...() 매크로로 save stack에 밀어 넣어져요. 이 매크로 중 많은 것이 변수를 받아 그 주소와 현재 값을 save stack에 저장해, 스코프 나가기 시 그 값이 복원되도록 보장해요.
SAVEI8(i8)
SAVEI16(i16)
SAVEI32(i32)
SAVEINT(i)
...
특정 종류의 값이나 흥미로운 값을 저장하는 다양한 특수 목적 매크로도 있어요. SAVETMPS는 이미 위에서 언급했어요. 다른 것에는 PV(즉 문자열 버퍼)를 해제하도록 배열하는 SAVEFREEPV, 스코프 나가기 시 주어진 함수 포인터를 호출하도록 배열하는 SAVEDESTRUCTOR가 포함돼요. 그런 매크로의 전체 목록은 scope.h에서 찾을 수 있어요.
save stack에서 개별 값이나 항목을 꺼내는 공개 API는 없어요. 대신 스코프 스택을 통해 ENTER와 LEAVE 쌍이 중첩 스코프를 시작·정지하는 방법을 형성해요. LEAVE로 중첩 스코프를 나가면 가장 최근 ENTER 이후 밀어 넣어진 모든 저장된 값을 복원해요.
스코프 스택 (Scope Stack)
마크 스택이 값 스택과 쌍을 이루듯, 스코프 스택은 save stack과 쌍을 이뤄요. 스코프 스택은 중첩 스코프가 시작되는 save stack의 높이를 저장하고, 스코프가 나가질 때 save stack을 그 지점까지 되감을 수 있게 해 줘요.
perl이 디버깅 활성화로 빌드되면 이 스택에는 스택 컨텍스트의 종류를 설명하는 사람이 읽을 수 있는 문자열 이름을 저장하는 두 번째 부분이 있어요. 각 push 연산은 save stack의 높이와 함께 이름을 저장하고, 각 pop 연산은 최상위 이름을 기대하는 것과 확인해 이름이 일치하지 않으면 assertion 실패를 일으켜요.
이 스택의 밑은 I32 * 타입의 인터프리터 변수 PL_scopestack이 가리켜요. 활성화되면 스코프 스택 이름은 const char ** 타입의 PL_scopestack_name이 가리키는 별도 배열에 저장돼요.
스택의 머리는 PL_scopestack_ix로 인덱스되며, 다음 항목이 밀어 넣어져야 할 배열의 인덱스를 저장하는 정수예요. (이것은 대부분의 다른 스택(가장 최근에 밀어 넣은 항목을 참조)과 다르다는 점에 유의하세요.)
값은 ENTER 매크로로 스코프 스택에 밀어 넣어지는데, 이는 새 중첩 스코프를 시작해요. save stack에 밀어 넣어진 어떤 항목이든 다음 중첩 LEAVE 매크로 호출에서 복원돼요.
동적 스코프와 컨텍스트 스택
참고: 이 절은 공지 없이 변경될 수 있는 비공개 내부 API를 설명해요.
컨텍스트 스택 소개
Perl에서 동적 스코프는 서브루틴 호출, eval 등과 같은 것들의 런타임 중첩뿐만 아니라 블록 스코프의 진입·이탈을 가리켜요. 예를 들어 local된 변수 복원은 동적 스코프에 의해 결정돼요.
Perl은 컨텍스트 스택이라고 불리는 데이터 구조로 동적 스코프를 추적해요. 이는 PERL_CONTEXT 구조체의 배열이며, 그 자체가 모든 종류의 컨텍스트를 위한 큰 union이에요. 새 스코프가 들어가면(블록, for 루프, 서브루틴 호출 등) 새 컨텍스트 항목이 스택에 밀어 넣어져요. 마찬가지로 블록을 나가거나 서브루틴 호출에서 반환할 때 컨텍스트가 꺼내져요. 컨텍스트 스택이 현재 동적 스코프를 나타내므로 검색할 수 있어요. 예를 들어 next LABEL은 라벨과 일치하는 루프 컨텍스트를 찾아 스택을 거슬러 검색해요; return은 sub이나 eval 컨텍스트 등을 찾을 때까지 컨텍스트를 꺼내요; caller는 스택의 sub 컨텍스트를 검사해요.
각 컨텍스트 항목은 컨텍스트 타입 cx_type으로 라벨링돼요. 전형적인 컨텍스트 타입은 CXt_SUB, CXt_EVAL 등이며, 기본 스코프(pp_enter가 밀어 넣는)와 정렬 블록을 나타내는 CXt_BLOCK과 CXt_NULL도 있어요. 타입은 컨텍스트 union의 어느 부분이 유효한지 결정해요.
컨텍스트 struct의 주요 분할은 치환 스코프(CXt_SUBST)와 그 외 모든 것인 블록 스코프 사이예요. 전자는 s///e를 실행하는 동안에만 사용되며 여기서 더 다루지 않아요.
모든 블록 스코프 타입은 CXt_BLOCK에 해당하는 공통 베이스를 공유해요. 이는 PL_curpm 같은 다양한 스코프 관련 변수의 옛 값뿐만 아니라 gimme 같은 현재 스코프에 대한 정보를 저장해요. 스코프를 나가면 옛 변수들이 복원돼요.
특정 블록 스코프 타입은 타입별 추가 정보를 저장해요. 예를 들어 CXt_SUB는 현재 실행 중인 CV를 저장하고, 다양한 for 루프 타입은 원래 루프 변수 SV를 보유할 수 있어요. 스코프를 나가면 타입별 데이터가 처리돼요; 예를 들어 CV의 참조 카운트가 감소되고 원래 루프 변수가 복원돼요.
매크로 cxstack은 현재 컨텍스트 스택의 밑을 반환하고, cxstack_ix는 그 스택 안의 현재 프레임 인덱스예요.
사실 컨텍스트 스택은 스택-중-스택 시스템의 일부예요; DESTROY나 tie 핸들러를 호출하는 것 같은 특이한 일을 할 때마다 새 스택이 밀어 넣어지고 끝에 꺼내져요.
여기 설명된 API가 perl 5.24에서 상당히 바뀌었다는 점에 유의하세요. 그 전에는 PUSHBLOCK, POPSUB 같은 큰 매크로가 사용됐어요; 5.24에서 아래 설명하는 인라인 static 함수로 교체됐어요. 또한 이 매크로/함수들이 동작하는 방식의 순서와 세부가 여러 면에서 종종 미묘하게 바뀌었어요. 특히 savestack과 temps 스택 위치 저장을 처리하지 않았고, 새 함수들에 비해 추가 ENTER, SAVETMPS, LEAVE가 필요했어요. 옛 스타일 매크로는 더 설명하지 않아요.
컨텍스트 밀어 넣기
새 컨텍스트를 밀어 넣기 위한 두 기본 함수는 cx = cx_pushblock()이며 새 기본 컨텍스트 블록을 밀어 넣고 그 주소를 반환해요, 그리고 cx_pushsub(cx) 같은 이름의 유사 함수 계열이 cx struct에서 추가 타입 의존 필드를 채워요. CXt_NULL과 CXt_BLOCK은 cx_pushblock이 밀어 넣은 것 이상의 데이터를 저장하지 않으므로 자체 push 함수가 없다는 점에 유의하세요.
컨텍스트 struct의 필드와 cx_* 함수의 인자는 perl 릴리스 사이에 변경될 수 있어, 그 릴리스에 편리하거나 효율적인 무엇이든 나타내요.
전형적인 컨텍스트 스택 밀어 넣기는 pp_entersub에서 찾을 수 있어요; 다음은 비-XS 호출의 단순화되고 축소된 예시와 각 함수가 대략 무엇을 하는지 보여주는 주석이에요.
dMARK;
U8 gimme = GIMME_V;
bool hasargs = cBOOL(PL_op->op_flags & OPf_STACKED);
OP *retop = PL_op->op_next;
I32 old_ss_ix = PL_savestack_ix;
CV *cv = ....;
/* ... make mortal copies of stack args which are PADTMPs here ... */
/* ... do any additional savestack pushes here ... */
/* Now push a new context entry of type 'CXt_SUB'; initially just
* doing the actions common to all block types: */
cx = cx_pushblock(CXt_SUB, gimme, MARK, old_ss_ix);
/* this does (approximately):
CXINC; /* cxstack_ix++ (grow if necessary) */
cx = CX_CUR(); /* and get the address of new frame */
cx->cx_type = CXt_SUB;
cx->blk_gimme = gimme;
cx->blk_oldsp = MARK - PL_stack_base;
cx->blk_oldsaveix = old_ss_ix;
cx->blk_oldcop = PL_curcop;
cx->blk_oldmarksp = PL_markstack_ptr - PL_markstack;
cx->blk_oldscopesp = PL_scopestack_ix;
cx->blk_oldpm = PL_curpm;
cx->blk_old_tmpsfloor = PL_tmps_floor;
PL_tmps_floor = PL_tmps_ix;
*/
/* then update the new context frame with subroutine-specific info,
* such as the CV about to be executed: */
cx_pushsub(cx, cv, retop, hasargs);
/* this does (approximately):
cx->blk_sub.cv = cv;
cx->blk_sub.olddepth = CvDEPTH(cv);
cx->blk_sub.prevcomppad = PL_comppad;
cx->cx_type |= (hasargs) ? CXp_HASARGS : 0;
cx->blk_sub.retop = retop;
SvREFCNT_inc_simple_void_NN(cv);
*/
cx_pushblock()이 두 개의 새 바닥(floor)을 설정한다는 점에 유의하세요: args 스택(MARK로)과 temps 스택(PL_tmps_ix로). 이 스코프 레벨에서 실행하는 동안 모든 nextstate(그 외에도)가 args와 tmps 스택 레벨을 이 바닥들로 재설정해요. cx_pushblock이 인자로 전달되는 대신 PL_tmps_ix의 현재 값을 사용하므로, 이것이 cx_pushblock을 언제 호출해야 하는지 결정한다는 점에 유의하세요. 특히, (다음 nextstate가 아니라) 스코프 나가기에서만 해제되어야 할 새 mortal은 먼저 만들어져야 해요.
cx_pushblock의 대부분 호출자는 새 args 스택 바닥을 이전 스택 프레임의 맨 위로 설정하지만, CXt_LOOP_LIST의 경우 반복되는 항목을 스택에 저장하므로 대신 blk_oldsp를 이 항목들의 맨 위로 설정해요. 이름과 달리 blk_oldsp가 항상 스코프 나가기 시 PL_stack_sp를 복원할 값을 나타내는 것은 아니라는 점에 유의하세요.
PL_savestack_ix를 old_ss_ix로 일찍 캡처하고 나중에 cx_pushblock에 인자로 전달하는 것에 유의하세요. pp_entersub의 경우, 저장이 필요한 대부분의 값이 컨텍스트 struct의 필드에 저장되지만, 디버거가 실행 중일 때만 추가 값 저장이 필요하고 이 드문 경우에 struct를 부풀리는 것은 말이 안 되기 때문이에요. 그래서 대신 savestack에 저장돼요. 이 값이 컨텍스트가 밀어 넣어지기 전에 계산·저장되므로, 저장된 값이 스코프 나가기 중 해제되도록 보장하려면 PL_savestack_ix의 옛 값을 cx_pushblock에 전달해야 해요. save stack에 아무것도 밀어 넣을 필요가 없는 cx_pushblock의 대부분 사용자에게는 PL_savestack_ix가 인자로 cx_pushblock에 그냥 직접 전달돼요.
가능하면 값은 save stack보다는 컨텍스트 struct에 저장해야 한다는 점에 유의하세요; 그게 훨씬 빨라요.
보통 cx_pushblock 다음에는 즉시 적절한 cx_pushfoo가 와야 하며 그 사이에 아무것도 없어야 해요. 사이의 코드가 죽을 수 있으면(예: fatal로 업그레이드된 경고) 컨텍스트 스택 되감기 코드 dounwind가 (위 예에서) CXt_SUB 컨텍스트 프레임을 보지만 서브루틴별 필드가 모두 설정되지 않은 채로 보고 곧 크래시가 발생할 것이기 때문이에요.
둘이 분리되어야 하는 곳에서는 처음에 타입을 CXt_NULL이나 CXt_BLOCK으로 설정하고, 나중에 cx_pushfoo를 할 때 CXt_foo로 바꾸세요. 이것이 pp_enteriter가 하는 일인데, 어떤 타입의 루프를 밀어 넣는지 결정한 다음 랍니다.
컨텍스트 꺼내기
컨텍스트는 cx_popsub() 등과 cx_popblock()으로 꺼내져요. 단, cx_pushblock과 달리 이 함수들 중 어느 것도 현재 컨텍스트 스택 인덱스를 실제로 감소시키지 않는다는 점에 유의하세요; 이는 CX_POP()로 별도로 수행돼요.
컨텍스트가 꺼내지는 주요 방법이 두 가지 있어요. 정상 실행 중 스코프가 나가질 때 pp_leave, pp_leaveloop, pp_leavesub 같은 함수가 cx_popfoo와 cx_popblock로 컨텍스트 하나만 처리하고 꺼내요. 반면 pp_return과 next 같은 것은 sub이나 루프 컨텍스트를 찾을 때까지 여러 스코프를 꺼내야 할 수 있고, 예외(die 같은)는 eval 컨텍스트를 찾을 때까지 컨텍스트를 꺼내야 해요. 둘 다 대상 컨텍스트 위의 모든 컨텍스트를 처리·꺼낼 수 있는 dounwind()로 수행돼요.
다음은 pp_leavesub에서 찾을 수 있는 컨텍스트 꺼내기의 전형적인 예시예요 (약간 단순화됨):
U8 gimme;
PERL_CONTEXT *cx;
SV **oldsp;
OP *retop;
cx = CX_CUR();
gimme = cx->blk_gimme;
oldsp = PL_stack_base + cx->blk_oldsp; /* last arg of previous frame */
if (gimme == G_VOID)
PL_stack_sp = oldsp;
else
leave_adjust_stacks(oldsp, oldsp, gimme, 0);
CX_LEAVE_SCOPE(cx);
cx_popsub(cx);
cx_popblock(cx);
retop = cx->blk_sub.retop;
CX_POP(cx);
return retop;
위 단계는 컨텍스트가 밀어 넣어진 때의 역순이 되도록 설계된 매우 특정한 순서예요. 첫 번째로 할 일은 어떤 반환 인자를 복사·보호하고 현재 스코프의 임시 값을 해제하는 것이에요. rvalue sub 같은 스코프 나가기는 보통 (lvalue sub와 달리) 반환 인자의 mortal 복사본을 반환해요. save stack이 꺼내지거나 변수가 복원되기 전에 이 복사본을 만드는 것이 중요해요. 그렇지 않으면 다음과 같은 나쁜 일이 발생할 수 있어요:
sub f { my $x =...; $x } # $x freed before we get to copy it
sub f { /(...)/; $1 } # PL_curpm restored before $1 copied
임시 값을 동시에 해제하고 싶지만, 반환 인자를 살려두는 임시 값을 해제하지 않도록 조심해야 해요; 반환 인자를 mortal 복사하면서 방금 만든 임시 값을 해제하지도 않아야 해요. 다행히 leave_adjust_stacks()는 반환 인자의 mortal 복사본을 만들고, 인자를 스택 아래로 옮기며, 해제해도 안전한 임시 스택 항목만 처리할 수 있어요.
void 컨텍스트에서는 반환 인자가 없으므로 leave_adjust_stacks() 호출을 건너뛰는 것이 더 효율적이에요. 또한 void 컨텍스트에서는 FREETMPS를 할 nextstate op가 곧 호출될 가능성이 높으므로 그것도 할 필요가 없어요.
다음 단계는 savestack 항목을 꺼내는 것이에요: CX_LEAVE_SCOPE(cx)는 그냥 LEAVE_SCOPE(cx->blk_oldsaveix)로 정의돼요. 꺼내는 동안 perl이 파괴자를 호출하고, tied 변수의 지역화를 취소하기 위해 STORE를 호출하는 등 할 수 있다는 점에 유의하세요. 이 중 어떤 것이든 die하거나 exit()을 호출할 수 있어요. 이 경우 dounwind()가 호출되고 현재 컨텍스트 스택 프레임이 재처리돼요. 따라서 컨텍스트를 꺼내는 모든 단계는 재진입을 지원하는 방식으로 이루어져야 하는 것이 중요해요. 프레임을 처리하기 전에 cxstack_ix를 감소시키는 다른 대안은 중간에 무언가가 죽으면 누수나 현재 프레임 덮어쓰기 같은 것을 초래할 거예요.
CX_LEAVE_SCOPE 자체는 안전하게 재진입 가능해요: savestack 항목의 절반만 꺼낸 후 죽어 eval에 잡히면, dounwind나 pp_leaveeval의 CX_LEAVE_SCOPE가 첫 번째가 중단한 곳을 계속할 거예요.
다음 단계는 타입별 컨텍스트 처리예요; 이 경우 cx_popsub. 부분적으로 이렇게 보여요:
cv = cx->blk_sub.cv;
CvDEPTH(cv) = cx->blk_sub.olddepth;
cx->blk_sub.cv = NULL;
SvREFCNT_dec(cv);
여기서 방금 실행된 CV를 처리하고 있어요. CV의 참조 카운트를 감소시키기 전에 blk_sub.cv를 null로 만든다는 점에 유의하세요. 이는 재진입하면 CV가 두 번 해제되지 않는다는 뜻이에요. 또한 cx_popfoo에서 반환된 후에 그런 타입별 필드가 유용한 값을 갖는 것에 의존할 수 없다는 뜻이기도 해요.
다음으로 cx_popblock이 다양한 인터프리터 변수를 이전 값이나 이전 고수위 표시로 복원해요; 이렇게 확장돼요:
PL_markstack_ptr = PL_markstack + cx->blk_oldmarksp;
PL_scopestack_ix = cx->blk_oldscopesp;
PL_curpm = cx->blk_oldpm;
PL_curcop = cx->blk_oldcop;
PL_tmps_floor = cx->blk_old_tmpsfloor;
PL_stack_sp는 복원하지 않는다는 점에 유의하세요; 앞서 언급했듯 어떤 값으로 복원할지는 컨텍스트 타입(특히 for (list) {})과 반환하는 인자(있으면)에 따라 달라지며, 이미 leave_adjust_stacks()가 해결했을 거예요.
마지막으로 컨텍스트 스택 포인터가 실제로 CX_POP(cx)로 감소돼요. 이 지점 이후에는 현재 컨텍스트 프레임이 다른 컨텍스트가 밀어 넣어져 덮어쓸 수 있어요. tie와 DESTROY 같은 것이 새 컨텍스트 스택 안에서 동작해야 하지만, 그렇게 가정하지 않는 것이 가장 좋아요. 실제로 디버깅 빌드에서 CX_POP(cx)는 여전히 그 컨텍스트 프레임의 필드 값에 의존하는 코드를 감지하기 위해 의도적으로 cx를 null로 설정해요. 위 pp_leavesub() 예에서 CX_POP을 호출하기 전에 blk_sub.retop을 가져간다는 점에 유의하세요.
컨텍스트 다시 하기
마지막으로 cx_topblock(cx)가 있어요. 이는 다양한 변수를 기본 값으로 재설정한다는 점에서 슈퍼-nextstate처럼 동작해요. pp_next, pp_redo, pp_goto 같은 곳에서 사용되는데, 스코프를 나가는 대신 스코프를 재초기화하려 할 때예요. nextstate처럼 PL_stack_sp를 재설정할 뿐 아니라 PL_markstack_ptr, PL_scopestack_ix, PL_curpm도 재설정해요. FREETMPS는 하지 않는다는 점에 유의하세요.
참조-카운트 인자 스택
소개
perl 5.40부터 빌드 옵션 PERL_RC_STACK이 있는데, 기본적으로 활성화되지 않으며 인자 스택에 밀어 넣거나 꺼낸 항목의 참조 카운트를 조정하도록 요구해요. 궁극적으로 이것이 perl를 구성하는 기본 방식(그리고 마지막으로 유일한 방식)이 되는 것이 의도예요.
PUSHs()와 POPs() 같은 스택을 조작하는 매크로는 SV의 참조 카운트를 조정하지 않아요. 대부분의 경우 이것은 괜찮은데, 인자 스택에 있는 동안 다른 무언가가 SV를 살려두기 때문이에요. 예: TEMPs 스택의 포인터나 pad의 포인터(lexical 변수나 PADTMP). 가끔 이것은 엄청나게 잘못될 수 있어요. 예를 들어 이 코드:
my @a = (1,2,3);
sub f { @a = (); print "(@_)\n" };
f(@a, 4);
@a의 요소에 별칭된 @_의 일부 요소가 해제되었으므로 정의되지 않거나 임의의 해제된 값을 인쇄할 수 있어요. PERL_RC_STACK은 인자 스택의 각 SV 포인터가 SV의 참조 카운트(RC)를 1 증가시켜 이것을 고치려고 해요.
이 새 환경에서, 비-참조-카운트 스택(줄여서 non-RC)을 가정하고 작성된 수정되지 않은 기존 PP 및 XS 함수는 전후에 스택을 조정하는 특수 래퍼 함수를 통해 호출돼요. 현재는 RC XS 함수를 작성할 API가 없으므로 모든 XS 코드는 계속 래퍼를 통해 호출될 거예요(약간 느려지지만), 이는 일반적으로 XS 코드를 포함한 CPAN 배포판이 수정 없이 계속 동작한다는 뜻이에요.
하지만 PP 함수는, perl 코어에 있든 커스텀 op를 구현하거나 내장 op의 PP 함수를 오버라이드하는 XS 함수에 있든, 특별히 다뤄야 해요. 후자의 경우 그냥 래핑하면 돼요; 이는 작업이 가장 적지만 성능 영향이 있어요. 장기적으로, 그리고 코어 PP 함수의 경우, 새 API로 풀고 다시 작성해야 해요. 이를 통해 PUSHs() 같은 옛 매크로가 rpp_push_1() 같은 공통 접두사를 가진 새 (대부분 인라인) 함수 집합으로 교체됐어요. "RPP"는 "reference-counted push and pop functions"을 뜻해요. 새 함수는 PERL_RC_STACK 빌드에서 참조 카운트를 수정하고 그 외에는 수정하지 않은 채 둬요. 따라서 코어에서는 일반적으로 양쪽 모두에서 동작하고, XS 코드에서는 PPPort(XXX PPPort에 추가된다고 가정)를 통해 옛 perl 버전으로 이식 가능해요.
이 절의 나머지는 주로 기존 PP 함수를 어떻게 변환하고, 새 rpp_ API를 사용하도록 새 PP 함수를 어떻게 작성하는지에 관한 것이에요.
참조-카운트 perl은 PERL_RC_STACK define으로 빌드할 수 있어요. 개발·디버깅 목적으로 누출 스칼라 디버깅도 활성화하는 것이 가장 좋은데, 누출되었거나 조기 해제된 스칼라에 대한 추가 정보를 표시하기 때문이에요.
Configure -DDEBUGGING \
-Accflags='-DPERL_RC_STACK -DDEBUG_LEAKING_SCALARS'
참조 카운트 스택 상태
새 체제에서 현재 인자 스택은 세 가지 상태 중 하나일 수 있으며, 표시된 표현식으로 결정할 수 있어요.
- 참조-카운트되지 않음
!AvREAL(PL_curstack)
이 경우 perl은 스택을 비울 때(croak() 동안 등) 그 항목을 해제할 필요가 없다고 가정해요. 이것이 전통적인 perl 행동이에요. PERL_RC_STACK 빌드에서는 그런 스택을 거의 만나지 않을 거예요.
- 완전히 참조-카운트됨
AvREAL(PL_curstack) && !PL_curstackinfo->si_stack_nonrc_base
스택의 모든 항목이 참조 카운트되고 rpp_popfree_1() 같은 함수나 perl이 croak()하면 해제될 거예요. 이것이 PERL_RC_STACK 빌드에서 스택의 정상 상태예요.
- 부분적으로 참조-카운트됨 (분할)
AvREAL(PL_curstack) && PL_curstackinfo->si_stack_nonrc_base > 0
이 경우 인덱스 si_stack_nonrc_base 위의 스택 항목은 non-RC이고, 그 아래는 RC예요. 이 상태는 PP 또는 XS 함수가 래핑되었을 때 발생해요. 이 경우 래퍼 함수는 컷 위에 arg 포인터의 non-RC 복사본을 밀어 넣은 다음 실제 함수를 호출해요. 그 함수가 반환하면 래퍼 함수는 반환된 arg의 RC를 올려요. 아래에서 더 자세히 참조.
perl은 스택-중-스택을 사용하고 AvREAL()과 si_stack_nonrc_base 상태는 스택별이라는 점에 유의하세요. perl이 시작하면 주 스택은 RC지만, 기본적으로 PUSHSTACKi()로 XS 코드에서 밀어 넣은 새 스택은 non-RC이므로 혼합을 얻는 것이 꽤 가능해요. perl 코어 자체는 PUSHSTACKi()를 대체하고 새 스택이 기본적으로 RC여야 한다고 지정할 수 있게 해 주는 새 push_stackinfo() 함수를 사용해요. (XXX 코어는 실제로 아직 push_stackinfo()를 사용하도록 대부분 업데이트되지 않음)
코어의 대부분 위치는 특정 RC 환경을 가정해요. 특히 runops 루프 안에서 모든 PP 함수가 RC-인지, (재)작성되어 인지하든 래핑되었든, RC-인지 가정돼요. CALLRUNOPS()로 runops 루프에 들어갈 때마다 스택의 현재 상태를 확인하고 완전히 RC가 아니면 주 runops 루프에 들어가기 전에 그 내용을 임시로 완전히 RC로 업데이트해요. 그런 다음 필요하면 반환 시 스택을 옛 상태로 복원해요. 이는 어느 환경에서든(예: RC 코어 또는 래핑되고 임시로 non-RC인 XS 코드) 호출될 수 있는 call_sv() 같은 함수가, 현재 스택 상태가 무엇이든 runops 루프를 호출할 때 항상 올바른 일을 할 것이라는 뜻이에요.
마찬가지로 croak(어디서든 발생 가능) 같은 것도 두 스택 타입을 모두 처리할 수 있어야 해요. 그래서 코어에는 call_sv(), eval_sv() 등, Perl_die_unwind()와 S_my_exit_jump()라는 몇 곳이 두 경우를 모두 처리하도록 특별히 제작됐어요; 그 외 모든 것은 고정 환경을 가정할 수 있어요.
래핑 (Wrapping)
보통 코어 PP 함수는 이렇게 선언돼요:
PP(pp_foo)
{
...
}
이것은 이렇게 확장돼요:
OP* Perl_pp_foo(pTHX)
{
...
}
그런 함수를 래핑해야 할 때는 대신 이렇게 선언해요:
PP_wrapped(pp_foo, nargs, nlists)
{
...
}
non-RC 빌드에서는 PP()와 같게 확장돼요 (추가 인자가 무시됨). RC 빌드에서는 이렇게 확장돼요:
OP* Perl_pp_foo(pTHX)
{
return Perl_pp_wrap(aTHX_ S_Perl_pp_foo_norc, nargs, nlists);
}
static OP* S_Perl_pp_foo_norc(pTHX)
{
...
}
여기서 외부에서 보이는 PP 함수는 pp_wrap()을 호출하며, 그것이 스택 내용을 조정한 다음 숨겨진 PP 함수의 실제 본문을 호출하고, 반환 시 스택을 다시 조정해요.
XS 코드에 선언된 PP 함수에 사용하기 위한 API 매크로 XSPP_wrapped()가 있어요. 함수 이름에 Perl_ 접두사를 붙이지 않는다는 점을 제외하면 PP_wrapped()와 동일해요.
매크로의 nargs와 nlists 파라미터는 PP 함수가 기대하는 인자 수 또는 리스트 수를 지정하는 숫자 상수나 단순 표현식이에요. 예:
PP_wrapped(pp_add, 2, 0); /* consumes two args off the stack */
PP_wrapped(pp_readline, /* consumes one or two args */
((PL_op->op_flags & OPf_STACKED) ? 2 : 1), 0);
PP_wrapped(pp_push, 0, 1); /* consumes one list */
PP_wrapped(pp_aassign, 0, 2); /* consumes two lists */
pp_wrap()이 무엇을 하는지 이해하려면 세 인자를 기대하는 Perl_pp_foo() 호출을 고려해 보세요. 들어갈 때 스택은 이렇게 보일 수 있어요:
... A+ B+ C+
(+는 A, B, C에 대한 포인터가 각각 참조 카운트되었음을 나타내요.) 래퍼 함수 pp_wrap()은 si_stack_nonrc_base로 현재 스택 위치에 컷을 표시한 다음, nargs 값에 따라 그 세 포인터의 복사본을 컷 위에 밀어 넣어요:
... A+ B+ C+ | A0 B0 C0
(0은 포인터가 RC가 아님을 나타내요.) 그런 다음 실제 PP 함수 S_Perl_pp_foo_norc()를 호출해요. 그 함수는 A, B, C를 처리하고 스택에서 꺼내며 몇 개의 결과 SV를 밀어 넣어요. 이 조작 중 어느 것도 RC를 조정하지 않아요. pp_wrap()으로 반환하면 스택은 이렇게 보일 수 있어요:
... A+ B+ C+ | X0 Y0
래퍼 함수는 X와 Y의 RC를 올리고, A B C를 감소시키며, 결과를 아래로 옮기고 si_stack_nonrc_base를 0으로 설정해 스택을 이렇게 남겨요:
... X+ Y+
pp_entersub() 같은 곳에서 XS sub를 호출할 때 (rpp_invoke_xs() 다음 xs_wrap() 함수를 통한) 유사한 래핑이 수행돼요.
nlists가 양수이면 유사한 동작이 일어나지만, 복사해야 할 인자 수를 결정하기 위해 마크 스택이 검사(및 조정)돼요.
복잡한 호출 환경은 RC 상태가 다른 중첩 스택 여러 개를 가질 수 있어요. Perl은 RC 스택으로 시작해요. 그런 다음 예를 들어 pp_entersub()이 호출돼 (xs_wrap()을 통해) 스택을 분할하고 XS 함수를 non-RC 환경에서 실행해요. 그 함수는 PUSHSTACKi()을 호출해 새 non-RC 스택을 만들고, 그런 다음 call_sv()를 호출하며, 이것이 CALLRUNOPS()을 해 새 스택이 일시적으로 RC가 되게 해요. 그런 다음 tied 메서드가 호출돼 새 RC 스택을 밀어 넣고, 이렇게 계속돼요. (XXX 현재 tied 메서드는 실제로 non-RC 스택을 밀어 넣어요. 곧 고쳐질 것.)
rpp_() API를 사용해 PP 함수 (재)작성
PP 함수를 래핑하는 것은 성능 오버헤드가 있고 주로 임시 버팀목으로 존재해요. 결국 PP 함수는 rpp_() 함수를 사용하도록 업데이트되어야 하고, 새 PP 함수는 처음부터 이렇게 작성되어 그 래핑이 전혀 필요 없어야 해요.
변환되고 있는 코어 PP 함수의 예시를 두 개는 커밋 v5.39.1-304-g205fcd8410과 v5.39.1-303-g2fe263a83a에서 볼 수 있는데, 각각 단항과 이진 op(pp_not()과 pp_and())가 변환되는 것을 보여줘요.
전통적 PP 스택 API는 dSP 선언과 스택을 push, pop, extend하는 여러 매크로로 구성됐어요. 매우 단순화된 pp_add() 함수는 이렇게 보일 수 있어요:
PP(pp_add)
{
dSP;
dTARGET;
IV right = SvIV(POPs);
IV left = SvIV(POPs);
TARGi(left + right, 1);
PUSHs(TARG);
PUTBACK;
return NORMAL;
}
이것은 이렇게 확장돼요:
{
SV **sp = PL_stack_sp;
SV *targ = PAD_SV(PL_op->op_targ);
IV right = SvIV(*sp--);
IV left = SvIV(*sp--);
sv_setiv(targ, left + right);
*++sp = targ;
PL_stack_sp = sp;
return PL_op->op_next;
}
dSP 것 전체는 괜찮은 최적화 컴파일러 이전 시절을 떠올리게 해요. 항상 오류가 발생하기 쉬웠어요, 예를 들어 PUTBACK이나 SPAGAIN을 잊으면. 새 API는 항상 PL_stack_sp에 직접 접근해요. 사실 PP 함수를 업그레이드하는 첫 단계는 항상 dSP 선언을 제거하는 것이에요. 이것은 pp 함수에 남아 있는 sp를 암묵적으로 사용하는 옛 스타일 매크로가 컴파일 오류가 되는 즐거운 부수 효과가 있어요. 코어 어딘가에 dSP가 존재하는 것은 그 함수가 여전히 업데이트가 필요하다는 좋은 신호예요.
명백한 질문은: 왜 PUSHs() 등 매크로 정의를 수정해 RC 빌드에서 참조 카운트를 수정하게 하지 않는가예요? 기본 문제는 SV가 이제 스택에서(그리고 temps 스택에도 있는 경향이 있던 이전과 달리) 단일 참조 카운트로만 살아 있을 수 있다는 것이에요. 그래서 이런 코드에서:
SV *sv = POPs;
IV i = SvIV(sv);
POPs 매크로 정의에 SvREFCNT_dec()을 포함하면 sv는 정수 값을 읽기 전에 즉시 해제돼요.
새 체제의 잠재적 문제는 perl이 실행의 거의 어느 지점에서든 croak할 수 있다는 것이에요 (예: 위의 SvIV()이 tied 변수에 FETCH()을 호출하고 그러면 croak할 수 있음). 따라서 항상 각 SV의 RC가 적절히 계산되어야 해요. 위 예에서 sv의 조기 해제를 피하려는 순진한 접근은:
SV *sv = *PL_stack_sp--;
IV i = SvIV(sv);
SvREFCNT_dec(sv); // got i, so ok to free sv now
하지만 그것은 SvIV()이 croak을 유발하면 sv가 누수된다는 뜻이에요.
그것을 피하기 위해 새 체제는 인자가 다 쓸 때까지 스택에 남아 있다가, 그 지점에서 제거되고 참조 카운트가 조정되는 일반적인 개요를 가져요. 새 API로 pp_add() 함수는 이렇게 보여요:
{
dTARGET;
IV right = SvIV(PL_stack_sp[ 0]); // NB: arguments left on stack
IV left = SvIV(PL_stack_sp[-1]);
TARGi(left + right, 1);
rpp_replace_2_1(targ);
return NORMAL;
}
rpp_replace_2_1() 함수는 참조 카운트를 (PERL_RC_STACK으로 빌드되었는지에 따라) 적절히 조정하면서 두 값을 스택에서 꺼내고 새 값을 하나 밀어 넣어요.
새 API의 rpp_() 함수는 아래에서 자세히 설명되겠지만, 요약하면:
new function approximate old equivant
------------ -----------------------
rpp_extend(n) EXTEND(SP, n)
rpp_push_1(sv) PUSHs(sv)
rpp_push_2(sv1, sv2)) PUSHs(sv1); PUSHs(sv2)
rpp_xpush_1(sv) XPUSHs(sv)
rpp_xpush_2(sv1, sv2)) EXTEND(SP,2); PUSHs(sv1); PUSHs(sv2);
rpp_push_1_norc(sv) mPUSHs(sv) // on RC builds, skips RC++;
// on non-RC builds, mortalises
rpp_popfree_1() (void)POPs;
rpp_popfree_2() (void)POPs; (void)POPs;
rpp_popfree_to(svp) PL_stack_sp = svp;
rpp_obliterate_stack_to(ix) // see description below
sv = rpp_pop_1_norc() sv = SvREFCNT_inc(POPs)
rpp_replace_1_1(sv) (void)POPs; PUSHs(sv);
rpp_replace_2_1(sv) (void)POPs; (void)POPs; PUSHs(sv);
rpp_replace_at(sp, sv) *sp = sv;
rpp_replace_at_norc(sp, sv) *sp = sv_2mortal(sv);
rpp_context(mark, gimme,
extra) SP -= extra;
// impose void/scalar/list context on return args
SP = (gimme == G_VOID) ? mark : ....
rpp_try_AMAGIC_1() tryAMAGICun_MG()
rpp_try_AMAGIC_2() tryAMAGICbin_MG()
rpp_is_lone(sv) SvTEMP(sv) && SvREFCNT(sv) == 1
rpp_stack_is_rc() no equivalent
rpp_invoke_xs(cv) CvXSUB(cv)(aTHX_ cv);
(no replacement) dATARGET // just write the macro body in full
스택에서 제거되는 항목이 non-NULL이라고 가정하므로 약간 더 효율적인 몇 가지 _NN 변형도 있어요:
rpp_popfree_1_NN()
rpp_popfree_2_NN()
rpp_popfree_to_NN(svp)
rpp_replace_1_1_NN(sv)
rpp_replace_2_1_NN(sv)
rpp_replace_at_NN(sp, sv)
rpp_replace_at_norc_NN(sp, sv)
또한 밀어 넣거나 교체되는 단일 값이 &PL_sv_undef 같은 불멸(immortal)일 것으로 기대하는 몇 가지 _IMM 변형도 있어요 - 이것은 불멸 SV의 참조 카운트 증가를 건너뜁니다. SV의 참조 카운트가 조기에 0에 도달해도 상관없어요. sv_free2()가 그냥 되살릴 거예요. 모든 변형이 제공되는 것은 아니에요; 적합한 것이 없으면 표준 _1 버전을 쓰는 것으로 충분해요, 약간 느리긴 하지만.
rpp_push_IMM(&PL_sv_undef)
rpp_xpush_IMM(&PL_sv_zero)
rpp_replace_1_IMM_NN(&PL_sv_yes)
rpp_replace_2_IMM_NN(&PL_sv_no)
참조-카운트 스택과 관련된 다른 새 C 및 perl 함수는:
push_stackinfo(type,rc) PUSHSTACKi(type)
pop_stackinfo() POPSTACK()
switch_argstack(to) SWITCHSTACK(from,to)
(Internals::stack_refcounted() & 1) # perl built with PERL_RC_STACK
이 새 함수 중 일부는 사소하지만, RC와 non-RC 빌드 모두에서 동작하고 DEBUGGING 빌드에서 추가 검사와 assertion을 할 수 있으므로 직접 코드를 작성하는 것보다 선호돼야 해요.
rpp_popfree_1() 등은 POPs의 직접 대체물이 아니라는 점에 유의하세요. rpp_() 변형은 값을 반환하지 않으며 SV를 다 썼을 때 호출하도록 의도됐어요. 그래서
SV *sv = POPs;
... do stuff with sv ...
는 이렇게 돼요
SV *sv = *PL_stack_sp;
... do stuff with sv ...
rpp_popfree_1(); /* does SvREFCNT_dec(*PL_stack_sp--) */
rpp_replace_M_N() 함수는 M 항목을 꺼내고 해제한 다음 N 항목을 밀어 넣고 RC를 올리는 단축키예요. 옛 SV와 새 SV가 같을 수 있는 것 같은 가장자리 경우를 처리한다는 점에 유의하세요.
rpp_replace_at(sp, sv)는 스택의 상단이 아니라 스택의 한 주소에서 SV를 교체한다는 점을 제외하면 rpp_replace_1_1()과 비슷해요.
rpp_replace_at_norc(sp, sv)는 sv가 이미 참조 카운트가 올라가 있다고 가정한다는 점을 제외하면 rpp_replace_at()과 비슷해요. 그래서 아래의 rpp_push_1_norc()처럼 sv의 참조 카운트를 늘리는 데 신경 쓰지 않거나, non-RC 빌드에서 대신 mortalize해요.
rpp_popfree_to(svp)는 인자를 다 썼을 때 리스트 연산이나 스코프 나가기에서 전형적으로 나타나는 이런 코드를 교체하도록 설계됐어요:
PL_stack_sp = PL_stack_base + cx->blk_oldsp;
그대로 두면 oldsp 위의 모든 SV가 누수될 거예요. 새 접근은:
rpp_popfree_to(PL_stack_base + cx->blk_oldsp);
이것의 드물게 사용되는 변형 rpp_obliterate_stack_to()가 있는데, 스택의 현재 RC 상태와 무관하게 스택을 지정된 인덱스까지 꺼내요. 예를 들어 스택이 분할되어 있으면 분할 지점 아래의 SV의 RC만 조정하는 반면, rpp_popfree_to()는 (RC 빌드에서 어쨌든) 모든 SV를 무작정 해제할 거예요. 정상 PP 함수에는 더 빠른 rpp_popfree_to()만 사용해야 해요.
POPi()나 (으으) dPOPPOPiirl() 같은 모든 편의 매크로의 새 등가물은 없어요. 이것들은 위의 rpp_() 함수와 변환 및 변수 선언을 명시적으로 만들어 교체해야 해요. 예: dPOPPOPiirl()은 이렇게 돼요:
IV right = SvIV(PL_stack_sp[ 0]);
IV left = SvIV(PL_stack_sp[-1]);
rpp_popfree_2();
이름에 norc가 있는 몇 가지 rpp_() 함수는 RC 빌드에서 참조 카운트를 조정하지 않아요 (하지만 반대로 non-RC 빌드에서는 조정해요).
rpp_push_1_norc(sv)는 RC 빌드에서 단순 *++PL_stack_sp = sv를 해요. 보통 이미 RC가 1인 새로 생성된 SV를 "근거(루트)"시키는 데 사용돼요. non-RC 빌드에서는 SV를 대신 mortalize해요. 그래서 예를 들어 이렇게 보이던 코드:
mPUSHs(newSViv(i));
이것의 등가물로 확장됐던:
PUSHs(sv_2mortal(newSViv(i));
이렇게 재작성해야 해요:
rpp_push_1_norc(newSViv(i));
newSViv() 등이 참조 카운트가 하나 너무 높은(0이 아닌 1) 새 SV를 만들기 때문이에요. 그런 다음 이 카운트는 밀어 넣어져 스택에 "기증"돼요. 반대로 non-RC 빌드에서 카운트는 TEMPs 스택에 기증돼요.
마찬가지로 RC 빌드에서 sv = rpp_pop_1_norc()는 참조 카운트를 조정하지 않고 단순 sv = *PL_stack_sv--를 해요, non-RC 빌드에서는 실제로 SV의 참조 카운트를 증가시키지만. 꺼낸 직후에 참조 카운트를 다시 증가시키고 싶은 경우, 예를 들어 SV를 어딘가에 곧바로 임베드할 때 의도됐어요. 예를 들어 이 코드:
SV *sv = PL_stack_sp[0];
SvREFCNT_inc(sv);
av_store(av, i, sv); /* in real life should check return value */
rpp_popfree_1();
는 더 효율적으로 이렇게 쓸 수 있어요:
av_store(av, i, rpp_pop_1_norc());
이 함수를 사용하면 코드가 RC와 non-RC 빌드 모두에서 올바르게 동작해요.
리스트 연산의 공통 연산은 반환 인자에 void, scalar 또는 list 컨텍스트를 부과하는 것이며, 전부 또는 하나를 제외한 전부를 버리는 것일 수 있어요. rpp_context(mark, gimme, extra)가 이것을 해요. 첫 단계로 (편의와 효율을 위해) 개념적으로 스택에서 extra 인자를 꺼내요. 그런 다음 list 컨텍스트에서는 그대로 둬요. void 컨텍스트에서는 스택 포인터가 mark로 재설정되고 그 위의 모든 것이 꺼내져요. scalar에서는 최상위 인자(또는 &PL_sv_undef)가 상단에서 mark+1로 옮겨지고 그 위의 모든 것이 버려져요.
많은 PP 함수 시작에 나타나 단항·이진 op 오버로딩(그 외에도)을 확인하는 매크로는 rpp_try_AMAGIC_1() 및 _2() 인라인 함수로 교체됐어요. 이것들은 이제 매크로에 반환이 숨겨져 있는 것이 아니라, 호출 PP 함수가 즉시 반환할지 선택하도록 의존해요.
rpp_invoke_xs() 함수는 CV와 연관된 XS 함수를 호출하지만, 필요에 따라 스택을 조정하기 위해 래퍼 함수를 통해 그렇게 할 수 있어요.
매크로에 덜 숨기려는 정신으로 dATARGET에는 대체물이 주어지지 않았어요; 그 효과가 필요하면 이제 전체로 작성돼요; 예는 pp_add() 참조.
마지막으로 몇 가지 rpp() 함수는 스택을 조작하지 않고 정보를 제공해요.
rpp_is_lone(sv)는 스택에 여전히 있다고 가정되는 sv가 인자 및/또는 temps 스택의 단일 참조-카운트 포인터로만 살아 있는지, 따라서 일부 최적화(서브루틴 호출에서 반환 인자 복사 건너뛰기 같은)의 후보인지를 나타내요.
rpp_stack_is_rc()는 현재 스택이 현재 참조-카운트되었는지 나타내요. 주로 어디서든 호출될 수 있어 두 경우를 모두 다뤄야 하는 call_sv() 같은 몇 곳에서 사용돼요.
그래서 예를 들어 rpp_xpush_1()을 사용하는 대신 call_sv()는 이런 줄이 있어요:
rpp_extend(1);
*++PL_stack_sp = sv;
#ifdef PERL_RC_STACK
if (rpp_stack_is_rc())
SvREFCNT_inc_simple_void_NN(sv);
#endif
이것은 표준 빌드와 RC 빌드 모두에서 동작하고, call_sv()가 표준 PP 함수(rpp_stack_is_rc()가 참)에서 호출되든 래핑된 PP 또는 XS 함수(rpp_stack_is_rc()가 거짓)에서 호출되든 동작해요. PP나 XS 함수 같은 대부분 위치에서는 항상 각각 RC 또는 non-RC이므로 이 함수를 쓸 필요가 없을 거라는 점에 유의하세요. 사실 PERL_RC_STACK 아래의 디버깅 빌드에서 PUSHs()와 유사 매크로는 assert(!rpp_stack_is_rc())를 포함하는 반면, rpp_push_1()과 유사 함수는 assert(rpp_stack_is_rc())를 가져요.
새 stackinfo를 밀어 넣는 매크로는 dSP가 스코프에 있을 것에 의존하지 않고 이름이 덜 모호한 인라인 함수로 교체됐어요: 새 stackinfo가 밀어 넣어지는 것이지 어떤 종류의 stack만이 아니라. push_stackinfo()는 또한 새 인자 스택이 참조-카운트되어야 하는지 여부를 나타내는 boolean 인자를 가져요. 역호환성을 위해 PUSHSTACKi(type)은 push_stackinfo(type, 0)으로 정의돼요.
일부 테스트 스크립트는 특정 변수의 참조 카운트가 기대 값을 갖는지 테스트해 누수 같은 것을 확인해요. 이것이 PERL_RC_STACK으로 빌드된 perl에서 다르다면 perl 함수 Internals::stack_refcounted()를 사용할 수 있어요. 이것은 정수를 반환하는데, 최하위 비트가 perl이 PERL_RC_STACK으로 빌드되었음을 나타내요. 다른 비트는 향후 사용을 위해 예약되어 마스킹해야 해요.
슬래브 기반 연산자 할당
참고: 이 절은 공지 없이 변경될 수 있는 비공개 내부 API를 설명해요.
Perl의 내부 오류 처리 메커니즘은 longjmp로 die (및 내부 등가물)을 구현해요. 이게 lexing, parsing, 컴파일 중에 발생하면 컴파일 과정의 일부로 할당된 어떤 op든 해제되도록 보장해야 해요. (옛 Perl 버전은 이 상황을 적절히 처리하지 못했어요: parse 실패 시 C auto 변수에 저장되고 다른 곳에 연결되지 않은 op를 누출했지요.)
이 상황을 처리하기 위해 Perl은 현재 컴파일 중인 CV에 붙은 op slab을 사용해요. slab은 할당된 메모리 덩어리예요. 새 op는 slab의 영역으로 할당돼요. slab이 가득 차면 새 것이 생성돼요(이전 것에서 연결됨). 오류가 발생하고 CV가 해제되면 남은 op가 해제돼요.
각 op 앞에는 두 포인터가 있어요: 하나는 slab의 다음 op를 가리키고, 다른 하나는 그것을 소유하는 slab을 가리켜요. 다음-op 포인터는 Perl이 slab을 반복하고 모든 op를 해제할 수 있게 하는 데 필요해요. (Op 구조체는 크기가 다르므로 slab의 op는 그냥 밀집 배열로 취급할 수 없어요.) slab 포인터는 slab의 참조 카운트에 접근하는 데 필요해요: slab의 마지막 op가 해제되면 slab 자체도 해제돼요.
slab 할당자는 op를 slab의 끝에 먼저 넣어요. 이는 op 트리의 잎을 먼저 할당하는 경향이 있어서 레이아웃이 바라건대 캐시에 친숙할 거예요. 또한 slab 크기를 저장할 필요가 없다는 뜻이에요 (slab의 크기가 왜 다양한지 아래 참조), Perl이 포인터를 따라 마지막 op를 찾을 수 있으니까요.
모든 op가 할당 시 암묵적으로 PL_compcv에 붙고 CV가 해제될 때 해제되게 해 slab 참조 카운트를 완전히 없앨 수 있을 것처럼 보일 수 있어요. 그것은 op_free가 FreeOp를 완전히 건너뛰게 해서 op를 더 빨리 해제할 수도 있을 거예요. 하지만 re-eval 같은 op가 CV를 넘어 살아남아야 하는 경우에는 작동하지 않아요.
CV도 slab에 대한 참조 카운트를 가져야 해요. 때때로 처음 만든 op가 즉시 해제돼요. slab의 참조 카운트가 0에 도달하면 CV가 여전히 그것을 가리키는 채로 해제될 거예요.
CV는 CVf_SLABBED 플래그를 사용해 CV가 slab에 대한 참조 카운트를 가짐을 나타내요. 이 플래그가 설정되면 slab은 CvROOT이 설정되지 않았을 때 CvSTART로, 설정되었을 때 CvROOT에서 두 포인터 (2*sizeof(I32 *))를 빼 접근할 수 있어요. 컴파일 중 slab을 CvSTART에 몰래 넣는 이 접근의 대안은 xpvcv struct를 포인터 하나 더 크게 하는 것이에요. 하지만 그러면 모든 CV가 더 커질 텐데, slab 기반 op 해제는 보통 문자열 eval을 많이 사용하는 프로그램에만 이득이 되지요.
CVf_SLABBED 플래그가 설정되면 CV가 slab 해제를 책임져요. CV가 해제되거나 undef될 때 CvROOT이 설정되지 않았다면 컴파일 오류가 발생한 것으로 가정되어 op slab을 횡단하고 모든 op가 해제돼요.
정상 상황에서 CV는 루트가 붙을 때 slab을 잊어요 (참조 카운트 감소). 그래서 op가 해제될 때 일어나는 slab 참조 카운팅이 slab 해제를 돌봐요. 어떤 경우, CV는 op가 CV가 폐기된 후에도 살아남을 수 있도록 정확히 slab을 잊도록 지시받아요 (cv_forget_slab).
루트가 붙을 때 slab을 잊는 것은 엄격히 필요하지 않지만 CvROOT이 덮여 쓰여지는 잠재적 문제를 피해요. 코어와 CPAN 어디에나 CvROOT으로 무언가를 하는 코드가 있어서, slab을 잊는 것이 더 견고하게 만들고 잠재적 문제를 피해요.
CV가 플래그될 때 slab의 소유권을 가지므로, 한 CV가 다른 CV가 여전히 가리키는 slab을 해제할 수 있기 때문에(op 강제 해제가 참조 카운트를 무시하지만 올바른 것처럼 보이는지 assert하기 때문에) 그 플래그는 CV가 클론될 때 절대 복사되지 않아요.
slab 단편화를 피하기 위해 해제된 op는 해제된 것으로 표시되고 slab의 해제 체인에 붙어요 (DBM::Deep에서 훔친 아이디어). 그 해제된 op는 가능하면 재사용돼요. 해제된 op를 재사용하지 않는 것이 더 단순하겠지만, 큰 if (DEBUG) {...} 블록이 있는 프로그램에서 상당히 더 높은 메모리 사용으로 이어질 거예요.
SAVEFREEOP는 이 체계 아래서 약간 문제가 있어요. 때때로 op가 그 CV 다음에 해제되게 할 수 있어요. CV가 slab과 slab 자체의 op를 강제로 해제했다면 해제된 slab을 만지작거리게 될 거예요. SAVEFREEOP를 no-op으로 만드는 것은 도움이 안 돼요, 때때로 컴파일 오류가 없을 때 op가 savefree될 수 있어 그 op가 절대 해제되지 않을 것이기 때문이에요. 그것은 slab에 대한 참조 카운트를 보유하므로 전체 slab이 누출될 거예요. 그래서 SAVEFREEOP는 이제 op에 특수 플래그(->op_savefree)를 설정해요. 컴파일 오류 후 op를 강제 해제하는 것은 그렇게 표시된 op를 해제하지 않아요.
많은 코드 조각이 몇 개의 op만으로 구성된 작은 서브루틴을 만들고, 거대한 slab은 그것들이 들고 다니기에 꽤 많은 수하물이 될 것이므로 첫 slab은 항상 매우 작아요. 단일 CV에 너무 많은 slab을 할당하지 않기 위해 각 후속 slab은 이전 것의 두 배 크기예요.
Smartmatch는 런타임에 op를 할당하고, 실행하고, 버릴 수 있기를 기대해요. 그것이 작동하려면 PL_compcv가 설정되지 않았을 때 op는 단순히 malloc됩니다. 그래서 모든 slab-할당 op는 malloc된 op와 구별하기 위해 그렇게 표시돼요(->op_slabbed).
AUTHORS
1997년 5월까지 이 문서는 Jeff Okamoto [email protected]가 유지했어요. 이제 Perl 자체의 일부로 Perl 5 Porters [email protected]가 유지해요.
Dean Roehrich, Malcolm Beattie, Andreas Koenig, Paul Hudson, Ilya Zakharevich, Paul Marquess, Neil Bowers, Matthew Green, Tim Bunce, Spider Boardman, Ulrich Pfeifer, Stephen McCamant, Gurusamy Sarathy의 많은 도움과 제안이 있었어요.