iconv работает только один раз

#c #iconv

#c #iconv

Вопрос:

Я пытаюсь создать метод, который преобразует строку s-jis в строку utf-8, используя iconv . Я написал код ниже,

 #include <iconv.h>
#include <iostream>
#include <stdio.h>
using namespace std;

#define BUF_SIZE 1024
size_t z = (size_t) BUF_SIZE-1;

bool sjis2utf8( char* text_sjis, char* text_utf8 )
{
  iconv_t ic;
  ic = iconv_open("UTF8", "SJIS"); // sjis->utf8
  iconv(ic , amp;text_sjis, amp;z, amp;text_utf8, amp;z);
  iconv_close(ic);
  return true;
}
int main(void)
{
  char hello[BUF_SIZE] = "hello";
  char bye[BUF_SIZE] = "bye";
  char tmp[BUF_SIZE] = "something else";

  sjis2utf8(hello, tmp);
  cout << tmp << endl;

  sjis2utf8(bye, tmp);
  cout << tmp << endl;
}
  

и вывод должен быть

 hello
bye
  

но на самом деле,

 hello
hello
  

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

Обратите внимание, что «hello» и «bye» — это японские строки s-jis в моей оригинальной программе, но я изменил это, чтобы сделать программу удобной для просмотра.

Комментарии:

1. пожалуйста, обратите внимание, что в вашем примере z уменьшается дважды для каждого преобразованного символа.

2. также вы не должны использовать utf8, вы должны использовать ascii и wcout UCS2 в Windows и UCS4 в Linux.

Ответ №1:

Я думаю, вы неправильно используете iconv функцию, передавая ей глобальную переменную z . При первом вызове sjis2utf8 значение z уменьшается до 0. Второй вызов sjis2utf8 не имеет никакого эффекта (z==0) и оставляет tmp без изменений.

Из iconv документации :

 size_t iconv (iconv_t cd,
              const char* * inbuf, size_t * inbytesleft,
              char* * outbuf, size_t * outbytesleft);
  

Функция iconv преобразует по одному многобайтовому символу за раз, и для каждого преобразования символов она увеличивает *inbuf и уменьшает *inbytesleft на количество преобразованных входных байтов, она увеличивает *outbuf и уменьшает * outbytesleft на количество преобразованных выходных байтов и обновляет состояние преобразования, содержащееся на cd.

Вы должны использовать две отдельные переменные для длин буферов :

 size_t il = BUF_SIZE - 1 ;
size_t ol = BUF_SIZE - 1 ;

iconv(ic, amp;text_sjis, amp;il, amp;text_utf8, amp;ol) ;
  

Затем проверьте возвращаемое значение iconv и длины буферов для успешного преобразования.

Комментарии:

1. после первого преобразования z переполнится. В c / c он будет обтекаться, если size_t не имеет знака. Если size_t подписан, это приведет к неопределенному поведению.

2. также в вашем втором заявлении… для использования двух переменных iconv преобразует символы BUF_SIZE -1, а не только содержимое строки… в его примере это очевидно, потому что нулевой ограничитель также будет преобразован в 0, и cout остановится в этот момент.

3. Это зависит от iconv реализации внутренних проверок. Как вы сказали в предыдущем комментарии: z уменьшается дважды для каждого преобразованного символа , но мы не знаем, как это проверяется и останавливается при достижении 0. Он может переполниться (да, size_t не имеет знака).

Ответ №2:

 #include <iconv.h>
#include <iostream>
#include <stdio.h>
#include <string.h>

using namespace std;

const size_t BUF_SIZE=1024;


class IConv {
    iconv_t ic_;
public:
    IConv(const char* to, const char* from) 
        : ic_(iconv_open(to,from))    { }
    ~IConv() { iconv_close(ic_); }

     bool convert(char* input, char* output, size_tamp; out_size) {
        size_t inbufsize = strlen(input) 1;// s-jis string should be null terminated, 
                                           // if s-jis is not null terminated or it has
                                           // multiple byte chars with null in them this
                                           // will not work, or to provide in other way
                                           // input buffer length....
        return iconv(ic_, amp;input, amp;inbufsize, amp;output, amp;out_size);
     }
};

int main(void)
{
    char hello[BUF_SIZE] = "hello";
    char bye[BUF_SIZE] = "bye";
    char tmp[BUF_SIZE] = "something else";
    IConv ic("UTF8","SJIS");

    size_t outsize = BUF_SIZE;//you will need it
    ic.convert(hello, tmp, outsize);
    cout << tmp << endl;

    outsize = BUF_SIZE;
    ic.convert(bye, tmp, outsize);
    cout << tmp << endl;
}
  
  • на основе ответа Клейста

Ответ №3:

Вы должны указать длину строки ввода в третьем параметре iconv .

Попробуйте:

 //...
int len = strlen(text_sjis);
iconv(ic , amp;text_sjis, amp;len, amp;text_utf8, amp;z);
//...
  

Ответ №4:

 size_t iconv (iconv_t cd,
          const char* * inbuf, size_t * inbytesleft,
          char* * outbuf, size_t * outbytesleft);
  

iconv изменяет значение, на которое указывает inbytesleft . Итак, после вашего первого запуска z равно 0. Чтобы исправить это, вы должны использовать calculate длину inbuf и сохранять ее в локальной переменной перед каждым преобразованием.

Это описано здесь:http://www.gnu.org/s/libiconv/documentation/libiconv/iconv.3.html

И поскольку вы пометили это как C , я бы предложил объединить все в симпатичный маленький класс, насколько я могу судить из документации, вы можете повторно использовать inconv_t полученные из iconv_open данные для стольких преобразований, сколько захотите.

 #include <iconv.h>
#include <iostream>
#include <stdio.h>
#include <string.h>

using namespace std;

const size_t BUF_SIZE = 1024;
size_t z = (size_t) BUF_SIZE-1;

class IConv {
    iconv_t ic_;
public:
    IConv(const char* to, const char* from) 
        : ic_(iconv_open(to,from))    { }

    ~IConv() { iconv_close(ic_); }

    bool convert(char* input, char* output, size_t outbufsize) {
        size_t inbufsize = strlen(input);
        return iconv(ic_, amp;input, amp;inbufsize, amp;output, amp;outbufsize);
    }
};

int main(void)
{
    char hello[BUF_SIZE] = "hello";
    char bye[BUF_SIZE] = "bye";
    char tmp[BUF_SIZE] = "something else";
    IConv ic("UTF8","SJIS");


    ic.convert(hello, tmp, BUF_SIZE);
    cout << tmp << endl;

    ic.convert(bye, tmp, BUF_SIZE);
    cout << tmp << endl;
}
  

Комментарии:

1. Я думаю, что ваш ответ ошибочен, потому что он всегда будет преобразовывать последовательность 1024 символов / байт.

2. Большое спасибо. Ваш код работает хорошо только после того, как я изменил его const size_t BUF_SIZE на #define BUF_SIZE , и это приятно, потому что на самом деле мне приходится конвертировать строку sjis в строку utf8 в других местах.

3. @neagoegab: Изменено на использование strlen и передачу outbufsize в качестве аргумента. Я не вижу необходимости создавать локальную переменную и передавать на нее указатель, как вы сделали в своей копии.

4. BUF_SIZE — это константа … ее нельзя изменить. iconv выполняет запись в outbufsize. Также ему понадобится размер преобразованного текста для дальнейших манипуляций.

5. @neagoegab Мой код не изменяет BUF_SIZE, поскольку он передается по значению. Кто сказал, что ему нужна длина преобразованного текста? Многие API просто ожидают C-строки с нулевым завершением.