В чем разница между этими двумя версиями кода (арифметика указателей и юникод)? - PullRequest
0 голосов
/ 16 февраля 2009

Я отлаживаю некоторый код с открытым исходным кодом в 64-битной системе Solaris, используя GCC, который преобразует 2-байтовые символы (wchar_t) в 4-байтовые символы (wchar_t). Поскольку Solaris, как и некоторые другие Unix, определяет wchar_t как 4 байта, а не 2 байта, как в Windows.

Теперь я исправил проблему, разместив арифметику указателя над двумя строками, но я не уверен, что не так с исходным кодом. Любые подсказки?

Оригинальный код

int StringCopy2to4bytes(const unsigned short* src, int src_size, 
                         unsigned int* dst, int dst_size)
{
 int cp_size = 0;

 const unsigned short *src_end = NULL;
 const unsigned int   *dst_end = NULL;

 unsigned int c1, c2;

 src_end = src + src_size;
 dst_end = dst + dst_size;

 while (src < src_end)
 {
     c1 = *src++;
     if ((c1 >= UNI_SUR_HIGH_START) && (c1 <= UNI_SUR_HIGH_END))
     {
         if (src < src_end)
         {
             c2 = *src;
             if ((c2 >= UNI_SUR_LOW_START) && (c2 <= UNI_SUR_LOW_END))
             {
                c1 = ((c1 - UNI_SUR_HIGH_START) << UNI_SHIFT) + 
                      (c1 - UNI_SUR_LOW_START )  + UNI_BASE;

                ++src;
             }
         } 
         else 
             return -1;
     } 

     if (dst >= dst_end) return -2;

     *dst++ = c1;

     cp_size++;
 }

 return cp_size;
}

Фиксированный код

int StringCopy2to4bytes(const unsigned short* src, int src_size, 
                         unsigned int* dst, int dst_size)
{
 int cp_size = 0;

 const unsigned short *src_end = NULL;
 const unsigned int   *dst_end = NULL;

 unsigned int c1, c2;

 src_end = src + src_size;
 dst_end = dst + dst_size;

 while (src < src_end)
 {
     c1 = *src; //FIX
     ++src;

     if ((c1 >= UNI_SUR_HIGH_START) && (c1 <= UNI_SUR_HIGH_END))
     {
         if (src < src_end)
         {
             c2 = *src;
             if ((c2 >= UNI_SUR_LOW_START) && (c2 <= UNI_SUR_LOW_END))
             {
                c1 = ((c1 - UNI_SUR_HIGH_START) << UNI_SHIFT) + 
                      (c1 - UNI_SUR_LOW_START )  + UNI_BASE;

                ++src;
             }
         } 
         else 
             return -1;
     } 

     if (dst >= dst_end) return -2;

     *dst = c1; //FIX
     ++dst;

     cp_size++;
 }

 return cp_size;
}

Редактировать : для записи, код не мой, я просто использую его, и я отлаживаю его, не то, чтобы это имело большое значение, но источник довольно большой , поэтому я пытаюсь исправить это с помощью пинцета, как это может быть, не рефакторинг всего, в любом случае ошибки являются ошибками, и мне нужно исправить это и отправить автору сообщение о том, что было не так.

Константы:

/* unicode constants */
#define UNI_SHIFT             ((int) 10 )
#define UNI_BASE              ((unsigned int) 0x0010000UL)
#define UNI_MASK              ((unsigned int) 0x3FFUL)
#define UNI_REPLACEMENT_CHAR  ((unsigned int) 0x0000FFFD)
#define UNI_MAX_BMP           ((unsigned int) 0x0000FFFF)
#define UNI_MAX_UTF16         ((unsigned int) 0x0010FFFF)
#define UNI_MAX_UTF32         ((unsigned int) 0x7FFFFFFF)
#define UNI_MAX_LEGAL_UTF32   ((unsigned int) 0x0010FFFF)
#define UNI_SUR_HIGH_START    ((unsigned int) 0xD800)
#define UNI_SUR_HIGH_END      ((unsigned int) 0xDBFF)
#define UNI_SUR_LOW_START     ((unsigned int) 0xDC00)
#define UNI_SUR_LOW_END       ((unsigned int) 0xDFFF)

Ответы [ 3 ]

4 голосов
/ 16 февраля 2009

Код, как написано здесь, все еще глючит - когда вы комбинируете c1 и c2, вам нужно использовать c2! То есть в строках:

c1 = ((c1 - UNI_SUR_HIGH_START) << UNI_SHIFT) +
      (c1 - UNI_SUR_LOW_START ) + UNI_BASE;

Третье вхождение c1 на самом деле должно быть c2.

Кроме того, кажется глупым инициализировать указатель src_end для нуля, а затем для src + src_size. Почему бы не добраться туда сразу?

Кроме того, cp_size может быть избыточным, если начало строки было сохранено; тогда он будет таким же, как (dst - initial_dst).


Тестовый код - с исправлением c1 - c2 - с использованием первого примера кода на Solaris 10 с GCC 4.3.3. Результаты для 32-битной и 64-битной компиляции показаны. Данные из таблицы 3.4 в главе 3 стандарта Unicode (технически Unicode 5.0, а не 5.1.0, но я не думаю, что это имеет значение).

