avva: (Default)
[personal profile] avva
Сегодня один из классических багов Перла привёл к тому, что в только что созданные журналы/коммьюнити невозможно было отправить записи, в течение 5 часов примерно. То есть баг не самого перла как языка, а типичный баг в программах на нём: в данном случае, путаница между 0 и undef.

Однако, несмотря на такие баги, отдельное значение undef в Перле себя оправдало, по-моему.

Другой типичный баг, который время от времени всплывает в исходниках ЖЖ, связан с тем фундаментальным фактом, что в Перле нет отдельных типов строк и чисел; скаляр в Перле может содержать целое число, или действительное число, или строку, и переводить одно в другое в зависимости от контекста. Но это значит, в частности, что проверка if("0") не проходит, т.к. "0" это то же самое, что 0, т.е. false. Для строк, возможно, удобнее было бы иметь другой критерий истинности, при котором любая непустая строка истинна, а пустая - ложна, но не получается. Из-за забывания этого иногда получаются баги.

Третий баг возникает как раз вследствие единственного нарушения вышеописанного принципа. Оператор & (двоичный and) действует по-разному в зависимости от того, что расположено внутри скаляров, на которые он действует: числа или строки.

$ perl -e 'print 12 & 5;'
4
$ perl -e 'print "12" & "5";'
1


Почему результаты разные? Потому что & действует на двоичное представление скаляра, не пытаясь привести его к канонической форме (которая в данном случае должна быть численной, конечно). Когда выполняется 12 & 5, выходит двоичный and между 1100 и 0101; результат - 0100, 4. Когда же выполняется "12" & "5", происходит двоичный and между ASCII-представлением строки "12", т.е.
00110001 00110010 , и ASCII-представлением строки "5", т.е. 00110101 . В результате выходит то же, самое, что "1" & "5", т.к. второму байту строки "12" ничего не соответствует в строке "5"; а
"1" & "5" выходит 00110001, т.е. "1", что корректно преобразуется при надобности в 1.

Таким образом, "x" & "y" и x & y дают одинаковый результат, если x и y - цифры от 0 до 9 (это выходит благодаря тому, что ASCII-представления цифр 0..9 начинаются с 48, т.е. совпадают с самими цифрами в четырёх последних двоичных регистрах), но если числа выходят за предел 0..9, результаты выходят разными.

Особенно больно это бьёт по разработчику, если он использует & для вычисления двоичной маски секьюрити, скажем, в целях обеспечения привилегированного доступа к какому-то объекту. Если при этом его числа, с которыми он делает &, на самом деле не числа, а в данный момент строки (например, потому, что их вернул модуль доступа к БД, всегда возвращающий строки, или Перл их внутри перевёл в строки для какой-то операции, того же print), то результат операции будет неверен, и легко может случиться так, что непривилегированный юзер получит доступ к объекту (всё это касается и двоичного or, кстати, т.е. оператора |, но он используется намного реже на практике, так что редко случаются баги с ним).


Четвёртый... пусть будет на баг, а kludge довольно забавный внутри Перла.
Цитируя документацию функции ioctl:
              The return value of "ioctl" (and "fcntl") is as follows:
 
                       if OS returns:          then Perl returns:
                           -1                    undefined value
                            0                  string "0 but true"
                       anything else               that number
 
               Thus Perl returns true on success and false on failure, yet you
               can still easily determine the actual value returned by the
               operating system:
 
                   $retval = ioctl(...) || -1;
                   printf "System returned %d\n", $retval;
 
               The special string "0 but true" is exempt from -w complaints
               about improper numeric conversions.

То же верно ещё для нескольких функций, являющихся перловскими обложками для системных функций: они возвращают обычно результат системной функции или undef в случае ошибки, но, если системная функция возвращает 0 и это для неё не ошибка, они возвращают "0 but true", таким образом пытаясь предотвратить второй баг, описанный выше: если неосторожный разработчик напишет
if(ioctl(...)), то это сработает, т.к. "0 but true" истинно, в отличие от "0"; а если нужно перевести в числовой контекст, то "0 but true" переведётся в 0:
$ perl -e 'print "0 but true" + 5;'
5


Но если мы запускаем perl с опцией -w (warnings), то обычно перевод строки с не-числовым "мусором" в число выдаёт предупреждение:
$ perl -w -e 'print "12garbage" + 5;' 
Argument "12garbage" isn't numeric in addition (+) at -e line 1.
17


