1바이트 문자열 타입
1바이트 문자열 타입 (Single-byte String types)
문자열은 Pascal에서 가장 자주 쓰는 타입 중 하나인데, Free Pascal은 상황에 따라 short string과 ansistring이라는 두 종류의 1바이트 문자열 타입을 제공해요. 어떤 차이가 있는지, 그리고 코드 페이지가 왜 중요한지를 이해하면 문자열을 훨씬 자신 있게 다룰 수 있어요.
본문
Free Pascal은 Turbo Pascal에서 정의된 것과 같은 String 타입을 지원해요. 이것은 선택적인 크기 지정이 있는 1바이트 문자들의 시퀀스예요. 또한 (길이 제한이 없는) ansistring과, Delphi처럼 코드 페이지 정보도 지원해요.
변수를 문자열로 선언하려면 다음 타입 지정을 사용해요.
var
A : String;
크기 지정자(대괄호 사용)가 있으면, 문자열의 최대 크기를 나타내는 최댓값이 255예요. 코드 페이지 지정자(괄호 사용)가 있으면, 관련 코드 페이지 정보가 있는 ansistring을 나타내요.
크기와 코드 페이지 지정 없이 문자열을 선언한 문장의 의미는 {$H} 스위치에 따라 다르게 해석돼요. 크기와 코드 페이지 지정이 없으면 위 선언은 ansistring 또는 short string을 선언할 수 있어요.
실제 타입이 무엇이든 1바이트 문자열은 서로 교환해서 사용할 수 있어요. 컴파일러가 항상 필요한 타입 변환을 처리해 줘요. 단, ansistring과 short string을 포함하는 표현식의 결과는 항상 ansistring이라는 점에 주의하세요.
Short strings
ShortString은 최대 길이 255의 문자열 타입이에요. 다음 경우에 문자열 선언은 short string을 선언해요.
$H스위치가 꺼져 있으면({$H-}) 문자열 선언은 항상 short string 선언이 돼요.- 스위치가 켜져 있고(
{$H+}) 최대 길이(크기) 지정자가 있으면 short string 선언이 돼요.
short string은 항상 시스템 코드 페이지를 사용한다고 가정해요. 미리 정의된 ShortString 타입은 크기 255의 문자열로 정의돼 있어요.
ShortString = String[255];
문자열 크기를 지정하지 않으면 기본값으로 255가 사용돼요. 실제 문자열 길이는 표준 런타임 루틴인 Length로 얻을 수 있어요.
{$H-}
Type
NameString = String[10];
StreetString = String;
예를 들어 위에서 NameString은 최대 10자를 담을 수 있고, StreetString은 최대 255자를 담을 수 있어요.
참고 short string의 최대 길이는 255자예요. 최대 길이를 지정할 때 255를 초과할 수 없어요. 255보다 큰 길이를 시도하면 컴파일러가 다음 오류 메시지를 줘요.
Error: string length must be a value from 1 to 255
short string의 길이는 인덱스 0의 문자에 저장돼요. 예전 Turbo Pascal 코드는 이것에 의존하며, Free Pascal에서도 비슷하게 구현돼 있어요.
그럼에도 이식성 있는 코드를 작성하려면 short string의 길이는 SetLength 호출로 설정하고 Length 호출로 검색하는 것이 가장 좋아요. 이 함수들은 현재 사용 중인 short string이나 다른 문자열의 내부 표현이 무엇이든 항상 동작하므로, 다양한 문자열 타입 사이를 쉽게 전환할 수 있게 해줘요.
Ansistrings
ansistring은 길이 제한이 없고 관련 코드 페이지가 있는 문자열이에요. 참조 카운트(reference counted)되며 null 종결이 보장돼요.
내부적으로 ansistring은 포인터로 취급돼요. 실제 문자열 내용은 힙(heap)에 저장되며, 문자열 내용을 저장하는 데 필요한 만큼 메모리가 할당돼요.
선언에 코드 페이지가 없으면 시스템 코드 페이지가 가정돼요. 그 코드 페이지가 무엇인지는 system 유닛의 DefaultSystemCodePage 상수로 결정돼요. 이 모든 것은 투명하게 처리되므로 일반 short string처럼 조작할 수 있어요. ansistring은 미리 정의된 AnsiString 타입이나 {$H+} 모드의 string 키워드로 정의할 수 있어요.
참고 null 종결이 null 문자(
char(0)또는#0)를 쓸 수 없다는 뜻은 아니에요. null 종결은 내부적으로 사용되지는 않고, null 종결 문자열을 기대하는 외부 루틴(대부분의 C 루틴처럼)을 다룰 때 편의를 위해 있는 거예요.
{$H} 스위치가 켜져 있으면, 길이 지정자가 없는 일반 String 키워드를 사용한 문자열 정의도 ansistring으로 간주돼요. 길이 지정자가 있으면 {$H} 설정과 무관하게 short string이 사용돼요.
문자열이 비어 있으면('') 문자열 포인터의 내부 포인터 표현은 Nil이에요. 문자열이 비어 있지 않으면 포인터는 힙 메모리의 구조를 가리켜요.
포인터로서의 내부 표현과 자동 null 종결 덕분에 ansistring을 pchar로 타입캐스트할 수 있어요. 문자열이 비어 있으면(그래서 포인터가 Nil이면) 컴파일러는 타입캐스트된 pchar가 null 바이트를 가리키도록 해줘요.
한 ansistring을 다른 ansistring에 할당하는 것은 실제 문자열을 옮기는 것이 아니에요.
S2:=S1;
S2:=S1;이라는 문장은 결과적으로 S2의 참조 카운트가 1 감소하고, S1의 참조 카운트가 1 증가하며, 마지막으로 S1(포인터로서)이 S2에 복사돼요. 이는 코드에서 상당한 속도 향상이에요.
문자열의 참조 카운트가 0에 도달하면 문자열이 차지한 메모리가 자동으로 할당 해제되고 포인터는 Nil로 설정돼요. 따라서 메모리 누수가 발생하지 않아요.
ansistring이 선언될 때 Free Pascal 컴파일러는 처음에는 포인터를 위한 메모리만 할당하고 그 이상은 할당하지 않아요. 이 포인터는 Nil임이 보장되며, 따라서 문자열은 처음에 비어 있어요. 이는 지역·전역 ansistring이나 구조(배열, 레코드, 객체)의 일부인 ansistring에 모두 해당돼요.
참고 함수 결과는 이 관점에서 var 파라미터와 동등한 것으로 간주되므로
Nil로 초기화되지 않아요. 그 결과 함수가 시작할 때 합법적인 비-Nil ansistring을 가리킬 수 있어요.
이것은 오버헤드를 도입해요. 예를 들어 다음처럼 선언하면
Var
A : Array[1..100000] of string;
Nil 값을 A에 100,000번 복사해요. A가 스코프를 벗어나면 100,000개 문자열 각각의 참조 카운트가 1씩 감소해요. 이 모든 것은 프로그래머에게 보이지 않게 일어나지만, 성능을 고려할 때는 중요해요.
문자열 내용을 위한 메모리는 문자열에 값이 할당될 때만 할당돼요. 문자열이 스코프를 벗어나면 참조 카운트가 자동으로 1 감소하고, 0에 도달하면 예약된 메모리가 해제돼요.
참조 카운트가 1보다 큰 문자열의 문자에 값을 할당하면, 예를 들어
S:=T; { reference count for S and T is now 2 }
S[I]:='@';
할당 전에 문자열의 사본이 만들어져요. 이것을 copy-on-write 의미론이라고 해요. UniqueString 호출로 문자열의 참조 카운트를 강제로 1로 만들 수 있어요.
S:=T;
R:=T; // Reference count of T is at least 3
UniqueString(T);
// Reference count of T is guaranteed 1
예를 들어 ansistring을 PChar var로 타입캐스트해서 문자열을 수정하는 C 루틴에 넘길 때 이렇게 하는 것이 좋아요.
ansistring의 길이를 얻으려면 Length 함수를 사용해야 해요. 길이는 ansistring의 0번째 문자에 저장되지 않아요.
L:=ord(S[0]);
Turbo Pascal shortstring에서는 유효했던 이 구문은 ansistring에는 더 이상 올바르지 않아요. 컴파일러는 이런 구문을 만나면 경고를 줄 거예요.
ansistring의 길이를 설정하려면 SetLength 함수를 사용해야 해요. 상수 ansistring은 참조 카운트가 -1이며 특별하게 취급돼요. Length에 대한 것과 같은 참고 사항이 적용돼요.
L:=12;
S[0]:=Char(L);
Turbo Pascal shortstring에서는 유효했던 이 구문은 ansistring에는 올바르지 않아요. 컴파일러는 경고를 줄 거예요.
ansistring은 필요하면 컴파일러가 short string으로 변환해요. 즉 ansistring과 short string의 사용을 문제없이 섞을 수 있어요.
ansistring은 PChar 또는 Pointer 타입으로 타입캐스트할 수 있어요.
Var P : Pointer;
PC : PChar;
S : AnsiString;
begin
S :='This is an ansistring';
PC:=Pchar(S);
P :=Pointer(S);
두 타입캐스트 사이에는 차이가 있어요. 빈 ansistring을 포인터로 타입캐스트하면 포인터는 Nil이 돼요. 빈 ansistring을 PChar로 타입캐스트하면 결과는 0바이트(빈 문자열)를 가리키는 포인터가 돼요.
이런 타입캐스트의 결과는 주의해서 사용해야 해요. 일반적으로 이런 타입캐스트의 결과는 읽기 전용으로 간주하는 것이 가장 좋아요. 즉 상수 pchar 인자가 필요한 프로시저에 전달하기에만 적합해요.
따라서 다음을 타입캐스트하는 것은 권장하지 않아요. ① 표현식(Expressions), ② 참조 카운트가 1보다 큰 문자열. 이 경우 문자열이 참조 카운트 1을 갖도록 Uniquestring을 호출해야 해요.
코드 페이지 변환 (Code page conversions)
문자열에는 코드 페이지 정보가 연결되어 있으므로, 문자열이 어느 코드 페이지를 쓰는지 아는 것이 중요해요.
- Short string은 항상 시스템 코드 페이지를 사용해요.
- 일반 ansistring은 시스템 코드 페이지를 사용해요.
- 선언된 코드 페이지가 있는 1바이트 문자열은 그 코드 페이지를 사용해요.
RawBytestring타입은 관련 코드 페이지 정보가 없어요.- 상수 문자열은 소스 파일의 코드 페이지를 가져요. 지정되지 않으면 시스템 코드 페이지(CP_ACP)가 사용돼요. (Programmers Guide의
{$CODEPAGE }지시어 참고)
이 코드 페이지를 선언된 코드 페이지(declared code page)라고 해요.
컴파일러는 필요에 따라 문자열의 코드 페이지를 변환해요. 문자열을 할당할 때, 선언된 소스·대상 코드 페이지가 다르면 소스 문자열의 실제 코드 페이지가 대상 문자열의 선언된 코드 페이지로 변환돼요.
특정 규칙은 복잡하지만 핵심은 이래요. 코드 페이지가 CODE_CP인 파일에서 선언된 코드 페이지 SOURCE_CP의 문자열을 선언된 코드 페이지 DEST_CP의 문자열에 할당할 때:
SOURCE_CP=CP_NONE또는DEST_CP=CP_NONE이면 RawByteString을 참고해요.CODE_CP ≠ CP_ACP라면,DEST_CP=CP_ACP이고SOURCE_CP=CODE_CP(또는 그 반대)면 런타임에DefaultSystemCodePage가 SOURCE_CP와 다른 값을 갖더라도 변환이 일어나지 않아요. 이 (CODE_CP ≠ CP_ACP) 조건은 이전 FPC 버전과의 하위 호환성 때문이에요. 이전 버전은 임의 코드 페이지의 AnsiString을 지원하지 않았지만, 항상 시스템 코드 페이지의 현재 값에 따라 AnsiString을 재해석했어요.- 그 외에
SOURCE_CP ≠ DEST_CP면 할당 전에 문자열 데이터가 SOURCE_CP에서 DEST_CP로 변환되며, CP_ACP는DefaultSystemCodePage의 현재 값으로 해석돼요. - 그 외에
SOURCE_CP = DEST_CP면 코드 페이지 변환이 수행되지 않아요.
이 규칙은 AnsiString 변수가 선언된 코드 페이지와 다른 코드 페이지를 얻게 되는 것이 전적으로 가능하다는 뜻이에요. 예를 들어 세 번째 경우 SOURCE_CP가 CP_ACP일 수 있고, 할당 후에는 DefaultSystemCodePage와 같은 동적 코드 페이지를 가질 수 있어요.
참고: 위에서 언급했듯이 코드 페이지 변환이 일어날지는 관련 문자열의 선언된 코드 페이지에만 달려 있어요. 즉 AnsiString(X)을 다른 AnsiString(X)에 할당했는데 전자의 동적 코드가 X와 달랐다면, 할당으로 문자열 데이터가 코드 페이지 X로 변환되지는 않아요.
이 모든 것은 다음 코드에서를 의미해요.
{$h+}
uses sysutils;
Type
TString1 = Type String(1252);
TString2 = Type String(1251);
Var
A : TString1;
B : TString2;
begin
A:='123'+'345'+intToStr(123);
B:=A;
Writeln('B: "',B,'" : ',StringRefCount(B),' -> ',StringCodePage(B));
Writeln('A: "',A,'" : ',StringRefCount(A),' -> ',StringCodePage(A));
end.
이것은 다음을 출력해요.
B: "123345123" : 1 -> 1251
A: "123345123" : 1 -> 1252
A를 B에 할당하는 동안 문자열 A의 내용은 문자열 B의 코드 페이지로 변환돼요. 코드 페이지 변환이 일어나면 참조 카운트 메커니즘이 사용되지 않는 것에 주의하세요. 새 문자열이 할당될 거예요.
이 자동 코드 페이지 변환은 코드를 심각하게 느리게 만들 수 있어요. 따라서 코드 페이지 변환을 최소한으로 제한하도록 주의해야 해요.
문자열의 코드 페이지는 system 유닛의 SetCodePage 루틴으로 명시적으로 설정할 수 있어요. 이 루틴을 호출하면 문자열의 값을 요청된 코드 페이지로 변환해요.
참고 코드 페이지 변환은 데이터 손실을 일으킬 수 있어요. 특정 문자가 대상 코드 페이지에서 표현될 수 없으면 그 문자의 출력은 정의되지 않아요.
참고 정적 코드 페이지가 소스 파일 코드 페이지와 같은 문자열을 코드 페이지 CP_ACP(즉 일반 ansistring, shortstring, pchar)로 할당할 때도 변환이 수행되지 않아요.
참고 코드 페이지 지원은 꽤 많은 헬퍼 루틴을 요구하며, 이것들은 unicodestring manager에 구현돼 있어요. Windows에서는 시스템 루틴이 사용돼요. Unix 계열에서는
cwstring유닛을 사용해 C 라이브러리에 링크하고 C 라이브러리 변환 지원을 사용할 수 있어요. 또는fpwidestring유닛에 Object Pascal로 네이티브 구현된 unicodestring manager가 들어 있어요.
RawByteString
미리 정의된 RawByteString 타입은 코드 페이지 정보가 없는(CP_NONE) ansistring 문자열 타입이에요.
Type
RawByteString = type ansistring(CP_NONE);
이 타입은 특별하게 취급돼요. 변환 루틴이 소스·대상 문자열에서 CP_NONE을 만나면 코드 페이지 변환이 수행되지 않고 소스 문자열의 코드 페이지가 보존돼요. 이런 이유로 system과 sysutils 유닛의 대부분 1바이트 문자열 루틴이 RawByteString 타입을 사용해요.
UTF8String
1바이트 코드 페이지 문자열은 그 코드 페이지에서 사용 가능한 문자만 저장할 수 있어요. 코드 페이지에 없는 문자는 그 문자열로 표현할 수 없어요. UTF-8 유니코드 인코딩은 1바이트 문자열과 함께 사용할 수 있는 인코딩이에요. 이 인코딩에서 ASCII 문자(서수값 < 128)는 CP_ACP 인코딩과 정확히 매핑돼요. 이 사실을 이용해 모든 문자를 담을 수 있는 1바이트 문자열 타입을 정의해요.
Type
UTF8String = type AnsiString(CP_UTF8);
UTF8String 문자열 타입은 모든 유니코드 문자를 표현하는 데 사용할 수 있어요. 하지만 이 강력함에는 대가가 있어요. 유니코드 문자는 UTF-8 인코딩에서 표현하는 데 여러 바이트가 필요할 수 있으므로, UTF8String을 사용할 때 두 가지를 주의해야 해요.
- 특정 위치에서 바이트 크기 char를 가져오는 문자 인덱스는 주의해서 사용해야 해요.
S[i]표현식이 UTF8String 타입 문자열 S에서 반드시 유효한 문자는 아니에요. - 문자열의 바이트 길이는 문자열의 문자 수와 같지 않아요. 표준 함수
length는 문자 길이를 얻는 데 쓸 수 없고 항상 바이트 길이를 반환해요.
다른 모든 코드 페이지에서는 1바이트 코드 페이지 문자열의 문자 수가 문자열의 바이트 길이와 같아요.
1: Free Pascal 버전 3.0부터 2: 코드 페이지는 Free Pascal 버전 3.0에서 도입됨
더 알아보기
- 2바이트 이상 문자를 다루는 문자열은 "Multi-byte String types" 문서를 참고해요.
- 상수 문자열을 쓰는 방법은 "Constant strings" 문서에서 확인해요.
- 코드 페이지 지시어에 대한 상세 내용은 Free Pascal Programmer's Guide를 참고해요.