avva: (Default)
[personal profile] avva

Пытаясь понять, почему моя программа не выводит текст так, как я хочу, обнаружил баг в одной из стандартных функций языка Перл. Подробности под катом - понятны будут только тем, кто знает Перл.

Баг в функции sprintf. Он проявляется, когда при выводе строки (аргумент %s), которая к тому же содержит широкие символы в UTF-8 (напр. русские буквы), используются одновременно оба ограничения на длину - минимальное и максимальное - и они равны. Ниже копирую вывод моей тест-программы, которая изолирует баг, и демонстрирует, при каких вызовах функции он происходит.

Первые две пары вызовов printf демонстрируют баг, остальные - его отсутствие при других аргументах.

printf('%-3.3s', 'абвгд'): |абв  |
printf('%-3.3s', '01234'): |012|

printf('%3.3s', 'абвгд'): |  абв|
printf('%3.3s', '01234'): |012|

printf('%3s', 'абвгд'): |абвгд|
printf('%3s', '01234'): |01234|

printf('%.3s', 'абвгд'): |абв|
printf('%.3s', '01234'): |012|

printf('%3.4s', 'абвгд'): |абвг|
printf('%3.4s', '01234'): |0123|

Проверил, что баг содержится и в самой последней версии языка. Теперь надо посмотреть, сколько осталось пороха в пороховницах и насколько будет легко его починить...

Date: 2006-10-08 09:11 pm (UTC)
From: [identity profile] b-a-t.livejournal.com
Как ведет себя данный код при use locale; и при use utf8; ?

Не может ли это быть ошибкой libc?

Date: 2006-10-08 09:17 pm (UTC)
From: [identity profile] avva.livejournal.com
Подразумевается use utf8; (поэтому я могу вставлять строку прямо в текст программы). use locale; тут вообще не должно играть роли. С точки зрения Перла (при наличии use utf8;) строки "абвгд" и "01234" одинаковой длины, 5 символов, и все указания длин внутри форматной строки sprintf относятся к символам, а не к байтам (и в принципе это работает, как видно из тех примеров, где баг не происходит).

Это точно не ошибка libc, sprintf перла не пользуется sprintf'ом системы, вместо этого все делает сам внутри.

Date: 2006-10-08 09:24 pm (UTC)
From: [identity profile] b-a-t.livejournal.com
А, про utf-8 было не совсем ясно, мы-то тут до сих пор KOI8-R балуемся. Поэтому вызвала удивление ссылка на wide characters.

Да, при таких вводных ошибка проявляется :(

Date: 2006-10-08 09:35 pm (UTC)
From: [identity profile] avva.livejournal.com
Добавил про UTF-8, чтобы было понятнее.
Я вообще теперь стараюсь работать только в utf-8, и локаль у меня такая стоит, и вообще все :)

Date: 2006-10-08 09:55 pm (UTC)
From: [identity profile] ninazino.livejournal.com
А-а-а, с этого надо было начинать. С этой локалью таки могут быть проблемы.

Date: 2006-10-08 10:12 pm (UTC)
From: [identity profile] avva.livejournal.com
Дело не в локали. Локаль не играет роли. Перл сохраняет строки внутри всегда либо как последовательность байтов, либо в UTF-8 (как в данном случае). Это - баг работы Перла внутри со своими utf-8-строками.

Date: 2006-10-08 10:28 pm (UTC)
From: [identity profile] ninazino.livejournal.com
Правда Ваша. Но вот так -- работает правильно:

use utf8;
my $string = 'абвгд';
utf8::encode($string);
printf('%-3.3s', $string);
printf('%3.3s', $string);

Date: 2006-10-08 10:50 pm (UTC)
From: [identity profile] avva.livejournal.com
Нет. Какое же это правильно? Это выключает utf8-флаг на скаларе $string, превращая его из строки в 5 широких символов в строку из 10 восьмибитных символов. Теперь printf обрежет строку на третьем байте, а не на шестом байте (третьем символе), как хотелось бы. Т.е. с точки зрения байтов printf больше не ошибается, верно, но вся семантика работы со строкой теряется.