А со строкой "0 but true" это не происходит, как и обещано в документации выше:

$ perl -w -e 'print "0 but true" + 5;' 
5


И это верно только для этой строки. На уровне исходников Перла это происходит так. В файле sv.c, имплементирующем операции со скалярами (sv=scalar value), есть функция looks_like_number(). Её вызывают, когда нужно определить, можно ли перевести данную строку в число; она смотрит на строку, и возвращает ноль, если эта строка не представляет из себя число, и ненулевое значение в обратном случае, причём тогда она возвращает значение, указывающее на то, какую функцию надо использовать для перевода данной строки в число (atol() для целых чисел, atof() для действительных). И вот, в этой функции, когда она уже проверила, выглядит ли строка как целое или действительное число, и вернула соответствующие значения в таких случаях, и уже совсем готова возвращать 0, указывая на неудачу, там стоит:

    if (len == 10 && memEQ(sbegin, "0 but true", 10))
        return IS_NUMBER_TO_INT_BY_ATOL;
    return 0;


Классический пример того, что по-английски называют kludge, по-моему.

Date: 2003-10-20 02:52 am (UTC)
nine_k: A stream of colors expanding from brain (Default)
From: [personal profile] nine_k
Так ведь "сделайте систему, которой сможет пользоваться даже идиот, и только идиот будет ей пользоваться" (ц) :-\ Вспомним язык Ada, с кучей хороших идей, в сущности. Но как-то в массы он не пошёл.

Date: 2003-10-20 06:43 am (UTC)
From: [identity profile] igorlord.livejournal.com
Есть Java. Jave лучше чем C++ (по моим стандартам). Javой пользуются многие. Да, Java не заменит C, но и C можно было бы "подправить" так что это не убьёт его место в нише языка для системного програмированья. Конечно, никто сейчас этого делать не будет -- слишком много кода на C и огромная инерция мышленья. Вот и те кому надо, будут продолжать пользоваться lint, прогоняя свой код через него. Компании как Oracle будут продолжать ужесточать требования своей собственой версии lint. (Да, это у нас обязательное мероприятие перед внедрением нового кода в систему). Спрашивается, ну почему надо держать какие-то features в C, когда даже Oracle от них может себе позволить отказаться? (Хотя, даже у нас люди извращаются пробуя надуть этот lint и сделать "закарючку" потому что это им быстрее чем продумать как обойтись без неё.)

Date: 2003-10-20 06:57 am (UTC)
nine_k: A stream of colors expanding from brain (Default)
From: [personal profile] nine_k
Да я всеми руками за java, только она памяти хочет весьма много, как ни прискорбно. И по времени не всегда предсказуема из-за gc. Это всё не даёт писать на java компактные и time-critical куски кода. Впрочем, JNI тут спасает :-)
А С как раз идеально подходит для создания крайне компактных и быстрых кусков кода, ценой сильного снижения производительности программера. На C хорошо и правильно писать драйвера и ядро ОС :-) Возможно, и ядро БД, например. А прочее надо писать на чём-то другом, факт. Вон у того же оракла все инструменты, кроме совсем уж консольных утилит, на Java и написаны :-)
Кстати, кто-то С пытался уже подправить -- не помню уже ссылки, но попадался компилятор подмножества C, дающий большую safety. Ну и запретить char* и strcat как класс ;-)

Date: 2003-10-20 07:28 am (UTC)
From: [identity profile] igorlord.livejournal.com
Консольные утилиты на C -- потому что очень старые. :) Весь Oracle (база данных) -- тоже на C, из-за скорости, но так же из-за того что ей больше 25 лет. Никуда от С не дется, ... а жаль. Если начать заново, то можно было бы нечто получше изобрести; практически такое-же по скорости, но на много более строгое. :)

Date: 2003-10-20 07:35 am (UTC)
nine_k: A stream of colors expanding from brain (Default)
From: [personal profile] nine_k
Ну-ка, ну-ка? Паскаль? Ада? :-)

Или, точнее: что бы вы поменяли в C, чтобы было строже?
У меня ровно 2 мысли: отдельный тип для boolean и уход от null-terminated строк. Боюсь, всё остальное нужно.

Date: 2003-10-20 07:37 am (UTC)
From: [identity profile] avva.livejournal.com
В C нет null-terminated строк. В C вообще нет строк ;-)

