avva: (Default)
[personal profile] avva
(интересно только программистам, знающим C)

Майкрософтовский strlen(), оказывается, не проверяет, не передали ли ему случаем NULL, а по-простому читает то, что по переданному адресу находится, и падает кверху лапками.

Если майкрософтовскому printf'у передать NULL в качестве аргумента, соответствующего %s в строке формата, он это проверит и выдаст "(null)".

Однако у Glib'а (библиотека низкого уровня, лежащая в основе GTK, в основном использующаяся на юниксах, но по идее поддерживающая и платформу Win32) есть внутри своя имплементация printf'а (а точнее, vasnprintf()'а, чтобы покрыть все возможные случаи). Она не делает полностью всю работу printf'а, а всего лишь обрабатывает строку формата и аргументы и строит отдельные более простые вызовы snprintf/sprintf (хотя бы sprintf уже должен быть везде) для каждого аргумента, размещая их по очереди в буфере возврата. Glib использует эту имплементацию в том случае, когда у платформы, на которой её строят, нет своего достаточно хорошего и поддерживающего всё, что нужно, printf'а. Win32 - одна из таких платформ.

Если этому внутреннему Glib'овскому printf'у передать NULL в качестве аргумента, соответствующего %s в форматной строке, то он в определённый момент вызовет strlen() на этот аргумент, чтобы узнать длину строки аргумента - для того, чтобы создать буфер достаточной длины. Увы, авторам этого printf'а не пришло в голову, что strlen на какой-то платформе может упасть, если передать ему NULL. Поэтому они это не проверяют, и в результате, если вызвать что-то вроде: g_printf("foo: %s\n", NULL), то программа упадёт.

Это всё я обнаружил, пытаясь построить и заставить что-то делать ЖЖ-клиент LogJam под Windows XP.

Интересно, кто виноват? Аппликация, передающая какой-либо версии printf'а аргумент NULL для подстановки к %s? Имплементация printf'а, вызывающая strlen(), не проверяя аргумент на ==NULL? Имплементация strlen(), идущая сразу по адресу аргумента, не проверяя на NULL?

P.S. Проверил strlen() на юниксах (glibc). Он тоже, оказывается, не проверяет на NULL. Но там тот же баг не проявлялся потому, что GLib использует системный printf, а не свой, а системный это дело проверяет.

Date: 2005-03-27 10:33 am (UTC)
From: [identity profile] valshooter.livejournal.com
Я так подозреваю, что все хороши.

Date: 2005-03-27 10:36 am (UTC)
stas: (Default)
From: [personal profile] stas
Насколько я знаю, никто не обязывает strlen проверять NULL - NULL не есть легитимная строка. То, что printf печатает (null) - это неожиданная удача (точнее, подарок от авторов libc криворуким программистам ;), но полагаться на это, ПМСМ, ни в коем случае не следует.
В данном случае виновата, конечно, программа, передающая printf-у NULL. Но виноват также и glib, не полностью поддерживающий интерфейс printf, то есть не проверяющий строки на NULL.

Date: 2005-03-27 02:18 pm (UTC)
From: [identity profile] arbat.livejournal.com
я бы добавил следующее: и не надо обязывать strlen проверять на NULL. Предположим, проверили, ай-яй-яй, это - NULL. Ну и? Что теперь делать? Exceptions в C не предусмотрены. Возвращаем мы size_t, т.е. совместить возврат осмысленного числа с вовзратом кода ошибки - не удастся. Ну, или - ценой потери половины диапазона заменой типа возврата на int, или ценой возврата, скажем, максимально значения в качестве кода ошибки, надеясь, что программист, который забыл проверить параметр на NULL - не забудет проверить возвращаемый код, да еще не ошибиться - как. Но, главное - это делать абсолютно незачем. Если клиент функции хочет сделать проверку, он может. Если он не хочет, одна из главных идеологий C состоит в том, что клиент не должен платить за то, что он не заказывал. Потому, вполне правильный выбор - не проверять. При вызове с NULL обещать - undefined behavior.

printf... ну, это, предположительно, функция медленная. Ей по барабану потратить чуток времени на NULL проверить, да и ответ на вопрос "что же делать, если это NULL?" там более понятен. Что про нее стандарт говорит - не знаю.

(no subject)

From: [personal profile] stas - Date: 2005-03-27 04:45 pm (UTC) - Expand

(no subject)

From: [identity profile] arbat.livejournal.com - Date: 2005-03-27 05:18 pm (UTC) - Expand
(deleted comment)

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2005-03-27 06:47 pm (UTC) - Expand

starbucks-shmarbucks

From: [identity profile] arbat.livejournal.com - Date: 2005-03-27 07:06 pm (UTC) - Expand

Date: 2005-03-27 10:39 am (UTC)
From: [identity profile] mff.livejournal.com
Я бы сказал, что виноват код вызывающий strlen(), не проверяя аргумент на ==NULL. Правильнее один раз упасть на strlen(NULL), чем ловить плавающую ошибку потом.

И еще -- мне кажется, что семантику strlen() нельзя естественным образом расширить на NULL.

Date: 2005-03-27 01:36 pm (UTC)
From: [identity profile] sobaker.livejournal.com
можно отдавать -1 :)

