2012-11-08 2 views
3

UTF8 문자열을 UTF16 (리틀 엔디안)으로 변환하는 함수를 작성하고 싶습니다. 문제는 iconv 함수가 출력 문자열을 저장하는 데 필요한 바이트 수를 미리 알 수없는 것입니다.iconv를 사용한 간단한 UTF8-> UTF16 문자열 변환

static int utf8_to_utf16le(char *utf8, char **utf16, int *utf16_len) 
{ 
    iconv_t cd; 
    char *inbuf, *outbuf; 
    size_t inbytesleft, outbytesleft, nchars, utf16_buf_len; 

    cd = iconv_open("UTF16LE", "UTF8"); 
    if (cd == (iconv_t)-1) { 
     printf("!%s: iconv_open failed: %d\n", __func__, errno); 
     return -1; 
    } 

    inbytesleft = strlen(utf8); 
    if (inbytesleft == 0) { 
     printf("!%s: empty string\n", __func__); 
     iconv_close(cd); 
     return -1; 
    } 
    inbuf = utf8; 
    utf16_buf_len = 2 * inbytesleft;   // sufficient in many cases, i.e. if the input string is ASCII 
    *utf16 = malloc(utf16_buf_len); 
    if (!*utf16) { 
     printf("!%s: malloc failed\n", __func__); 
     iconv_close(cd); 
     return -1; 
    } 
    outbytesleft = utf16_buf_len; 
    outbuf = *utf16; 

    nchars = iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft); 
    while (nchars == (size_t)-1 && errno == E2BIG) { 
     char *ptr; 
     size_t increase = 10;     // increase length a bit 
     size_t len; 
     utf16_buf_len += increase; 
     outbytesleft += increase; 
     ptr = realloc(*utf16, utf16_buf_len); 
     if (!ptr) { 
      printf("!%s: realloc failed\n", __func__); 
      free(*utf16); 
      iconv_close(cd); 
      return -1; 
     } 
     len = outbuf - *utf16; 
     *utf16 = ptr; 
     outbuf = *utf16 + len; 
     nchars = iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft); 
    } 
    if (nchars == (size_t)-1) { 
     printf("!%s: iconv failed: %d\n", __func__, errno); 
     free(*utf16); 
     iconv_close(cd); 
     return -1; 
    } 

    iconv_close(cd); 
    *utf16_len = utf16_buf_len - outbytesleft; 

    return 0; 
} 

이 그것을 할 수있는 가장 좋은 방법 정말 :

내 솔루션 realloc 필요한 경우와 그 버퍼의 크기를 증가 2*strlen(utf8)을 할당하여 시작한 다음 루프에서 iconv을 실행하는 것입니다? 반복 된 realloc s는 낭비 적이지만 utf8에 어떤 문자 시퀀스가 ​​있을지 모르고 utf16에서 어떤 결과가 나올지 모르는 상태에서 2*strlen(utf8)보다 초기 버퍼 크기를 더 잘 예측할 수 있는지 알 수 없습니다.

답변

4

올바른 방법은 iconv입니다.

iconv은 임의의 문자 인코딩에서 다른 임의의 문자 인코딩으로 코드를 다시 작성할 수 있도록 설계되었습니다. 모든 조합을 지원합니다. 이 점을 감안할 때 기본적으로 얼마나 많은 공간을 출력해야 하는지를 알 수있는 2 가지 방법 만 있습니다.

  • 짐작하십시오. 전환을 수행하고 필요한 경우 추측을 늘리십시오.
  • 변환을 두 번 수행하십시오. 처음으로 출력을 무시하고 계산합니다. 계산 한 총 공간을 할당 한 다음 다시 변환하십시오.
  • 첫 번째 작업은 수행 한 작업입니다. 두 번째 것은 분명히 두 번 일을해야한다는 단점이 있습니다. (그런데 iconv으로 로컬 변수의 스크래치 패드 버퍼를 첫 번째 패스의 출력 버퍼로 사용하여 두 번째 방법을 수행 할 수 있습니다.)

    정말 다른 방법은 없습니다. 미리 입력에 얼마나 많은 문자 (바이트가 아님)가 있는지, 얼마나 많은 문자가 BMP에 없는지 알고 있습니다. 그렇지 않으면 당신은 그들을 세어야합니다.

    이 경우 사전에 입력 및 출력 인코딩이 무엇인지 알게됩니다. 시작하기 전에 입력 문자열에 UTF-8 체조를 수행하는 경우 필요한 출력 버퍼 공간을 추측 할 수 있습니다. 위의 두 번째 옵션과 조금 비슷하지만 필요한 UTF-8 체조가 만발한 iconv만큼 비싸지 않기 때문에 더욱 최적화되었습니다.

    그래도 그렇게하지 않는 것이 좋습니다. 당신은 여전히 ​​입력 문자열에 대해 두 번 통과시켜 저장하지 않을 것이므로 많은 코드를 작성할 수 있으며, 버퍼가 작을 수있는 버퍼의 가능성을 소개합니다. 체조가 옳지 않습니다.

    실제로 신체적 인 문제는 UTF-8 디코더를 구현하는 것이기 때문에 체조를 설명하지 않을 것입니다. 핵심은 비트 마스킹 및 이동의 간단한 몇 가지 사례 일 뿐이지 만, 보안에 영향을 미치는 방식으로 잘못된 시퀀스를 거부하는 것과 관련된 세부 사항이 있습니다. 그러지 마라.

    5

    UTF-8을 UTF-16으로 변환하면 결코 데이터 크기의 두 배가되지 않습니다. 최악의 경우는 ASCII (1 -> 2 바이트)입니다. UTF-8의 다른 모든 BMP 코드 포인트는 2 또는 3 바이트를 사용하므로 UTF-16으로 변환하면 크기가 같아 지거나 작아집니다. 비 BMP 코드 포인트는 UTF-8 또는 UTF-16에서 정확히 4 바이트입니다.

    따라서 낭비적이고 복잡하며 오류가 발생하기 쉬운 realloc 논리를 제거하여 버퍼를 확장 할 수 있습니다.

    그런데 널 종료를위한 공간은 strlen으로 계산되지 않도록하십시오.

    +0

    좋은 점은'strlen'이지만, 제 경우에는 출력 문자열에 널 종료 입력 문자열과 종료되지 않은 버퍼 + 길이를 원했습니다. 나는 분명히하지 않았다. – craig65535