enum { NULL = 0 };
enum { UNI_SUR_HIGH_START = 0xD800, UNI_SUR_HIGH_END = 0xDBFF,
       UNI_SUR_LOW_START  = 0xDC00, UNI_SUR_LOW_END  = 0xDFFF,
       UNI_SHIFT = 10, UNI_BASE = 0x10000 };

int StringCopy2to4bytes(const unsigned short* src, int src_size, 
                         unsigned int* dst, int dst_size)
{
 int cp_size = 0;

 const unsigned short *src_end = NULL;
 const unsigned int   *dst_end = NULL;

 unsigned int c1, c2;

 src_end = src + src_size;
 dst_end = dst + dst_size;

 while (src < src_end)
 {
     c1 = *src++;
     if ((c1 >= UNI_SUR_HIGH_START) && (c1 <= UNI_SUR_HIGH_END))
     {
         if (src < src_end)
         {
             c2 = *src;
             if ((c2 >= UNI_SUR_LOW_START) && (c2 <= UNI_SUR_LOW_END))
             {
                c1 = ((c1 - UNI_SUR_HIGH_START) << UNI_SHIFT) + 
                      (c2 - UNI_SUR_LOW_START )  + UNI_BASE;    /* Fixed */

                ++src;
             }
         } 
         else 
             return -1;
     } 

     if (dst >= dst_end) return -2;

     *dst++ = c1;

     cp_size++;
 }

 return cp_size;
}

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    unsigned short w2_chars[] = { 0x004D, 0x0430, 0x4E8C, 0xD800, 0xDF02, 0x004D };
    unsigned int   w4_wanted[] = { 0x00004D, 0x000430, 0x004E8C, 0x010302, 0x00004D };
    unsigned int   w4_actual[5];
    int w2_len = 6;
    int w4_len = 5;
    int w4_actlen;
    int i;
    int failed = 0;

    w4_actlen = StringCopy2to4bytes(w2_chars, w2_len, w4_actual, w4_len);
    if (w4_actlen != w4_len)
    {
        failed = 1;
        printf("Length mismatch: got %d, wanted %d\n", w4_actlen, w4_len);
    }
    for (i = 0; i < w4_len; i++)
    {
        if (w4_actual[i] != w4_wanted[i])
        {
            printf("Mismatch: index %d: wanted 0x%06X, actual 0x%06X\n",
                   i, w4_wanted[i], w4_actual[i]);
            failed = 1;
        }
    }
    if (failed == 0)
        printf("No problem observed\n");
    return((failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
}


$ gcc -m32 -O utf.c -o utf32 && ./utf32
No problem observed
$ gcc -m64 -O utf.c -o utf64 && ./utf64
No problem observed
$

Мне интересно, что случилось с вашим компилятором или с вашим тестовым примером.


Вот пересмотренная версия функции StringCopy2to4bytes (). Он обнаруживает и сообщает об ошибке, которой не было в оригинале, а именно, когда второе слово суррогатной пары не является допустимым низким суррогатным кодом, он возвращает статус -3.

int StringCopy2to4bytes(const unsigned short *src, int src_size, 
                        unsigned int *dst, int dst_size)
{
    const unsigned short *src_end = src + src_size;
    const unsigned int   *dst_end = dst + dst_size;
    const unsigned int   *dst0    = dst;

    while (src < src_end)
    {
        unsigned int c1 = *src++;
        if ((c1 >= UNI_SUR_HIGH_START) && (c1 <= UNI_SUR_HIGH_END))
        {
            if (src >= src_end)
                return -1;
            unsigned int c2 = *src++;
            if ((c2 >= UNI_SUR_LOW_START) && (c2 <= UNI_SUR_LOW_END))
            {
               c1 = ((c1 - UNI_SUR_HIGH_START) << UNI_SHIFT) + 
                     (c2 - UNI_SUR_LOW_START )  + UNI_BASE;    /* Fixed */
            }
            else
                return -3;  /* Invalid second code point in surrogate pair */
        } 
        if (dst >= dst_end)
            return -2; 
        *dst++ = c1;
    }
    return dst - dst0;
}

Тот же тестовый код дает тот же чистый счет здоровья. Объявление c2 предполагает, что вы используете C99, а не C89.

1 голос
/ 16 февраля 2009

Это пахнет, как будто нам может понадобиться немного () здесь.

Посмотри на разницу

  1. c1 = * src ++;
  2. c1 = (* src) ++;
  3. c1 = * (src ++);

Мне очень нравится (), поскольку они убирают некоторую двусмысленность в том, что программист хочет сделать.

/ Johan

0 голосов
/ 16 февраля 2009

Разницы не должно быть, ++ связывается выше, чем *.

Быстрый test.c также не показывает никакой разницы:

#include <stdio.h>

int main()
{
    int a[] = { 0, 1, 2, 3, 4, 5 };
    int * p = a;
    printf( "%d\n", *p );
    printf( "%d\n", *p++ );
    printf( "%d\n", *p );
    printf( "%d\n", *(p++) );
    printf( "%d\n", *p );
    return 0;
}

Дает:

0
0
1
1
2

Что заставляет вас думать, что у вас проблема с исправлением нового кода?

Редактировать: Найти ошибку компилятора в чем-то , что тривиально, крайне маловероятно. Выше был проведен тест с GCC 4.1.2.

Редактировать 2: У вас есть некоторые несоответствия типов. c1 - беззнаковое целое, * src - беззнаковое короткое. Размеры должны быть size_t, а не int. Сохраняется ли проблема с исходным кодом, если вы их исправите?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...