Date: 2006-10-08 10:57 pm (UTC)
From: [identity profile] ninazino.livejournal.com
Но у меня печатает "абв", что в этом неправильного? То есть как раз на третьем символе.

Date: 2006-10-08 11:12 pm (UTC)
From: [identity profile] avva.livejournal.com
Не знаю. У меня печатает а + половинку utf8-пары б, т.е. мусор. Возможно, как-то связано с разным поведением utf8 в разных версиях, это довольно новая прагма (особенно ее методы, т.е. utf8::encode).

Date: 2006-10-08 11:55 pm (UTC)
From: [identity profile] ninazino.livejournal.com
Любопытно. У меня честно выпечатывает "абв", сохраняя тем самым семантику работы со строкой. Интересно бы понять, чем такое различие объясняется. Опять вернусь к вопросу о версии перла. У Вас какая?

Date: 2006-10-08 11:58 pm (UTC)
From: [identity profile] avva.livejournal.com
5.8.7 и 5.9.4, поведение одинаково.

Date: 2006-10-09 12:02 am (UTC)
From: [identity profile] ninazino.livejournal.com
Вот интересно. У меня старше -- 5.8.4. Так что же получается, в более поздних версиях что-то сломали?

Date: 2006-10-09 12:54 am (UTC)
From: [identity profile] avva.livejournal.com
Или наоборот, исправили. Ведь мое поведение соответствует документации utf::encode(), хоть оно и показывает мусор. Может быть, в 5.8.4 sprintf неправильно распознает utf8-аргументы... не знаю.

Date: 2006-10-10 07:41 am (UTC)
From: [identity profile] pernat1y.livejournal.com
У avva исходники в кодировке utf8, а у ninazino очевидно нет. Отсюда и разница в работе.

Date: 2006-10-10 08:33 am (UTC)
From: [identity profile] avva.livejournal.com
Да, действительно.

Date: 2006-10-08 10:13 pm (UTC)
From: [identity profile] ninazino.livejournal.com
Да, проверила, с UTF-8 и у меня проявилось.

Date: 2006-10-08 10:13 pm (UTC)
From: [identity profile] b-a-t.livejournal.com
Я вот на FreeBSD, не смотря на поддержку оной UTF-8 все не так хорошо, как хотелось бы, поэтому пока KOI8-R. Ну, и соответсвенно - мышление однобайтное :)

Date: 2006-10-08 10:21 pm (UTC)
nine_k: A stream of colors expanding from brain (Default)
From: [personal profile] nine_k
Хм. Когда под веб приходится писать, да под много языков, локаль консоли перестаёт играть определяющую роль %)

Date: 2006-10-08 09:53 pm (UTC)
From: [identity profile] ninazino.livejournal.com
Хм. В моей версии нет проблемы. Следующий код:

print "_"; printf('%-3.3s', 'abcde');print "_\n";
print "_"; printf('%-3.3s', '01234');print "_\n";
print "_"; printf('%3.3s', 'abcde');print "_\n";
print "_"; printf('%3.3s', '01234');print "_\n";

Печатает(ожидаемо):
_abc_
_012_
_abc_
_012_


А следующий код:
print "_"; printf '<%.5s>', "truncated"; print "_\n";
print "_"; printf '<%.5s>', "012345678"; print "_\n\n;

print "_"; printf '<%10.5s>', "truncated"; print "_\n";
print "_"; printf '<%10.5s>', "012345678"; print "_\n\n";

print "_"; printf '<%-.5s>', "truncated"; print "_\n";
print "_"; printf '<%-.5s>', "012345678"; print "_\n\n";

print "_"; printf '<%-10.5s>', "truncated"; print "_\n";
print "_"; printf '<%-10.5s>', "012345678"; print "_\n\n";

Печатает (тоже ожидаемо):
__
_<01234>_

