программистское
Mar. 27th, 2005 11:43 am(интересно только программистам, знающим 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, а не свой, а системный это дело проверяет.
Майкрософтовский 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, а не свой, а системный это дело проверяет.
no subject
Date: 2005-03-27 10:33 am (UTC)no subject
Date: 2005-03-27 10:36 am (UTC)В данном случае виновата, конечно, программа, передающая printf-у NULL. Но виноват также и glib, не полностью поддерживающий интерфейс printf, то есть не проверяющий строки на NULL.
no subject
Date: 2005-03-27 02:18 pm (UTC)printf... ну, это, предположительно, функция медленная. Ей по барабану потратить чуток времени на NULL проверить, да и ответ на вопрос "что же делать, если это NULL?" там более понятен. Что про нее стандарт говорит - не знаю.
(no subject)
From:(no subject)
From:(no subject)
From:starbucks-shmarbucks
From:no subject
Date: 2005-03-27 10:39 am (UTC)И еще -- мне кажется, что семантику strlen() нельзя естественным образом расширить на NULL.
no subject
Date: 2005-03-27 01:36 pm (UTC)ух, какие волшебные спецэффекты это может вызвать!..
no subject
Date: 2005-03-27 10:53 am (UTC)Так что виновата самая нижняя в иерархии strlen().
С другой стороны, прикладной код, вызывающий printf с NULL, тоже заслуживает внимания разработчика.
no subject
Date: 2005-03-27 11:16 am (UTC)Виновата вызывающая программа, на 100%
Re: Reply to your comment...
From:no subject
Date: 2005-03-27 10:57 am (UTC)no subject
Date: 2005-03-27 02:17 pm (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From: (Anonymous) - Date: 2005-03-28 10:05 am (UTC) - Expand(no subject)
From:no subject
Date: 2005-03-27 11:00 am (UTC)передавать printf`у NULL для %s (ровно как и strlen`у) является нарушением POSIX стандарта
no subject
Date: 2005-03-27 11:07 am (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2005-03-27 11:00 am (UTC)Хорошая программа не должна прятать свои ошибки. Если strlen будет обрабатывать null, он пойдет все дальше и дальше -- и может в конце концов серьезно попортить юзеру данные. Когда его обрабатывают printfы и пишут (null) -- это хотя бы явно видно, и есть шанс, что ошибка не будет пропущена. А когда он просто крадется незаметно -- может оказаться, что программа будет на вид как живая, только вот иногда невоспроизводимо падать.
Идеально было бы поставить неотключаемый в релизе assert(s). Паша Сенаторов когда-то написал очень хорошую статью в SU.SOFTW, пропагандировавшую такую технику. Я потом поработал с его кодом и кодом на основе его идеологий -- действительно, очень хорошо (ну, там еще и C++ и assert кидает исключение.)
no subject
Date: 2005-03-27 11:41 am (UTC)no subject
Date: 2005-03-27 11:42 am (UTC)И синтаксис поудобнее?
Тогда получим очередной Перл или С# c басиком.
Голубчик, это ведь С, а не жава и не перл.
Вы знаете как концептировался С, когда создавался?
Наверняка читали - как замена ассемблеру.
Что бы писать на нём ДРУГИЕ ИНСТРУМЕНТЫ.
Ну где вы видели, что бы ассемблер проверял null?
size_t __cdecl strlen (const char * str)
{
const char *eos = str;
while( *eos++ ) ;
return( (int)(eos - str - 1) );
}
no subject
Date: 2005-03-27 01:35 pm (UTC)no subject
Date: 2005-03-27 02:27 pm (UTC)no subject
Date: 2005-03-27 12:31 pm (UTC)П.С. Оффтопик. Я Вам как то обещал ссылку из медицинской литературы на простуду и ее связь с холодом. Отмена, я неправильно понял своего информатора.
no subject
Date: 2005-03-27 01:04 pm (UTC)2. В документации на g_printf написано что можно использовать NULL? Если нет - см. п.1.
3. Программисть, использующий недокументированные функции/расширения/отступления от стандарта ССЗБ, а если и не комментирует такие места - увольнять без выходного пособия.
4. Сколько бы не гнобили С++ за его якобы сложность, а вот такого рода ошибок, если не пользоваться С-ным насделием, там в разы меньше.
5. Эх, я так понимаю ошибки "нулевых указателей" и прочих "переполнений буффера" умрут только вместе с Си ...
no subject
Date: 2005-03-28 08:14 am (UTC)В "правильных" языках, типа Cyclone (http://www.eecs.harvard.edu/~greg/cyclone/) есть несколько типов для указателей - у некоторых NULL входит в диапазан допустимых значений, у некоторых нет. Вот где настоящая защита от ошибок, а не в гнилом C++.
no subject
Date: 2005-03-27 01:33 pm (UTC)size_t strlen(const char *str) {
register const char *s;
for (s = str; *s; ++s);
return(s - str);
}
И мне этот подход в чем-то симпатичен. Не расслабляет.
no subject
Date: 2005-03-27 01:34 pm (UTC)(no subject)
From:(no subject)
From:no subject
Date: 2005-03-27 03:41 pm (UTC)Забываем-с? (-:
no subject
Date: 2005-03-27 03:50 pm (UTC)no subject
Date: 2005-03-27 04:45 pm (UTC)no subject
Date: 2005-03-27 05:17 pm (UTC)no subject
Date: 2005-03-27 09:46 pm (UTC)no subject
Date: 2005-03-27 10:20 pm (UTC)