Date: 2003-10-20 08:16 am (UTC)
nine_k: A stream of colors expanding from brain (Default)
From: [personal profile] nine_k
Это да.
Но с самой зари C строки обрабатывались именно так. Imho, строки со счётчиком и внятная стандартная библиотека для них куда лучше. Есть же совершенно вменяемый posix-овский стандарт для file I/O.
Впрочем, уезжание указателей за границы массива происходит не только со строками :-\
Так что трансформируем пожелание в другое:
введение отдельного типа массивов, не эквивалентного указателям, и возможность включать bounds check.

Date: 2003-10-21 10:06 am (UTC)
From: [identity profile] onodera.livejournal.com
А в VB 5.0 и 6.0 строки были уникодовые, со счётчиком и нуль-концом (для совместимости), причём работать с функциями WinAPI, возвращающими эти самые сроки, было безумно весело...
A bounds check будет противоречить идее C и С++: «я за вас ещё и думать должен? Вам надо, вы и контролируйте, а я буду только делать то, что приказано»

Date: 2003-10-20 10:16 am (UTC)
From: [identity profile] igorlord.livejournal.com
Я об этом уже писал в этом посте. Вот мой список (я над ним думал ровно 1 минуту, так что многое наверняка здесь нет, чего мне бы тоже хотелось):

  • тип для boolean
  • запрет на использование численных значений как conditionals (разрешаю исключение -- поинтры)
  • запрет на использование численных значений вместо enum
  • введение exceptions и, как в Jave, обязательные их децларация, и либо отлавливание либо передецларация (кроме критических exceptions)
  • введение понятий "поинтер к массиву" и "поинтер к данным". Запрет произвольной арифметики поинтеров, т.е. индехировать можно только поинтеры "к массиву". Все массивы должны иметь "встроенный" размер (должны знать свой размер). (Уход от NULL-terminated строк -- это частный случай этого правила.) Надо подумать заставлять ли компилятор автоматически проверять размер массива на каждом индехировании (мне кажется что эту проверку компилятор сможет отоптимизировать для большенства полезных циклов). Но у любом случае, если массивы уже знают всегда свой размер, то это уже легче т.к. программист всегда может его проверить.
  • минимальная "обектная система" (нужен inheritance но не надо полиморфизма) -- нет виртуальных методов. Иными словами, хочешь добавить переменных (как там по-Русски переменные в классе?), пожалуйста. Хочешь добавить методов, пожалуйста, но старых не меняй. Это позволит компилятору знать какую функцию вызывать во время компиляции. Ну и, конечно, система защиты данных в обьектах типа private, protected, public.


Скорее всего я сейчас это запощу и через минуту захочу ещё что-то добавить. :)

Date: 2003-10-21 06:01 am (UTC)
nine_k: A stream of colors expanding from brain (Default)
From: [personal profile] nine_k
1, 2 и 3 -- практически без вопросов.
4 -- тоже можно, не слишком оно тяжело реализуется. Удобнее, чем обработка сигналов :-)
5 -- нельзя запрещать произвольную pointer arithmetics, поскольку тогда будет очень грустно писать драйверы к memory-mapped устройствам. Массивы, знающие свой размер, можно отнести в стандартную библиотеку, вместе со строками, hashtables, b-trees и прочими полезными в хозяйстве контейнерами. Такой stdcont.h :-)
6 -- наследование как в modula-3 и oberon? Ну, не помешало бы, наверное. Хотя, признаться, и так можно на C писать вполне в ОО-стиле, см. тот же GTK :-)

Date: 2003-10-21 06:40 am (UTC)
From: [identity profile] igorlord.livejournal.com
4. В С есть подобие -- non-local goto в стд. библиотеке. Но можно-же это всё 1) "одеть" в абстарцию exceptions 2) Сделать их оброботку обязательной.

5. Почему? Ведь memory-maped устройства тоже работают по принципу массивов (хотя и без специальных "размер в первом слове"). Наверное можно что-то придумать для таких случаев. Не хочется делать bounds-aware массивы библиотечными а не встроенными. Библиотечные будут хуже оптимизироватся компилятором, а это именно очень надо.

6. Нет. Это я сам придумал пока писал. :) Можно, конечон, писать "в стиле". Можно и вообже ничего плохого не делать. Но делают. :( ОО-стиль навюзывает некую дисциплину. Это полезно. :)

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
2829 30 31   

Most Popular Tags

Page Summary

Style Credit

Expand Cut Tags

No cut tags
Page generated Jan. 1st, 2026 10:17 pm
Powered by Dreamwidth Studios