_< trunc>_
_< 01234>_

__
_<01234>_

__
_<01234 >_

А какая у Вас версия версия perl? У меня v5.8.4

Date: 2006-10-08 10:25 pm (UTC)
nine_k: A stream of colors expanding from brain (Default)
From: [personal profile] nine_k
А где тут символы с кодом более 255? %)
Попробуйте русские буквы (начинаются с 0x410), расширенную латиницу (от 0x100), греческие (0x391), etc.

Date: 2006-10-08 10:31 pm (UTC)
From: [identity profile] ninazino.livejournal.com
Да, я уже поняла, в чем тут дело. И даже нашла work-around (http://avva.livejournal.com/1657361.html?thread=37461777#t37461777).

Date: 2006-10-08 10:39 pm (UTC)
nine_k: A stream of colors expanding from brain (Default)
From: [personal profile] nine_k
Хм. А может, это не workaround, а вообще intended use? %)

Date: 2006-10-08 10:51 pm (UTC)
From: [identity profile] avva.livejournal.com
Ни в коем случае. См. ответ выше.
utf8::encode выключает utf8-флаг, делая строку "абвгд" идентичной строке "0123456789" (с точки зрения количества символов и того, как перл с ними работает).

Date: 2006-10-08 10:52 pm (UTC)
From: [identity profile] ninazino.livejournal.com
Да, мне тоже пришла в голову такая мысль ((:

Date: 2006-10-08 11:09 pm (UTC)
From: [identity profile] avva.livejournal.com
Вроде бы починил.

--- perl-5.9.4-orig/sv.c        2006-08-15 15:37:41.000000000 +0300
+++ perl-5.9.4/sv.c     2006-10-09 01:06:31.000000000 +0200
@@ -8874,6 +8874,8 @@
                    }
                    if (width) { /* fudge width (can't fudge elen) */
                        width += elen - sv_len_utf8(argsv);
+                       if (has_precis && precis < elen)
+                           width += precis - elen;
                    }
                    is_utf8 = TRUE;
                }


Теперь послать им еще наверное неплохо бы.

Date: 2006-10-08 11:43 pm (UTC)
From: [identity profile] avva.livejournal.com
Нет, это не совсем верно. Надо еще кое-что подкрутить.

Date: 2006-10-08 11:50 pm (UTC)
From: [identity profile] avva.livejournal.com
Новая версия. Проходит тесты и не падает даже :)

--- sv.c.old    2006-10-09 01:29:33.000000000 +0200
+++ sv.c        2006-10-09 01:46:15.000000000 +0200
@@ -8873,7 +8873,13 @@
                        precis = p;
                    }
                    if (width) { /* fudge width (can't fudge elen) */
-                       width += elen - sv_len_utf8(argsv);
+                       if (has_precis && precis < elen) {
+                           I32 p = precis; /* in bytes now */
+                           sv_pos_b2u(argsv, &p);
+                           width += precis - p;
+                       }
+                       else
+                           width += elen - sv_len_utf8(argsv);
                    }
                    is_utf8 = TRUE;
                }

