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, а не свой, а системный это дело проверяет.
Page 1 of 2 << [1] [2] >>

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

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

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

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

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

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

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:00 am (UTC)
From: [identity profile] mtyukanov.livejournal.com
Главная вина -- на программе.

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

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

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

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

Re: Reply to your comment...

Date: 2005-03-27 11:20 am (UTC)
From: [identity profile] yurri.livejournal.com
Если с этой позиции - то вы правы.

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

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

Date: 2005-03-27 12:42 pm (UTC)
From: [identity profile] michk.livejournal.com
А разве это не предполагается по умолчанию в C? Мне всегда казалось, что ни одна функция не обязана проверять на NULL, а если кто-то проверяет, то это "подарок", и никак не стандарт.

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-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)" :)

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

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

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

Date: 2005-03-27 01:52 pm (UTC)
From: [identity profile] gdy.livejournal.com
C99 7.1.4
Each of the following statements applies unless explicitly stated otherwise in the detailed
descriptions that follow: If an argument to a function has an invalid value (such as a value
outside the domain of the function, or a pointer outside the address space of the program,
or a null pointer, or a pointer to non-modifiable storage when the corresponding
parameter is not const-qualified) or a type (after promotion) not expected by a function
with variable number of arguments, the behavior is undefined. If a function argument is
described as being an array, the pointer actually passed to the function shall have a value
such that all address computations and accesses to objects (that would be valid if the
pointer did point to the first element of such an array) are in fact valid.

В 7.9.6.1 (fprintf) ничего про NULL не видно

Date: 2005-03-27 02:02 pm (UTC)
From: [identity profile] gdy.livejournal.com
А posix это повторяет
Each of the following statements shall apply unless explicitly stated otherwise in the detailed descriptions that follow:
  1. If an argument to a function has an invalid value (such as a value outside the domain of the function, or a pointer outside the address space of the program, or a null pointer), the behavior is undefined.

Date: 2005-03-27 02:07 pm (UTC)
From: [identity profile] avva.livejournal.com
Да, спасибо, я тоже уже эту цитату нашёл в каком-то споре по этому поводу сетевом. Но всё равно спасибо ;)

Date: 2005-03-27 02:10 pm (UTC)
From: [identity profile] avva.livejournal.com
А вот glibc'шный ;)
size_t
strlen (const char *str)
{
  int cnt;

  asm("cld\n"			/* Search forward.  */
      /* Some old versions of gas need `repne' instead of `repnz'.  */
      "repnz\n"			/* Look for a zero byte.  */
      "scasb" /* %0, %1, %3 */ :
      "=c" (cnt) : "D" (str), "0" (-1), "a" (0));

  return -2 - cnt;
}


Правда, есть ещё специализированные версии для i486 и i586, там ассемблер посложнее ;)

Date: 2005-03-27 02:12 pm (UTC)
From: [identity profile] arbat.livejournal.com
это не подарок, а нарушение стандарта :-)

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

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 11:59 pm
Powered by Dreamwidth Studios