ух, какие волшебные спецэффекты это может вызвать!..

Date: 2005-03-27 10:53 am (UTC)
From: [identity profile] yurri.livejournal.com
Проверка параметра на корректность должна делаться внутри вызываемой функции, а не вне её, и в случае несоответствия функция должна вернуть оговоренное ошибочное значение или разбудить исключение.

Так что виновата самая нижняя в иерархии strlen().

С другой стороны, прикладной код, вызывающий printf с NULL, тоже заслуживает внимания разработчика.

Date: 2005-03-27 11:16 am (UTC)
From: [identity profile] zimopisec.livejournal.com
Это было бы правильно для "безопасного" языка типа джавы , C# и прочих, несть им числа, но противоречило бы идеологии низкоуровневого С. Вся суть которого- не делать за программиста ничего лишнего, точнее, вообще ничего, кроме избавления последнего от необходимости писать на ассемблере.
Виновата вызывающая программа, на 100%

Re: Reply to your comment...

From: [identity profile] yurri.livejournal.com - Date: 2005-03-27 11:20 am (UTC) - Expand

Date: 2005-03-27 10:57 am (UTC)
From: [identity profile] pendelschwanz.livejournal.com
Настоящие джигиты не падают при нулевых аргументах.

Date: 2005-03-27 02:17 pm (UTC)
stas: (Ну-ка)
From: [personal profile] stas
Как раз наоборот - настоящие джигиты падают немедленно, обнаружив некорректный аргумент, который не предусмотрен интерфейсом функции (поскольку другого способа обработки исключений в C нет). Тогда, запустив программу в отладчике, можно узнать, что же случилось и где оно случилось. Если же вместо этого будет возвращено какое-нибудь левое значение, которое пойдёт гулять дальше по программе, то обнаружить, где же случилась проблема, станет задачей гораздо более сложной.

(no subject)

From: [identity profile] pendelschwanz.livejournal.com - Date: 2005-03-27 03:21 pm (UTC) - Expand

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2005-03-27 03:34 pm (UTC) - Expand

(no subject)

From: [personal profile] stas - Date: 2005-03-27 04:43 pm (UTC) - Expand

(no subject)

From: [identity profile] igorlord.livejournal.com - Date: 2005-03-27 04:55 pm (UTC) - Expand
(deleted comment)

(no subject)

From: [identity profile] igorlord.livejournal.com - Date: 2005-03-27 07:17 pm (UTC) - Expand

(no subject)

From: (Anonymous) - Date: 2005-03-28 10:05 am (UTC) - Expand

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2005-03-28 10:26 am (UTC) - Expand

Date: 2005-03-27 11:00 am (UTC)
From: [identity profile] bod-hi.livejournal.com
разумеется, аппликация.
передавать printf`у NULL для %s (ровно как и strlen`у) является нарушением POSIX стандарта

Date: 2005-03-27 11:07 am (UTC)
From: [identity profile] avva.livejournal.com
В POSIX'овском определении этих функций ничего такого не написано.

(no subject)