Date: 2006-10-09 12:52 am (UTC)
From: [identity profile] avva.livejournal.com
Последняя версия, посланная с perlbug. Вызывать sv_pos_b2u все же не надо, у нас уже есть эта информация.
--- sv.c.old    2006-10-09 01:29:33.000000000 +0200
+++ sv.c        2006-10-09 02:40:58.000000000 +0200
@@ -8867,13 +8867,17 @@
            else {
                eptr = SvPVx_const(argsv, elen);
                if (DO_UTF8(argsv)) {
+                   I32 old_precis = precis;
                    if (has_precis && precis < elen) {
                        I32 p = precis;
                        sv_pos_u2b(argsv, &p, 0); /* sticks at end */
                        precis = p;
                    }
                    if (width) { /* fudge width (can't fudge elen) */
-                       width += elen - sv_len_utf8(argsv);
+                       if (has_precis && precis < elen)
+                           width += precis - old_precis;
+                       else
+                           width += elen - sv_len_utf8(argsv);
                    }
                    is_utf8 = TRUE;
                }

Date: 2006-10-09 01:13 am (UTC)
From: [identity profile] smilga.livejournal.com
У меня куда серьёзней проблема вылезла в субботу. Представьте себе, что нужно прочитать файл, кодированный в utf-8, это, например, open(IN, "<:encoding(utf-8)", $FILE) or die; while(<IN>) { ... }. Если в файле при этом испорченный utf (что в Сети встречается гораздо чаще, чем следовало бы), то 5.8.6-й перл может при чтении ошибочной строки свалиться в бесконечный цикл, печатая всё время utf8 "..." does not map to Unicode. Я пока не разобрался, исправлено ли это в более свежих версиях.

Date: 2006-10-09 01:18 am (UTC)
From: [identity profile] avva.livejournal.com
Какая прелесть!

Видимо, читайте весь файл в память в байтах и переводите через Encode после чтения (или построчно).

Date: 2006-10-09 10:35 am (UTC)
From: [identity profile] smilga.livejournal.com
Я, собственно, так и сделал — «но осадок остался».

Date: 2006-10-09 03:45 pm (UTC)
From: [identity profile] smilga.livejournal.com
А вот ещё лучше. Имеется файл (довольно большой) с совершенно на этот раз, извините за выражение, доброформенным ютээфом. В нём словоформы одного известного западнославянского языка, т.е. много характерных пар байт \xC4\x8D, \xC5\xBE и т.п. Открываем файл тем же способом, что описан выше, читаем строки. На этот раз бесконечных циклов, слава богу программистов, никаких нет, но нерегулярно раз в несколько сотен строк цифруха высказывается, что, дескать, кто-то там опять does not map to Unicode. Но несмотря ни на что конечный результат получается совершенно корректный, ни одного знака и ни одной строки не теряется.

Хочется научить перл время от времени писать: фашизм поднимает голову at foo.pl line 14142 или там — поток убит кровавой гебней at bar.pl line 2718. У нас тут любят такие развлечения.

Date: 2006-10-09 03:56 pm (UTC)
From: [identity profile] avva.livejournal.com
А это reproducible? Всегда выдается ошибка в одном и том же месте? Тогда можно изолировать и найти баг.

Date: 2006-10-09 04:19 pm (UTC)
From: [identity profile] smilga.livejournal.com
А, нет. Вся прелесть в том, что это не вызывается никакой конкретной строкой. Скажем, для всего большого файла первые предупреждения вылезают в строках 617, 630 и дальше каких-то. Если выбрать из файла строки с 600-й по 650-ю и пропустить через тот же скрипт, предупреждения не печатаются вовсе. Если выбрать строки с 100-й по 700-ю, печатаются в строках 233 и 534 (т.е., соответственно, 332 и 633 исходного файла); если выбрать со 101-й по 700-ю, то печатается в одной строке 235; если с 200-й по 1700-ю, то в строках 419 и 915; если с 200-й по 1000-ную, то в одной только 419.

Date: 2006-10-09 04:22 pm (UTC)
From: [identity profile] avva.livejournal.com
Тем не менее, если файл один и тот же, предупреждение всегда вылезет в одной и той же строке или не всегда? Если всегда, и вам не лень, пришлите мне пример файла и самый тривиальный пример скрипта, который вызывает предупреждение, и я попробую разобраться.

Date: 2006-10-11 08:46 am (UTC)
From: [identity profile] smilga.livejournal.com
Если один и тот же, то да, всегда в одних и тех же строках. Т.е. это скорее heisenbug, чем schroedinbug.

Вам точно хочется с этим возиться? Я проверил, в 5.8.8 уже обе блохи исправлены. Иначе бы я и сам стал разбираться.

Date: 2006-10-11 08:57 am (UTC)
From: [identity profile] avva.livejournal.com
А, тогда не надо, естественно.

Date: 2006-10-09 03:03 am (UTC)
From: [identity profile] serejik.livejournal.com
О господи. :-)) 7 последних лет пишу на перле, всегда обхожусь минимумом доступого синтаксиса, всю обработку строк, в том числе и в разных локалях делаю ручками, как будто в языке ничего кроме побайтовой выборки из строки нет.