From: [identity profile] michk.livejournal.com - Date: 2005-03-27 12:42 pm (UTC) - Expand

(no subject)

From: [identity profile] arbat.livejournal.com - Date: 2005-03-27 02:12 pm (UTC) - Expand

(no subject)

From: [identity profile] lazyreader.livejournal.com - Date: 2005-04-05 05:58 pm (UTC) - Expand

(no subject)

From: [identity profile] gdy.livejournal.com - Date: 2005-03-27 01:52 pm (UTC) - Expand

(no subject)

From: [identity profile] gdy.livejournal.com - Date: 2005-03-27 02:02 pm (UTC) - Expand

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2005-03-27 02:07 pm (UTC) - Expand

(no subject)

From: [identity profile] dvv.livejournal.com - Date: 2005-03-27 08:16 pm (UTC) - Expand

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2005-03-27 08:28 pm (UTC) - Expand

(no subject)

From: [identity profile] dvv.livejournal.com - Date: 2005-03-27 08:47 pm (UTC) - Expand

Date: 2005-03-27 11:00 am (UTC)
From: [identity profile] mtyukanov.livejournal.com
Главная вина -- на программе.

Хорошая программа не должна прятать свои ошибки. Если strlen будет обрабатывать null, он пойдет все дальше и дальше -- и может в конце концов серьезно попортить юзеру данные. Когда его обрабатывают printfы и пишут (null) -- это хотя бы явно видно, и есть шанс, что ошибка не будет пропущена. А когда он просто крадется незаметно -- может оказаться, что программа будет на вид как живая, только вот иногда невоспроизводимо падать.

Идеально было бы поставить неотключаемый в релизе assert(s). Паша Сенаторов когда-то написал очень хорошую статью в SU.SOFTW, пропагандировавшую такую технику. Я потом поработал с его кодом и кодом на основе его идеологий -- действительно, очень хорошо (ну, там еще и C++ и assert кидает исключение.)

Date: 2005-03-27 11:41 am (UTC)
From: [identity profile] 314truha.livejournal.com
На мой неискушённый взгляд проблема не в том что strlen() падает, или в том что printf печатает (null) вместо падения, а в непоследовательности поведения разных функций одной и той же библиотеки - а это в данном случае MSVCRT.

Date: 2005-03-27 11:42 am (UTC)
From: [identity profile] senatov.livejournal.com
Может вам ещё exceptions встроить и garbage collector
И синтаксис поудобнее?

Тогда получим очередной Перл или С# c басиком.

Голубчик, это ведь С, а не жава и не перл.
Вы знаете как концептировался С, когда создавался?
Наверняка читали - как замена ассемблеру.
Что бы писать на нём ДРУГИЕ ИНСТРУМЕНТЫ.

Ну где вы видели, что бы ассемблер проверял null?


size_t __cdecl strlen (const char * str)
{
const char *eos = str;
while( *eos++ ) ;
return( (int)(eos - str - 1) );
}

Date: 2005-03-27 01:35 pm (UTC)
From: [identity profile] sobaker.livejournal.com
Я согласен, что это макроассемблер, но Boehm GC - очень и очень неплох :)

Date: 2005-03-27 02:27 pm (UTC)
From: [identity profile] gdy.livejournal.com
хи-хи

Date: 2005-03-27 12:31 pm (UTC)
From: [identity profile] mfi.livejournal.com
Насколько я помню - отсутствие проверки на NULL ( и не принятие NULL как пустой строки ) в str функциях - это ANSI стандарт. Мы в свое время на этом хорошо сыпались при портинге с HP на SUN.

П.С. Оффтопик. Я Вам как то обещал ссылку из медицинской литературы на простуду и ее связь с холодом. Отмена, я неправильно понял своего информатора.

Date: 2005-03-27 01:04 pm (UTC)
From: [identity profile] dimas.livejournal.com
1. Берем стандарт и смотрим. Ни про strlen, ни про printf никто не обещал специального поведения для NULL. Итого, кто не проверяет что подсовывает на вход этим фунциям - ССЗБ.

2. В документации на g_printf написано что можно использовать NULL? Если нет - см. п.1.

3. Программисть, использующий недокументированные функции/расширения/отступления от стандарта ССЗБ, а если и не комментирует такие места - увольнять без выходного пособия.

4. Сколько бы не гнобили С++ за его якобы сложность, а вот такого рода ошибок, если не пользоваться С-ным насделием, там в разы меньше.

5. Эх, я так понимаю ошибки "нулевых указателей" и прочих "переполнений буффера" умрут только вместе с Си ...

Date: 2005-03-28 08:14 am (UTC)
From: [identity profile] potan.livejournal.com
4. Ох сколько я видел программ, которые проверяют указатель на объект на NULL во всех местах, кроме одного :-))).

В "правильных" языках, типа Cyclone (http://www.eecs.harvard.edu/~greg/cyclone/) есть несколько типов для указателей - у некоторых NULL входит в диапазан допустимых значений, у некоторых нет. Вот где настоящая защита от ошибок, а не в гнилом C++.

Date: 2005-03-27 01:33 pm (UTC)
From: [identity profile] sobaker.livejournal.com
Да вроде как никто не проверяет.. Вот кусочек FreeBSD's libc:

size_t strlen(const char *str) {
register const char *s;
for (s = str; *s; ++s);
return(s - str);
}

И мне этот подход в чем-то симпатичен. Не расслабляет.

Date: 2005-03-27 01:34 pm (UTC)
From: [identity profile] sobaker.livejournal.com
Справедливости ради (или контраста для?), FreeBSD's printf("%s", NULL) печатает "(null)" :)

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2005-03-27 02:10 pm (UTC) - Expand

(no subject)

From: [identity profile] sobaker.livejournal.com - Date: 2005-03-27 05:48 pm (UTC) - Expand

Date: 2005-03-27 03:41 pm (UTC)
From: [identity profile] gaius-julius.livejournal.com
"Аппликацию" в руском языке принято называть приложением.

Забываем-с? (-:

Date: 2005-03-27 03:50 pm (UTC)
From: [identity profile] avva.livejournal.com
Забываем :)

Date: 2005-03-27 04:45 pm (UTC)
From: [identity profile] yanis.livejournal.com
самый верхний уровень виноват, как и всегда в таких случаях. твоя программа или ты чью-то конфигурируешь? если твоя то не передавай нули куда не положено, а если чужая - может перегрузить vasnprintf этот - написать свой отдельный модуль и воткнуть в линк самым первым?

Date: 2005-03-27 05:17 pm (UTC)
From: [identity profile] avva.livejournal.com
Не моя, но я просто патч к ней сделаю, не проблема.

Date: 2005-03-27 09:46 pm (UTC)
From: [identity profile] oxfv.livejournal.com
А вот, например, дебажная и релизная версии программ в ВЦ++ под Win32 по-разному инициализируют неинициализированные переменные. Если strlen проверял бы пойнтер на ноль, то не должен ли бы он был проверять его на 0xcdcdcdcd или чем там инициализируется пойнтер поначалу? А если вспомнить, что разные инструменты типа BoundsChecker'a инициализируют такие переменные чем пожелаешь, становится вообще уныло при мысли, что strlen будет икать от нулевого пойнтера, но падать от другого заведомо бессмысленного пойнтера. Вывод: ответственность за проверку должна быть на вызывающем коде.

Date: 2005-03-27 10:20 pm (UTC)
From: [identity profile] dimrub.livejournal.com
Мне кажется, тут ведь не уголовный суд, и главное не установить, кто прав, кто виноват, а сделать так, чтобы свести до минимума количество ошибок, и время на отладку в ситуациях, когда ошибка все же произошла. В данной ситуации (поскольку strlen не возвращает статус) сделать ничего нельзя, пожалуй (то есть, виновата вызывающая сторона). Но если бы я разрабатывал данный интерфейс с нуля, я бы скорее всего сделал так, как сделали разработчики COM: ВСЕ методы возвращают статус. Тогда бы у меня была возможность как минимум "to be liberal with the inputs I accept & to be conservative with outputs I produce".

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. 28th, 2025 10:16 pm
Powered by Dreamwidth Studios