Прямо америку открыли для меня, честно. Занятно...
From: [identity profile] aburachil.livejournal.com
А вот скажите, Анатолий, может быть Вы знаете ответ на вопрос, который меня давно занимает --- если я говорю что-то вроде

use charnames ':full';print "\x{0041}\N{WHITE SMILING FACE}"



то получаю на выходе букву А и смайлик, что вполне понятно, но кроме того, пёрл говорит "Wide character in print". Вот вопрос собственно такой --- зачем он это говорит, наверное что-то я не так делаю, но не понимаю, что именно?
From: [identity profile] avva.livejournal.com
Да, надо сделать binmode STDOUT, ":utf8"; чтобы он не боялся посылать юникодные символы в данный поток вывода. С точки зрения самого потока ничего не изменится, те же utf8-байты. Если писать в файл, то можно его сразу открывать чем-то вроде open OUT, ">:utf8", "filename".
From: [identity profile] aburachil.livejournal.com
А понятно, то есть пёрл боится, что консолька не юникодная, несмотря на переменную "LANG", в которой стоит, "что-то-там.UTF8"... А стоит такое поведение тоже рассматривать как ошибку или есть разумные основания именно так себя вести?

Date: 2006-10-09 09:18 am (UTC)
From: [identity profile] avva.livejournal.com
перл не может и не должен знать, что STDOUT ведет на консоль - может вести куда угодно. В принципе мне это кажется разумным компромиссом между внутренней достаточно чистой utf8-имплементацией перла, и необходимостью существовать во внешней среде, в которой до недавнего времени хорошая поддержка utf-8 была редкостью.

может вести куда угодно

Date: 2006-10-09 09:21 am (UTC)
From: [identity profile] aburachil.livejournal.com
Аминь! Действительно ;-)

Date: 2006-10-09 11:00 am (UTC)
From: [identity profile] egorfine.livejournal.com
перл и ютф8 - какая боль, какая боль. :(

Date: 2006-10-09 11:31 am (UTC)
From: [identity profile] avva.livejournal.com
Уже не очень - начиная с 5.8.

Date: 2006-10-09 11:53 am (UTC)
From: [identity profile] egorfine.livejournal.com
я бы сказал что жить можно более-менее начиная с 5.8.6. На 5.8.4 это был сплошной pain.

Date: 2006-10-09 05:46 pm (UTC)
From: (Anonymous)
Простите, милый Авва, что не в дугу, но все же выбрала спросить вас,т.к. вы всегда вежливы и все знаете к тому ж.
Я тут тоже размечталась открыть журнал - но кто дает? В смысле я для начала хочу фрее попробовать, а мне в ответ - сначала заплатите.
А я в кантри три тысячи заплатила и ни разу не пошла.
Может фрее акоунт отменинили? И где взять мануал на все действия - открытие и последующая эксплуатация.
Тут есть ваш мобильник - но это я думаю уже слишком?
сто раз спасибо и извините.
Ирина
i_risha7@hotmail.com

Date: 2006-10-09 05:55 pm (UTC)
From: [identity profile] avva.livejournal.com
https://www.livejournal.com/create.bml

Никто не отменял бесплатные аккаунты, попробуйте еще раз.

http://www.livejournal.com/manage/settings/

здесь вы можете выбрать русский язык просмотра и потом читать FAQ сайта: http://www.livejournal.com/support/faq.bml

December 2025

S M T W T F S
  123 4 56
78 9 10 11 1213
1415 1617181920
21 22 23 24 2526 27
28293031   

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Dec. 30th, 2025 03:08 pm
Powered by Dreamwidth Studios