Что такое код strspn


Содержание

C library function — strspn()

Description

The C library function size_t strspn(const char *str1, const char *str2) calculates the length of the initial segment of str1 which consists entirely of characters in str2.

Declaration

Following is the declaration for strspn() function.

Parameters

str1 − This is the main C string to be scanned.

str2 − This is the string containing the list of characters to match in str1.

Return Value

This function returns the number of characters in the initial segment of str1 which consist only of characters from str2.

Example

The following example shows the usage of strspn() function.

Let us compile and run the above program that will produce the following result −

strstr, wcsstr, _mbsstr, _mbsstr_l strstr, wcsstr, _mbsstr, _mbsstr_l

Возвращает указатель на первое вхождение искомой строки в строке. Returns a pointer to the first occurrence of a search string in a string.

Функции _mbsstr и _mbsstr_l не могут использоваться в приложениях, запускаемых в среде выполнения Windows. _mbsstr and _mbsstr_l cannot be used in applications that execute in the Windows Runtime. Дополнительные сведения: Функции CRT, которые не поддерживаются в приложениях универсальной платформы Windows. For more information, see CRT functions not supported in Universal Windows Platform apps.

Синтаксис Syntax

Параметры Parameters

str str
Строка для поиска, завершающаяся символом NULL. Null-terminated string to search.

стрсеарч strSearch
Искомая строка, завершающаяся символом NULL. Null-terminated string to search for.

locale locale
Используемый языковой стандарт. Locale to use.

Возвращаемое значение Return Value

Возвращает указатель на первое вхождение стрсеарч в strили значение null, если стрсеарч не отображается в str. Returns a pointer to the first occurrence of strSearch in str, or NULL if strSearch does not appear in str. Если стрсеарч указывает на строку нулевой длины, функция возвращает str. If strSearch points to a string of zero length, the function returns str.

Примечания Remarks

Функция возвращает указатель на первое вхождение стрсеарч в str. strstr The strstr function returns a pointer to the first occurrence of strSearch in str. Поиск не включает завершающие нуль-символы. The search does not include terminating null characters. wcsstr является версией strstr с расширенными символами, а _mbsstr — версией с многобайтовыми символами. wcsstr is the wide-character version of strstr and _mbsstr is the multibyte-character version. Аргументы и возвращаемое значение wcsstr представляют собой двухбайтовые строки; аргументы и возвращаемое значение _mbsstr представляют собой многобайтовые строки. The arguments and return value of wcsstr are wide-character strings; those of _mbsstr are multibyte-character strings. _mbsstr проверяет свои параметры. _mbsstr validates its parameters. Если str или стрсеарч имеет значение null, вызывается обработчик недопустимых параметров, как описано в разделе Проверка параметров . If str or strSearch is NULL, the invalid parameter handler is invoked, as described in Parameter Validation . Если выполнение может быть продолжено _mbsstr , errno присваивает свойству значение еинвал и возвращает значение 0. If execution is allowed to continue, _mbsstr sets errno to EINVAL and returns 0. Функции strstr и wcsstr не проверяют свои параметры. strstr and wcsstr do not validate their parameters. В остальном эти три функции ведут себя идентично. These three functions behave identically otherwise.

Эти функции могут создать угрозу в связи с проблемой переполнения буфера. These functions might incur a threat from a buffer overrun problem. Проблемы переполнения буфера могут использоваться для атаки на систему, поскольку они могут допустить выполнение произвольного кода, приводящего к несанкционированному повышению прав доступа. Buffer overrun problems can be used to attack a system because they can allow the execution of arbitrary code, which can cause an unwarranted elevation of privilege. Дополнительные сведения см. в разделе Как избежать переполнения буфера. For more information, see Avoiding Buffer Overruns.

В C эти функции принимают указатель const для первого аргумента. In C, these functions take a const pointer for the first argument. В языке C++ доступны две перегрузки. In C++, two overloads are available. Перегрузка, которая принимает указатель на const , возвращает указатель на константу; версия, принимающая указатель на non-const , возвращает указатель нанеконстантный. The overload that takes a pointer to const returns a pointer to const; the version that takes a pointer to non-const returns a pointer to non-const. Макрос _CRT_CONST_CORRECT_OVERLOADS определяется, если доступны и константные , инеконстантные версии этих функций. The macro _CRT_CONST_CORRECT_OVERLOADS is defined if both the const and non-const versions of these functions are available. Если требуется поведение, не являющеесяконстантой , для C++ обеих перегрузок, определите символ _CONST_RETURN. If you require the non-const behavior for both C++ overloads, define the symbol _CONST_RETURN.

На выходное значение влияет настройка LC_CTYPE в категории locale; Дополнительные сведения см. в разделе setlocale, _wsetlocale. The output value is affected by the locale-category setting of LC_CTYPE; for more information, see setlocale, _wsetlocale. Версии этих функций, не имеющие суффикса _l , используют текущий языковой стандарт для этого поведения, зависящего от языкового стандарта. версии с суффиксом _l идентичны за исключением того, что вместо них используется переданный параметр языкового стандарта. The versions of these functions that do not have the _l suffix use the current locale for this locale-dependent behavior; the versions that have the _l suffix are identical except that they instead use the locale parameter that’s passed in. Для получения дополнительной информации см. Locale. For more information, see Locale.

Сопоставления подпрограмм обработки обычного текста Generic-Text Routine Mappings

Подпрограмма TCHAR.H TCHAR.H routine _UNICODE и _MBCS не определены _UNICODE & _MBCS not defined _MBCS определено _MBCS defined _UNICODE определено _UNICODE defined
_tcsstr strstr _mbsstr wcsstr
Н/Д n/a Н/Д n/a _mbsstr_l Н/Д n/a

Требования Requirements

Подпрограмма Routine Обязательный заголовок Required header
strstr
wcsstr или or
_mbsstr , _mbsstr_l _mbsstr , _mbsstr_l

Дополнительные сведения о совместимости см. в статье Совместимость. For more information about compatibility, see Compatibility.

Функции обработки строк в Cи

В программе строки могут определяться следующим образом:

  • как строковые константы;
  • как массивы символов;
  • через указатель на символьный тип;
  • как массивы строк.

Кроме того, должно быть предусмотрено выделение памяти для хранения строки.

Любая последовательность символов, заключенная в двойные кавычки «» , рассматривается как строковая константа .

Для корректного вывода любая строка должна заканчиваться нуль-символом ‘\0’ , целочисленное значение которого равно 0. При объявлении строковой константы нуль-символ добавляется к ней автоматически. Так, последовательность символов, представляющая собой строковую константу, будет размещена в оперативной памяти компьютера, включая нулевой байт.

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

Для помещения в строковую константу некоторых служебных символов используются символьные комбинации. Так, если необходимо включить в строку символ двойной кавычки, ему должен предшествовать символ «обратный слеш»: ‘\»‘ .

Строковые константы размещаются в статической памяти. Начальный адрес последовательности символов в двойных кавычках трактуется как адрес строки. Строковые константы часто используются для осуществления диалога с пользователем в таких функциях, как printf() .

При определении массива символов необходимо сообщить компилятору требуемый размер памяти.

Компилятор также может самостоятельно определить размер массива символов, если инициализация массива задана при объявлении строковой константой:

В этом случае имена m2 и m3 являются указателями на первые элементы массивов:

  • m2 эквивалентно &m2[0]
  • m2[0] эквивалентно ‘Г’
  • m2[1] эквивалентно ‘o’
  • m3 эквивалентно &m3[0]
  • m3[2] эквивалентно ‘x’

При объявлении массива символов и инициализации его строковой константой можно явно указать размер массива, но указанный размер массива должен быть больше, чем размер инициализирующей строковой константы:

Для задания строки можно использовать указатель на символьный тип .

В этом случае объявление массива переменной m4 может быть присвоен адрес массива:

Здесь m3 является константой-указателем. Нельзя изменить m3 , так как это означало бы изменение положения (адреса) массива в памяти, в отличие от m4 .

Для указателя можно использовать операцию увеличения (перемещения на следующий символ):

Массивы символьных строк

Иногда в программах возникает необходимость описание массива символьных строк . В этом случае можно использовать индекс строки для доступа к нескольким разным строкам.

В этом случае poet является массивом, состоящим из четырех указателей на символьные строки. Каждая строка символов представляет собой символьный массив, поэтому имеется четыре указателя на массивы. Указатель poet[0] ссылается на первую строку:
*poet[0] эквивалентно ‘П’,
*poet[l] эквивалентно ‘-‘.

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

Разница заключается в том, что такая форма задает «прямоугольный» массив, в котором все строки имеют одинаковую длину.

strspn

(PHP 4, PHP 5, PHP 7)

strspn — Возвращает длину участка в начале строки, полностью соответствующего маске

Описание

Возвращает длину участка от начала строки subject , содержащий только символы из mask .

Если параметры start и length не указаны, то будет исследована вся строка subject . Если они указаны, то эффект будет аналогичен вызову strspn(substr($subject, $start, $length), $mask) (подробнее см. функцию substr).

Список параметров

Список из разрешенных символов.

Позиция начала поиска в subject .

Если start указан и неотрицателен, то strspn() начнет искать в строке subject , начиная с позиции start . К примеру, в строке ‘abcdef‘, символом с позицией является ‘a‘, символом с позицией 2 является ‘c‘ и т.д.

Если start указан и отрицателен, то strspn() начнет поиск в строке subject с позиции, отстоящей на start символов с конца subject .

Длина исследуемого фрагмента subject .

Если length указан и неотрицателен, то строка subject будет исследована в течение length после стартовой позиции.

Если length указан и отрицателен, то строка subject будет исследована начиная со стартовой позиции до позиции, отстоящей на length символов с конца subject .

Возвращаемые значения

Возвращает длину первого участка subject , состоящего целиком из символов в mask .

Когда start указан, возращаемая длина строки определяется, начиная с этой позиции, а не с начала строки subject .

Примеры

Пример #1 Пример использования strspn()

// subject не начинается ни из какого символа из mask
var_dump ( strspn ( «foo» , «o» ));

// проверить два символа из subject, начиная с позиции 1
var_dump ( strspn ( «foo» , «o» , 1 , 2 ));

// проверить один символ из subject, начиная с позиции 1
var_dump ( strspn ( «foo» , «o» , 1 , 1 ));
?>

Результат выполнения данного примера:

Примечания

Замечание: Эта функция безопасна для обработки данных в двоичной форме.

Смотрите также

  • strcspn() — Возвращает длину участка в начале строки, не соответствующего маске

User Contributed Notes 7 notes

you can use this function with strlen to check illegal characters, string lenght must be the same than strspn (characters from my string contained in another)

if ( strlen ( $phone ) != strspn ( $phone , $digits ))
echo «illegal characters» ;

It took me some time to understand the way this function works…
I’ve compiled my own explanation with my own words that is more understandable for me personally than the official one or those that can be found in different tutorials on the web.
Perhaps, it will save someone several minutes…

( string $haystack , string $char_list [, int $start [, int $length ]])
?>

The way it works:
— searches for a segment of $haystack that consists entirely from supplied through the second argument chars
— $haystack must start from one of the chars supplied through $char_list, otherwise the function will find nothing
— as soon as the function encounters a char that was not mentioned in $chars it understands that the segment is over and stops (it doesn’t search for the second, third and so on segments)
— finally, it measures the segment’s length and return it (i.e. length)

In other words it finds a span (only the first one) in the string that consists entirely form chars supplied in $chars_list and returns its length

Quick way to check if a string consists entirely of characters within the mask is to compare strspn with strlen eg:

= $_SERVER [ ‘PATH_INFO’ ];
if ( strspn ( $path , ‘/’ ) == strlen ( $path )) <
//PATH_INFO is empty
>
?>

very dificult to get from the definition directly, while i search for that,i came to know that

strspn() will tell you the length of a string consisting entirely of the set of characters in accept set. That is, it starts walking down str until it finds a character that is not in the set (that is, a character that is not to be accepted), and returns the length of the string so far.

strcspn() works much the same way, except that it walks down str until it finds a character in the reject set (that is, a character that is to be rejected.) It then returns the length of the string so far.

= «aeiou» ;
$rejectSet = «y» ;

$str1 = «a banana» ;
$str2 = «the bolivian navy on manuvers in the south pacific» ;

echo $n = strspn ( $str1 , $acceptSet ); // $n == 1, just «a»

echo $n = strcspn ( $str2 , $rejectSet ); // n = 16, «the bolivian nav»
?>

hope this example will help in understanding the concept of strspn() and strcspn().

strspon and preg_match seem to be equally fast for validating numbers:

= ‘foobar123^^’ ;
$testValValid = ‘12346’ ;
$allowedChars = ‘1234567890’ ;

$t1 = microtime ( true );
for ( $i = 0 ; $i 1000000 ; $i ++) <
assert ( strspn ( $testValInvalid , $allowedChars ) != strlen ( $testValInvalid ));
assert ( strspn ( $testValValid , $allowedChars ) == strlen ( $testValValid ));
>
print ‘Time taken for strspon: ‘ . ( microtime ( true ) — $t1 );
print PHP_EOL ;

$t1 = microtime ( true );
for ( $i = 0 ; $i 1000000 ; $i ++) <
assert ( preg_match ( ‘/^[0-9]+$/’ , $testValInvalid ) === 0 );
assert ( preg_match ( ‘/^[0-9]+$/’ , $testValValid ));
>

print ‘Time taken for preg_match: ‘ . ( microtime ( true ) — $t1 );
print PHP_EOL ;

Что означает libc_h >

Я нашел код выше в glibc-2.18/string/strspn.c . Может кто-нибудь объяснить, что это значит. Это важно для остальной части кода? Вот содержимое файла strspn.c :

    3 1
  • 20 мар 2020 2020-03-20 23:57:17
  • marcelo

1 ответ

Может кто-то объяснить, что это значит.

Это #define d макрос, который расширяет ничего при создании (не Shared) libc.a и:

при компиляции libc.so.6.

Это означает, что это псевдоним __GI_strcspn , который имеет то же значение, что и strcspn , но не экспортируется из libc.so.6 (т. Е. Это внутренний символ).

Действительно ли это важно для остальной части кода?

Реклама PVS-Studio — статический анализ кода на языке Си и Си++

Данная статья устарела. Обновляемый список статей о проверенных проектах находится здесь.

Этот документ рекламирует статический анализатор PVS-Studio. Описывается, как использование PVS-Studio уменьшит количество ошибок в коде проекта на языке C/C++/C++11 и сократит затраты на тестирование, отладку и сопровождение кода. Приводится большое количество примеров ошибок, найденных анализатором в различных Open-Source проектах. Документ описывает PVS-Studio на момент версии 4.38 от 12 октября 2011 и, как следствие, не отражает возможности следующих версий. Чтобы познакомиться с новыми возможностями, предлагаем посетить сайт продукта www.viva64.com или поискать обновленный вариант этой статьи.

Что такое статический анализ кода

Статический анализ кода — это методология выявления ошибок в программном обеспечении. Методология основана на быстром и эффективном просмотре программистом участков кода, помеченных статическим анализатором там, где потенциально может находиться ошибка. Другими словами инструмент для статического анализа определяет в тексте программы места, содержащие ошибки, предрасположенные к ошибкам или имеющие плохое форматирование. Такие участки кода предоставляются программисту для изучения, и он может принять решение о модификации данного участка программы.

Статический анализ кода отчасти схож с методикой обзоров кода (code review). Отличие в том, что роль экспертов здесь выполняет программное обеспечение. Конечно, возможности программы далеки от возможности команды людей. Но зато, программа неутомима, и её использование на несколько порядков дешевле по сравнению с обзорами кода.

Статические анализаторы могут быть как общего назначения (например, PVS-Studio, Microsoft PREFast, Gimpel PC-Lint, Parasoft C++Test), так и специализированными для поиска определенных классов ошибок (например, Chord для верификации параллельных Java программ). Статические анализаторы обычно используются в компаниях с высокой культурой разработки и зрелыми процессами разработки программного обеспечения. Это связано с тем, что инструменты статического анализа требуют понимания принципов их работы, необходимости уделить некоторое время их изучению и интеграции в процесс разработки. Взамен они позволяют выявить большое количество ошибок на самых ранних этапах разработки программного кода.

Основное преимущество использования статических анализаторов кода состоит в возможности существенного снижения стоимости устранения дефектов в программе. Чем раньше ошибка выявлена, тем меньше стоимость ее исправления. Так, согласно данным, приведенным в книге Макконнелла «Совершенный Код», исправление ошибки на этапе тестирования обойдется в десять раз дороже, чем на этапе конструирования (кодирования):

Рисунок 1. Средняя стоимость исправления дефектов в зависимости от времени их внесения и обнаружения (данные для таблицы взяты из книги С. Макконнелла «Совершенный Код»).

Инструменты статического анализа позволяют выявить большое количество ошибок этапа конструирования, что существенно снижает стоимость разработки всего проекта.

Инструмент PVS-Studio

PVS-Studio это легкий в изучении и использовании инструмент статического анализа кода. PVS-Studio представляет собой модуль расширения к среде программирования Visual Studio 2005/2008/2010. Впрочем, анализатор можно использовать и из командной строки, о чем более подробно можно узнать в документации.

Основные характеристики PVS-Studio:

Поддерживаемые языки: С / C++ / С++11 (в тех рамках, в которых эти языки поддерживает компилятор Visual C++).

Операционные системы: 32-битные и 64-битные версии Windows XP / 2003 / Vista / 2008 / 7.

Системные требования: Системные требования к анализатору совпадают с требованиями к Microsoft Visual Studio.

Режимы работы: Как расширение среды Visual Studio 2005/2008/2010. Запуск из командной строки (есть solution-файл / нет solution-файла). Примечание: работа PVS-Studio с Visual C++ Express Edition не возможна, поскольку эта система не поддерживает модули расширений.

Интеграция с системами continuous integration: Есть.

Документация: На русском и английском языке.

Возможность скачать демонстрационную версию: Есть.

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

Типы выявляемых дефектов

Диагностические сообщения PVS-Studio можно разделить на 4 группы:

1. Диагностика 64-битных ошибок

PVS-Studio имеет, на наш взгляд, самый мощный в мире набор правил статического анализа, выявляющих 64-битные ошибки. Эти диагностические правила помогают перенести 32-битный код на 64-битную систему и контролировать написание нового 64-битного кода. На тему 64-битных ошибок мы написали множество интересных статей, с которыми можно познакомиться на нашем сайте:

  • Коллекция примеров 64-битных ошибок в реальных программах: http://www.viva64.com/ru/a/0065/
  • 64-битный конь, который умеет считать: http://www.viva64.com/ru/a/0043/
  • Что такое size_t и ptrdiff_t: http://www.viva64.com/ru/a/0050/
  • Уроки разработки 64-битных приложений на языке Си/Си++: http://www.viva64.com/ru/l/

В перечисленных статьях подробно рассмотрена тематика 64-битных дефектов, и здесь мы не будем на ней останавливаться. Приведем только один пример, чтобы дать понять о каких ошибках в этих статьях идёт речь:

Этот код был обнаружен PVS-Studio в проекте Newton Game Dynamics. Обратите внимание на второй вызов функции ‘memset’. Из-за случайной опечатки эта функция заполняет нулями ‘sizeof(faceOffsetHitogram)’ байт, а не ‘sizeof(mainSegmenst)’.

Почему эту ошибку мы называем 64-битной? Дело в том, что эта ошибка проявит себя только при компиляции кода в 64-битном режиме. В 32-битной программе размер указателей и типа ‘dgInt32’ совпадает. Это значит, что в 32-битной программе размер массивов faceOffsetHitogram и mainSegmenst совпадут. В 64-битной программе эти массивы занимают разное количество байт, а следовательно функция ‘memset’ заполнит только часть массива.

2. Выявление неэффективных конструкций

PVS-Studio содержит ряд правил для выявления конструкций, код которых можно оптимизировать. Неэффективный код это, конечно, не ошибка, и каждый программист сам должен решить, использовать эти диагностические сообщения или нет. В качестве пояснения приведем простой пример:

Анализатор выдаст для этого кода предупреждение V804. Этот код можно оптимизировать, если предварительно вычислить длину строки и поместить её во временную переменную. Это не всегда нужно. Однако, если подобный код выполняется множество раз в цикле, такая оптимизация может быть весьма полезна.

3. Диагностические правила, имплементированные по заказу пользователей

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

4. Диагностические правила общего назначения

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

Лучшей рекламой для программистов являются примеры исходного кода. Поэтому не будем писать абстрактные тексты о возможностях статического анализа, а покажем эти самые примеры на практике. Если вы стремитесь поскорее посмотреть их, то перейдите к разделу «Примеры выявленных ошибок в различных open-source проектах». Или ещё немного потерпите и прочитайте всю скромную хвалебную статью по порядку.

Работа с отчетом PVS-Studio

Если вы работаете с анализатором в рамках среды Visual Studio, то в вашем распоряжении будет простой интерфейс, состоящий из 2 компонент: новое меню и интерактивное окно для работы с предупреждениями.

1. Меню PVS-Studio

Рисунок 2. Меню PVS-Studio, интегрированное в Visual Studio 2005.

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

2. Интегративное окно с предупреждениями

Рисунок 3. Окно PVS-Studio для работы с предупреждениями (кликните на картинку для просмотра полноценного снимка экрана)

  • Навигация по коду. Осуществляется двойным щелчком мыши по сообщению с помощью иконок вперёд/назад или с помощью горячих клавиш ‘Alt — [‘ и ‘Alt — ]’.
  • Открытие документации для соответствующей ошибки. Необходимо щелкнуть мышкой на коде ошибки.
  • Включение/выключение различных наборов предупреждений. Например, можно просмотреть только предупреждения первого уровня важности, относящиеся к 64-битным диагностикам.
  • Различные способы сортировки и фильтрации сообщений. Возможен поиск определенных сообщений. Есть и другие мелкие возможности, такие как «пометка звездочкой» сообщений, вызвавших интерес.
  • Для отдельных сообщений также доступно контекстное меню (наведите на сообщение и нажмите правую кнопку мыши). Например, с помощью него можно осуществить такую важную функцию, как пометить диагностику как ложную (Mark selected errors as False Alarms).

Инкрементальный анализ кода

PVS-Studio позволяет дешево внедрить статический анализ в процесс разработки.

Если включен режим «Incremental Analysis after Build», то анализатор запускается сразу после компиляции и проверяет только те файлы, которые были «задеты» правками пользователя. То есть пользователь видит ошибки только в том коде, который он непосредственно пишет или затрагивает при рефакторинге.

Программисту не надо беспокоиться о большом объеме кода, с которым он в данный момент не работает. Возможно, этому коду уже более 5 лет. Он практически не модифицируется и большинство дефектов в нем уже исправлено. Этот код не надо бросаться проверять в первую очередь, и анализатор этого и не делает. Программист будет видеть предупреждения только в свежем коде. А уж если у него будет дополнительное время, он всегда сможет проверить проект целиком, заглянув в самые редко посещаемые места.

Инкрементальный анализ выполняется в фоновом режиме, и вы можете, не дожидаясь его завершения, заниматься другими действиями и правкой кода. Если анализатор что-то найдет, то он просигнализирует это сменой цвета иконки окна и всплывающим уведомлением.

Рисунок 5. Всплывающее уведомление PVS-Studio.

Конечно, мы все не любим всякие назойливые всплывающие уведомления. Но в данном случае оно будет явно полезно программисту и будет появляться редко, при условии, что он не злоупотребляет ошибками в коде.

Попробуйте. Мы уверены, программистам понравится этот режим работы.

Возврат инвестиций при использовании PVS-Studio (ROI)

Этот раздел на тот случай, если попробуете анализатор PVS-Studio и найдете только несколько ошибок. У вас возникнет вопрос, в чём польза от использования статического анализа, если он смог выявить только небольшое количество дефектов? Если это произошло, то вспомните про этот текст и ознакомьтесь со следующими 2 заметками:

Если совсем кратко, то польза статического анализа проявляется при его регулярном использовании, а не от случая к случаю. Те ошибки, которые мог бы легко и быстро обнаружить PVS-Studio, были уже исправлены с помощью таких дорогостоящих процедур, как отладка, тестирование или получение замечаний от пользователей.

Поддержка наших пользователей

С нашими клиентами общаются непосредственно разработчики анализатора, что позволяет быстро отвечать даже на сложные вопросы, связанные с программированием. Мы всегда готовы помочь в адаптации и интеграции PVS-Studio в процесс разработки, используемый в компании клиента. В качестве примера приведем несколько функциональных возможностей, реализованных по просьбе пользователей:

  • создание файлов-отчетов в определенном формате;
  • разработка скриптов (сценариев) для особых вариантов запуска PVS-Studio;
  • доработка инструмента для взаимодействия с той или иной системой непрерывной интеграции или сборки;
  • введение новых настроек в инструменте;
  • проверка не всех файлов проекта/решения, а только некоторых (по именам или по времени модификации – к примеру, за последний день).

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

Примеры выявленных ошибок в различных open-source проектах

Мы регулярно проверяем известные и не очень известные open-source проекты. Это делается для того, чтобы иметь возможность написать соответствующую рекламную заметку и протестировать работу анализатора PVS-Studio на новом коде. Многие читатели спрашивают, сообщаем ли мы авторам проектов о найденных ошибках. Да, в обязательном порядке. И так случается, что иногда после этого у нас появляется новый клиент.

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

Из проверенных нами проектов, конечно, взято только по несколько ошибок. Если описывать все найденные дефекты, то это уже будет справочник. Список проверенных проектов:

Рисунок 6. Логотипы проверенных проектов

Ошибки работы с массивами и строками

Ошибки в обработке массивов и строк являются самым обширным классом дефектов в программах на языке Си/Си++. Это плата за возможность эффективной низкоуровневой работы с оперативной памятью. В статье будет показа лишь малая часть подобных ошибок, найденных анализатором PVS-Studio. Но думаем, что любой Си/Си++ программист понимает, как их много и как они коварны.

Пример 1. Проект Wolfenstein 3D. Очистка только части объекта.

Ошибка найдена с помощью диагностики V568: It’s odd that the argument of sizeof() operator is the ‘&itemInfo’ expression. cgame cg_weapons.c 1467.

Оператор sizeof() вычисляет размер указателя, а не структуры ‘itemInfo_t’. На самом деле должно быть написано «sizeof(*itemInfo)».

Пример 2. Проект Wolfenstein 3D. Копирование только части матрицы.

Ошибка найдена с помощью диагностики V511: The sizeof() operator returns size of the pointer, and not of the array, in ‘sizeof(src)’ expression. Splines math_matrix.h 94

Как правило, программисты ожидают, что ‘sizeof(src)’ вернет размер массива равного «3*3*sizeof(float)». Но согласно стандарту языка, ‘src’ это просто указатель, а вовсе не массив. Таким образом, матрица будет скопирована только частично. Функция ‘memcpy’ скопирует 4 или 8 байт (размер указателя) в зависимости от того, этот код 32-битный или 64-битный.

Если хочется скопировать матрицу целиком, то можно передать в функцию ссылку на массив. Корректный вариант кода:

Пример 3. Проект FAR Manager. Очистка только части массива.

Ошибка найдена с помощью диагностики V579: The memset function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the third argument. far treelist.hpp 66

Скорее всего, здесь не хватает умножения на количество очищаемых элементов, и код должен был выглядеть так: «memset(Last, 0, LastCount * sizeof(*Last));».

Пример 4. Проект ReactOS. Некорректное вычисление длины строки.

Ошибка найдена с помощью диагностики V579: The strncmp function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the third argument. vga vbe.c 57

Имеющиеся в коде вызовы функции ‘strncmp’ сравнивают только несколько первых символов, а не строки целиком. Ошибка в том, что для вычисления длины строк используется совершенно неуместный здесь оператор sizeof(). Оператор sizeof() вычисляет размер указателя, а вовсе не количество байт в строке.

Самое неприятное и коварное с этой ошибкой в том, что этот код почти работает. В 99% случаев сравнение по первым нескольким символам бывает достаточно. Зато 1% может подарить массу удовольствий и долгую отладку.

Пример 5. Проект VirtualDub. Выход за рамки массива (явный индекс).

Фрагмент кода найден с помощью диагностики V557: Array overrun is possible. The ‘9’ index is pointing beyond array bound. VirtualDub f_convolute.cpp 73

Это пример не настоящей ошибки, но зато хорошей диагностики. Почему это не ошибка, поясняет автор: http://www.virtualdub.org/blog/pivot/entry.php? >

Пример 6. Проект CPU Identifying Tool. Выход за рамки массива (индекс в макросе).

Ошибка найдена с помощью диагностики V557: Array overrun is possible. The ’64’ index is pointing beyond array bound. stickies stickies.cpp 7947

Эта ошибочная ситуация является разновидностью предыдущей. Терминальный ноль записывается за границей массива. Корректным вариантом кода будет: «findWhat[FINDBUFFLEN — 1] = ‘\0’;».

Пример 7. Проект Wolfenstein 3D. Выход за рамки массива (неверное выражение).

Ошибка найдена с помощью диагностики V557: Array overrun is possible. The ‘sizeof (bs->teamleader)’ index is pointing beyond array bound. game ai_team.c 548

И ещё один пример выхода за границу массива при использовании явно заданного индекса. Эти примеры показывают, что такие, на первый взгляд простые, ошибки распространены гораздо шире, чем может показаться.

Терминальный ноль записывается за пределами массива ‘teamleader’. Корректный вариант:

Пример 8. Проект Miranda IM. Копирование только части строки.

Ошибка найдена с помощью диагностики V512: A call of the ‘memcpy’ function will lead to a buffer overflow or underflow. tabsrmm utils.cpp 1080

Если используются Unicode-строки, то один символ занимает не один байт, а 2 или 4 байта (в зависимости от используемой модели данных в компиляторе). К сожалению, про этом легко забывается, и нередко в программах можно встретить дефекты, аналогичные тому, который показан здесь.

Функция ‘CopyMemory’ скопирует только часть строки L»mailto:», так как работает с байтами, а не с символами. Код можно исправить, используя более подходящую функцию для копирования строк или, по крайней мере, умножив число 7 на sizeof(wchar_t).

Пример 9. Проект CMake. Выход за границу массива внутри цикла.

Ошибка найдена с помощью диагностики V557: Array overrun is possible. The value of ‘i’ index could reach 367. cmlibarchive archive_windows.c 1140, 1142

Обработчик ошибок сам содержит ошибку. Оператор sizeof() возвращает размер массива в байтах, а не количество элементов в нём. В результате, в цикле программа попытается перебирать намного больше элементов, чем должна. Корректный цикл должен выглядеть так:

Пример 10. Проект CPU Identifying Tool. Печать строки саму в себя.

Ошибка найдена с помощью диагностики V541: It is dangerous to print the string ‘szOperatingSystem’ into itself. stickies camel.cpp 572, 603

К достаточно печальным последствиям может привести попытка форматированной печати строки в саму себя. Результат работы такого кода зависит от входных данных и предсказать, что произойдет сложно. Скорее всего, результатом работы станет бессмысленная строка или возникнет Access Violation.

Эту ошибку вполне можно отнести к категории «уязвимость в коде». В ряде программ, подав на вход специальные данные, можно использовать такие участки кода, чтобы привести к переполнению буфера или иным полезным злоумышленнику действиям.

Пример 11. Проект FCE Ultra. Для строки выделяется памяти меньше, чем надо.

Ошибка найдена с помощью диагностики V518: The ‘realloc’ function allocates strange amount of memory calculated by ‘strlen(expr)’. Perhaps the correct variant is ‘strlen(expr) + 1’. fceux cheat.cpp 609

Причиной ошибки является опечатка. Аргументом функции strlen() должен быть указатель ‘name’, а вовсе не выражение «name+1». В результате, функция realloc выделит на 2 байта меньше памяти, чем необходимо. Один байт потеряется из-за того, что к длине строки не прибавлена единица. Другой байт потеряется из-за того, что функция ‘strlen’ считает длину строки, пропустив первый символ.

Пример 12. Проект Notepad++. Частичное обнуление массива.

Ошибка найдена с помощью диагностики V512: A call of the memset function will lead to a buffer overflow or underflow. notepadPlus DockingManager.cpp 60

Очередная путаница с количеством элементов в массиве и его размером. Забыто умножение на sizeof(int).

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

Неопределенное поведение (Undefined behavior)

В начале немного теории.

Неопределённое поведение (англ. undefined behaviour) — свойство некоторых языков программирования (наиболее заметно в Cи и Си++) в определённых ситуациях выдавать результат, зависящий от реализации компилятора. Другими словами, спецификация не определяет поведение языка в любых возможных ситуациях, а говорит: «при условии А результат операции Б не определён». Допускать такую ситуацию в программе считается ошибкой, даже если на некотором компиляторе программа успешно выполняется, она не будет кроссплатформенной и может отказать на другой машине в другой ОС и даже на других настройках компилятора.

Точка следования (англ. Sequence point) — в программировании любая точка программы, в которой гарантируется, что все побочные эффекты предыдущих вычислений уже проявились, а побочные эффекты последующих еще отсутствуют. Подробнее про точки следования и какие ситуации неопределенного поведения с ними связаны можно прочитать здесь: http://www.viva64.com/ru/t/0065/.

Пример 1. Проект Chromium. Некорректное использование умного указателя.

Ошибка найдена с помощью диагностики V554: Incorrect use of auto_ptr. The memory allocated with ‘new []’ will be cleaned using ‘delete’. interactive_ui_tests accessibility_win_browsertest.cc 171

Это пример демонстрирует, когда использование умного указателя может привести к неопределенному поведению. Проявить себя это может повреждением кучи, аварийным завершением программы, неполному разрушению объектов или любым другим образом. Ошибка заключается в том, что память выделяется с помощью оператора new [], а освобождается в деструкторе класса ‘auto_ptr’ с помощью оператора delete:

Чтобы исправить ситуации, необходимо использовать более подходящий класс, например boost::scoped_array.

Пример 2. Проект IPP Samples. Классический Undefined behavior.

Ошибка найдена с помощью диагностики V567: Undefined behavior. The ‘pTemp’ variable is modified while being used twice between sequence points. me umc_me_cost_func.h 168

Это классический пример неопределенного поведения программы. Именно такую конструкцию используют для демонстрации Undefined behavior во многих статьях. Неизвестно, увеличится pTemp на единицу или нет. Два действия по изменению значения pTemp находятся в одной точке следования. Это значит, что компилятор может создать следующий псевдокод:

pTemp = pTemp + 1;

А может, создать другой вариант кода:

pTemp = pTemp + 1;

Какой именно код будет создан, зависит от компилятора и ключей оптимизации.

Пример 3. Проект Fennec Media Project. Сложное выражение.

Ошибка найдена с помощью диагностики V567: Undefined behavior. The ‘m_nCurrentBitIndex’ variable is modified while being used twice at single sequence point. MACLib unbitarrayold.cpp 78

Между двумя использованиями переменной m_nCurrentBitIndex нет точек следования. Это значит, что стандартом не определено, в какой момент эта переменная увеличится. Соответственно, в зависимости от компилятора и ключей оптимизации, этот код может работать по-разному.

Пример 4. Проект Miranda IM. Сложное выражение.

Ошибка найдена с помощью диагностики V567: Undefined behavior. The ‘s’ variable is modified while being used twice between sequence points.msne zxml.c 371

Здесь используется префиксный инкремент переменной. Но это ничего не значит. Нет никакой гарантии, что переменная ‘s’ будет увеличена перед вызовом функции strspn().

Ошибки, связанные с приоритетом операций

Для легкости понимания примеров освежим в памяти таблицу приоритетов операций.

Рисунок 7 — Приоритет операций языка Си/Си++

Пример 1. Проект MySQL. Приоритет операции ! и &.

Ошибка найдена с помощью диагностики V564: The ‘&’ operator is applied to bool type value. You’ve probably forgotten to include parentheses or intended to use the ‘&&’ operator. innobase ha_innodb.cc 6789

По замыслу программиста, часть выражения должна проверить, что определенный бит в переменной ‘create_info->options’ равен нулю. Однако, приоритет операции ‘!’ выше, чем операции ‘&’. И выражение работает так:

Чтобы код работал правильно, необходимо использовать дополнительные скобки:

Или, что на наш взгляд более красиво, написать так:

Пример 2. Проект eMule Plus. Приоритет операции * и ++.

Ошибка найдена с помощью диагностики V532: Consider inspecting the statement of ‘*pointer++’ pattern. Probably meant: ‘(*pointer)++’. emule customautocomplete.cpp 277

Если указатель ‘pceltFetched’ не нулевой, функция должна увеличивать переменную типа ULONG, на которую этот указатель указывает. Ошибка в том, что в приоритет операции ‘++’ выше, чем приоритет операции ‘*’ (разыменования указателя). Строка «*pceltFetched++;» эквивалентна следующим действиям:

Фактически, здесь просто увеличивается значение указателя. Чтобы код стал корректен, необходимо добавить скобки: «(*pceltFetched)++;».

Пример 3. Проект Chromium. Приоритет операции & и !=.

Ошибка найдена с помощью диагностики V564: The ‘&’ operator is applied to bool type value. You’ve probably forgotten to include parentheses or intended to use the ‘&&’ operator. base platform_file_win.cc 216

Очень легко забыть, что приоритет операции ‘!=’ выше, чем операции ‘&’. Так произошло и здесь. В результате получается выражение:

Ещё раз упростим выражение:

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

Пример 4. Проект BCmenu. Путаница с IF и ELSE.

Ошибка найдена с помощью диагностики V563: It is possible that this ‘else’ branch must apply to the previous ‘if’ statement. fire bcmenu.cpp 1853

Здесь ошибка не с приоритетом операций, но родственная ей. Не учтено, что ветка ‘else’ относится к ближайшему оператору ‘if’. Видно, что код оформлен исходя из того, как будто он работает так:

Но на самом деле он эквивалентен следующей конструкции:

Пример 5. Проект IPP Samples. Приоритет операции ?: и |.

Ошибка найдена с помощью диагностики V502: Perhaps the ‘?:’ operator works in a different way than it was expected. The ‘?:’ operator has a lower priority than the ‘|’ operator. vm vm_file_win.c 393

В зависимости от значения переменной ‘islog’, выражение должно было быть равно «FILE_ATTRIBUTE_NORMAL» или «FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING». Но этого не происходит. Приоритет операции ‘?:’ ниже, чем операции ‘|’. В результате код работает так:

Так как FILE_ATTRIBUTE_NORMAL равняется 0x00000080, то условие всегда истинно. Это означает, что в mds[3] всегда будет записываться 0.

Пример 6. Проект Newton Game Dynamics. Приоритет операции ?: и *.

Ошибка найдена с помощью диагностики V502: Perhaps the ‘?:’ operator works in a different way than it was expected. The ‘?:’ operator has a lower priority than the ‘*’ operator. physics dgminkowskiconv.cpp 1061

В этом коде ошибка вновь связана с низким приоритетом операции ‘?:’. Условием для оператора ‘?:’ является бессмысленное подвыражение «dgFloat32 (1.0e-24f) * (den > dgFloat32 (0.0f))». Исправить ситуацию можно, используя круглые скобки.

Кстати, программисты часто забывают о коварстве оператора ‘?:’. Предлагаю заметку на эту тему: «Как уменьшить вероятность ошибки на этапе написания кода. Заметка N2».

Ошибки форматированного вывода

Примеры этих ошибок однообразны и скучны, поэтому рассмотрим совсем немного примеров. Их суть в том, что функции с переменным количеством аргументов принимают фактические аргументы, несовместимые со строкой, задающей формат. Любой программист, использующий такие функции, как printf(), хорошо знаком с данной разновидностью ошибок.

Пример 1. Проект ReactOS. Некорректная печать символа типа WCHAR.

Ошибка найдена с помощью диагностики V576: Incorrect format. Consider checking the third actual argument of the ‘fprintf’ function. The char type argument is expected. regedit regproc.c 293

Функция fprinf() должна распечатать символ типа char. Но третьим аргументов является символ типа WCHAR. Пользователю будет выдано некорректно сформированное сообщение. Чтобы код стал корректен, в строке, задающей формат, следует заменить ‘%c’ на ‘%C’.

Пример 2. Проект Intel AMT SDK. Пропущенный символ ‘%’.

Ошибка найдена с помощью диагностики V576: Incorrect format. A different number of actual arguments is expected while calling ‘_snprintf’ function. Expected: 18. Present: 19. mod_pvs mod_pvs.cpp 308

На взгляд найти здесь ошибку очень непросто. Однако, статический анализатор PVS-Studio неутомим и замечает, что функция принимает больше фактических аргументов, чем задано в строке форматирования. Дело в том, что в одном месте пропущен символ ‘%’. Выделим этот фрагмент:

Пример 3. Проект Intel AMT SDK. Неиспользуемый аргумент.

Ошибка найдена с помощью диагностики V576: Incorrect format. A different number of actual arguments is expected while calling ‘printf’ function. Expected: 1. Present: 2. RemoteControlSample remotecontrolsample.cpp 792

Ошибка в том, что переменная ‘tmp’ никак не используется при выводе информационного сообщения.

Пример 4. Проект G3D Content Pak. Печать бессмысленных данных.

Ошибка найдена с помощью диагностики V520: The comma operator ‘,’ in array index expression ‘[0, 0]’. graphics3D anyval.cpp 275

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

Рассмотрим, как работает выражение ‘m[0, 1]’. Вначале вычисляется выражение «0, 1». Результатом такого выражения является 1. Затем вызывается функция ‘operator[]’ в классе Matrix3. Функция принимает фактический аргумент 1 и вернет указатель на первую строку в матрице. Именно значение этого указателя и будет распечатано функцией ‘printf()’, хотя она ожидает значение типа float.

Примеры выявленных опечаток в коде

Огромное количество ошибок при программировании допускается из-за опечаток. Большинство этих ошибок быстро выявляются на самых ранних этапах тестирования. Однако, некоторые такие ошибки надолго остаются в коде программы, доставляя беспокойство программистам и неудобства пользователям.

Количество таких ошибок можно существенно сократить, используя статический анализатор PVS-Studio. Анализатор найдет их еще до начала тестирования, что существенно сократит цену нахождения и устранения дефектов.

Пример 1. Проект Miranda IM. Присваивание внутри IF.

Ошибка найдена с помощью диагностики V560: A part of conditional expression is always true: 0x29. icqoscar8 fam_03buddy.cpp 632

Из-за опечатки, внутри условия оператора ‘if’ происходит присваивание. Корректное условие: «if (wTLVType == 0x29 && wTLVLen == sizeof(DWORD))».

Пример 2. Проект ReactOS. Ошибка присваивания.

Ошибка найдена с помощью диагностики V570: The ‘mii->cch’ variable is assigned to itself. user32 menu.c 4347

Значение переменной присваивается само себе. Очевидно, планировалось написать так: «mii->cch = miiW->cch;».

Пример 3. Проект Clang. Опечатка в названии объекта.

Ошибка найдена с помощью диагностики V501: There are identical sub-expressions ‘LBO->hasNoUnsignedWrap ()’ to the left and to the right of the ‘&&’ operator. LLVMAnalysis instructionsimplify.cpp 1891

Имеется опечатка при использовании переменных с похожими именами. В первой строке надо использовать как переменную LBO, так и RBO. Исправленный вариант кода:

Пример 4. Проект Notepad++. Неправильная проверка состояния.

Ошибка найдена с помощью диагностики V501: There are identical sub-expressions to the left and to the right of the ‘&&’ operator. _isPointXValid && _isPointXValid

Дважды используется имя ‘_isPointXValid’. На самом деле, функция должна вернуть: «_isPointXValid && _isPointYValid».

Пример 5. Проект StrongDC++. Неудачная проверка наличия \r\n.

Ошибка найдена с помощью диагностики V501: There are operator. miniupnpc miniupnpc.c 153

Из-за опечатки дважды проверяем наличие символа ‘\r’. На самом деле еще должно проверяться наличие символа ‘\n’.

Пример 6. Проект G3D Content Pak. Не там поставлена круглая закрывающаяся скобка.

Ошибка найдена с помощью диагностики V575: The ‘memcmp’ function processes ‘0’ elements. Inspect the ‘third’ argument. graphics3D matrix4.cpp 269

Одна круглая скобка закрывается не там, где необходимо. Получается, что размер сравниваемой области памяти вычисляется выражением «sizeof(Matrix4) == 0». Это выражение всегда даёт в результате значение ‘false’. Затем ‘false’ превращается в целочисленное значение, равное 0. Корректный код:

Пример 7. Проект QT. Ошибка копирования членов структуры.

Ошибка найдена с помощью диагностики V570: The ‘transition->m_hasGetterSetterProperties’ variable is assigned to itself. QtScript structure.cpp 512

Рассматривая подобный код, очень сложно заметить ошибку. Однако, она здесь есть. Поле ‘m_hasGetterSetterProperties’ копируется само в себя. Корректный код должен выглядеть так:

Пример 8. Проект Apache HTTP Server. Лишний оператор sizeof.

Ошибка найдена с помощью диагностики V568: It’s odd that the argument of sizeof() operator is the ‘sizeof (SECURITY_ATTRIBUTES)’ expression. libhttpd util_win32.c 115

В поле ‘nLength’ должен был записан размер структуры ‘SECURITY_ATTRIBUTES’. В коде допущена опечатка. Оператор ‘sizeof’ здесь используется два раза. Как результат, в поле ‘nLength’ записывается размер, которыё имеет тип ‘size_t’. Корректный код:

Пример 9. Проект FCE Ultra. Двойное объявление переменной.

Ошибка найдена с помощью диагностики V561: It’s probably better to assign value to ‘x’ variable than to declare it anew. Previous daclaration: ines.cpp, line 960. fceuxines.cpp 962

Переменная ‘x’ должна хранить информацию, удалось ли открыть файл или нет. Из-за опечатки, вместо присваивания переменной единицы создается и инициализируется новая переменная с именем ‘x’. Корректный код, должен был быть таким:

Пример 10. Проект Notepad++. Использование оператора &&, вместо &.

Ошибка найдена с помощью диагностики V560: A part of conditional expression is always true: 0xff. notepadPlus babygrid.cpp 694

Выражение «(lParam >> 16) && 0xff» не имеет никакого практического смысла и всегда равно значению 1 (true). Здесь опечатка заключается в том, что используется оператор ‘&&’, хотя должен был использоваться оператор ‘&’.

Пример 11. Проект WinDjView. Недописанное условие.

Ошибка найдена с помощью диагностики V560: A part of conditional expression is always true: 0xA. WinDjView xmlparser.cpp 45 False

Функция IsValidChar всегда возвращает значение ‘true’. Из-за опечатки, в одном месте пропущено сравнение: «. || 0xA || . «.

Пример 12. Проект Fennec Media Project. Лишняя точка с запятой.

Ошибка найдена с помощью диагностики V529: Odd semicolon ‘;’ after ‘for’ operator. settings.c 483

Про то, как опасна лишняя точка с запятой ‘;’ знают все программисты на Си и Си++. К сожалению, это знание не мешает делать подобные опечатки. После первого оператора ‘for’ стоит лишняя точка с запятой, что делает этот фрагмент программы неработоспособным.

Пример 13. Проект QT. Забытый оператор break.

Ошибка найдена с помощью диагностики: V519: The ‘ret’ variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 3765, 3767. QtGui qcleanlooksstyle.cpp 3767

Классическая ошибка — пропущен ‘break’ внутри оператора ‘switch’. Думаю, комментарии здесь излишни.

Пример 14. Проект Miranda IM. Присваивание вместо сравнения.

Ошибка найдена с помощью диагностики V559: Suspicious assignment ins >

Опечатка находится внутри условия оператора ‘if’. Вместо ‘==’ написано просто ‘=’. Функция некорректно обработает ситуацию, когда некий элемент не будет найден.

Пример 15. Проект IPP Samples. Некорректный индекс.

Ошибка найдена с помощью диагностики V557: Array overrun is possible. The ’30’ index is pointing beyond array bound. avs_enc umc_avs_enc_compressor_enc_b.cpp 495

Обратите внимание вот на этот фрагмент: «m_pMbInfo->refIdx[dir][30]». Из-за опечатки вместо индекса 3 написано число 30. Кстати, этот пример хорошо показывает относительность разделения в статье ошибок по типам. Эту ошибку вполне можно отнести к разделу «Ошибки работы с массивами и строками». Деление условно и сделано, чтобы показать разнородность ошибок, которые может найти анализатор PVS-Studio.

Пример 16. Проект ReactOS. Опечатка в макросе.

Ошибка найдена с помощью диагностики V519: The ‘v2’ variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 343, 343. win32k gradient.c 343

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

В этот раз до пункта под номером 13 закончить раздел не получилось. Уж очень много в программах ошибок связано именно с опечатками. Гораздо больше, чем думают программисты. Этот раздел можно продолжить и дальше. В нашей коллекции ещё много забавных примеров. Но мы нашли в себе силы всё-таки остановиться на 16 примерах.

Неверное использование базовых функций и классов

Пример 1. Проект Fennec Media Project. Отсутствие двух терминальных нулей.

Ошибка найдена с помощью диагностики V540: Member ‘lpstrFilter’ should point to string terminated by two 0 characters. base windows.c 5309

В Windows API есть структуры, в которых указатели на строки должны заканчиваться двойным нулем. Именно на такую строку и указывает член ‘lpstrFilter’ в структуре OPENFILENAME.

Описание ‘lpstrFilter’ в MSDN:

A buffer containing pairs of null-terminated filter strings. The last string in the buffer must be terminated by two NULL characters.

Если забыть написать в конце дополнительный ноль, то диалог работы с файлами в поле фильтров можем содержать мусор. Исправленный код:

Пример 2. Проект TortoiseSVN. Неверное использование функции ‘remove’.

Ошибка найдена с помощью диагностики V530: The return value of function ‘remove’ is required to be utilized. contextmenu.cpp 442

Функция std::remove не удаляет элементы из контейнера. Она только сдвигает элементы и возвращает итератор на начало мусора. Пусть мы имеем контейнер vector , содержащий элементы 1,2,3,1,2,3,1,2,3. Если выполнить код «remove( v.begin(), v.end(), 2 )», то контейнер будет содержать элементы 1,3,1,3,X,X,X, где X — некий мусор. При этом функция вернет итератор на первый мусорный элемент, и если мы хотим удалить эти мусорные элементы, то должны написать код: «v.erase(remove(v.begin(), v.end(), 2), v.end())».

Пример 3. Проект TortoiseSVN. Использование функции ’empty’ вместо ‘clear’.

Ошибка найдена с помощью диагностики V530: The return value of function ’empty’ is required to be utilized. mailmsg.cpp 40

Ошибка заключается в том, что вместо функции vector::clear() случайно вызывается функция vector::empty() и содержимое массива остается неизменным. Это достаточно распространенная ошибка, так как слова ‘clear’ и ’empty’ достаточно близки по смыслу, и их легко спутать.

Пример 4. Проект WinMerge. Использование функции ’empty’ вместо ‘clear’.

Ошибка найдена с помощью диагностики V530: The return value of function ’empty’ is required to be utilized WinMerge DirActions.cpp 1307, 1308

Вновь ошибка связана с тем, что вместо clear() используется функция empty(). Примеры таких ошибок можно взять и из других проектов: InstantVNC, IPP Samples, Chromium, Intel AMT SDK и так далее. К сожалению, все эти примеры будут однообразны, и рассматривать их будет неинтересно. Но поверьте, эти дефекты встречаются в серьезных проектах, разработанных профессиональными разработчиками.

Пример 5. Проект Pixie. Использование функции ‘alloca’ внутри циклов.

Ошибка найдена с помощью диагностики V505: The ‘alloca’ function is used inside the loop. This can quickly overflow stack. ri polygons.cpp 1120

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

Пример 6. Проект Miranda IM. Перепутанные аргументы.

Ошибка найдена с помощью диагностики V575: Function receives an odd argument. clist_modern modern_image_array.cpp 59

Функция ‘memset’ обрабатывает 0 элементов. То есть фактически, ничего не делает. Причина в перепутанных аргументах. Корректный вызов функции memset:

Примеры бессмысленного кода

Пример 1. Проект IPP Samples. Недописанное условие.

Ошибка найдена с помощью диагностики V503: This is a nonsensical comparison: pointer Пример 2. Проект Pc Ps2 Emulator. Некорректный switch.

Ошибка найдена с помощью диагностики V560: A part of conditional expression is always true: 2. pcsx2 debugger.cpp 321

Подобный код не имеет практического смысла. Видимо, на самом деле планировалось написать так:

Пример 3. Проект CPU Identifying Tool. Слишком строгое условие.

Ошибка найдена с помощью диагностики V550: An odd precise comparison: x == 0. It’s probably better to use a comparison with defined precision: fabs(A — B) ‘ Пример 4. Проект Lugaru. Двойное присваивание.

Ошибка найдена с помощью диагностики V519: The ‘radius’ object is assigned values twice successively. Perhaps this is a mistake. Lugaru gamedraw.cpp 1505

Возможно, для эксперимента, в переменную ‘radius’ явно записали значение 110. А потом забыли удалить эту строку. В результате, имеем бессмысленный и возможно даже ошибочный код.

Пример 5. Проект QT. Дублирующаяся проверка.

Ошибка найдена с помощью диагностики V501: There are identical sub-expressions to the left and to the right of the ‘&&’ operator. Qt3Support q3richtext.cpp 6978

Ошибка найдена с помощью диагностики V516: Cons >

Проверка «sf_error != 0» всегда возвращает true, так как ‘sf_error’ это имя функции, в которой мы находимся.

Пример 7. Проект IPP Samples. Странный код внутри цикла.

Ошибка найдена с помощью диагностики V532: Consider inspecting the statement of ‘*pointer++’ pattern. Probably meant: ‘(*pointer)++’. mpeg2_dec umc_mpeg2_dec.cpp 59

Видимо, тело цикла не дописано, так как в текущем виде оно не имеет практического смысла.

Всегда ложные или истинные условия

Очень большой и распространенный вид ошибок. Также эти ошибки очень сильно варьируются по уровню критичности. К неопасным ошибкам можно отнести некорректные условия в ASSERT, которые на самом деле ничего не проверяют. К опасным ошибкам относятся неправильные проверки размеров буфера или индексов.

Пример 1. Проект Shareaza. Диапазон значений типа char.

Ошибка найдена с помощью диагностики V547: Expression ‘pBytes [ 0 ] == 0xEF’ is always false. The value range of signed char type: [-128, 127]. Shareaza remote.cpp 350

В данном коде тип ‘TCHAR’ представляет собой тип ‘char’. Диапазон значений char от -128 до 127 включительно. Значение 0xEF в переменной типа char это не что иное, как число -17. При сравнении переменной типа ‘char’ с числом 0xEF, её тип расширяется до типа ‘int’. Но значение по-прежнему лежит в диапазоне [-128..127]. Условие «pBytes[0] == 0xEF» («-17 == 0xEF») всегда ложно и программа работает не так, как задумывалось.

Пример 2. Проект TortoiseSVN. Диапазон значений типа char.

Ошибка найдена с помощью диагностики V547: Expression ‘* utf8CheckBuf == 0xC0’ is always false. The value range of signed char type: [-128, 127]. tortoiseblame.cpp 310

Если дефект в предыдущем примере показался случайной невнимательностью, то здесь это не так. Вот еще аналогичный пример, где условие всегда ложно. Это очень распространенный тип ошибки в разнообразнейших проектах.

Пример 3. Проект VirtualDub. Беззнаковый тип всегда >= 0.

Ошибка найдена с помощью диагностики V547: Expression ‘c Пример 4. Проект Swiss-Army Knife of Trace. Работа с сокетами.

Ошибка найдена с помощью диагностики V547: Expression ‘(m_socketHandle = socket (2, 1, 0)) Пример 5. Проект Chromium. Работа со временем.

Ошибка найдена с помощью диагностики V547: Expression ‘current_idle_time Пример 6. Проект ICU. Ошибка в условии.

Ошибка найдена с помощью диагностики V547: Expression ‘*string != 0 || *string != ‘_» is always true. Probably the ‘&&’ operator should be used here. icui18n ucol_sit.cpp 242

Условие содержит логическую ошибку. Подвыражение «(*string != 0 || *string != ‘_’)» всегда истинно. Один и тот же символ строки не может быть одновременно не равен 0 и ‘_’.

Пример 7. Проект QT. Опасный цикл.

Ошибка найдена с помощью диагностики V547: Expression ‘—size >= 0’ is always true. Unsigned type value is always >= 0. QtCLucene arrays.h 154

Условие (—size >= 0) всегда истинно, так как переменная size имеет беззнаковый тип. Это значит, что если две сравниваемые последовательности одинаковы, то мы выйдем за их пределы. Это в свою очередь приведет к Access Violation или другим сбоям в работе программы.

Пример 8. Проект MySQL. Ошибка в условии.

Ошибка найдена с помощью диагностики V547: Expression ‘str [0] != ‘a’ || str [0] != ‘A» is always true. Probably the ‘&&’ operator should be used here. clientlib my_time.c 340

Условие всегда истинно, ведь символ всегда не равен ‘a’ или не равен ‘A’. Корректная проверка:

Пример 9. Проект QT. Неправильный учёт количества ссылок.

Ошибка найдена с помощью диагностики V545: Such conditional expression of ‘if’ operator is incorrect for the HRESULT type value ‘(HRESULT) 0L’. The SUCCEEDED or FAILED macro should be used instead. phonon_ds9 qbasefilter.cpp 60

Условием проверки является константа S_OK. Так как S_OK это 0, то функция AddRef() никогда не вызывается. Здесь должна была быть проверка: if (hr == S_OK).

Пример 10. Проект TickerTape. Неправильный торнадо.

Ошибка найдена с помощью диагностики V517: The use of ‘if (A) <. >else if (A) <. >‘ pattern was detected. There is a probability of logical error presence. TickerTape wind.cpp 118

Второе условие всегда ложно. Причина в том, что первое условие совпадает со вторым. Вероятно, здесь допущена опечатка.

Пример 11. Проект Apache HTTP Server. Ошибка работы с сокетами в Windows.

Ошибка найдена с помощью диагностики V547: Expression ‘csd Пример 12. Проект QT. Опечатка в сравнениях.

Ошибка найдена с помощью диагностики V517: The use of ‘if (A) <. >else if (A) <. >‘ pattern was detected. There is a probability of logical error presence. Check lines: 2303, 2305. lrelease profileevaluator.cpp 2303

В отмеченной строке должно быть написано «ver == QSysInfo::WV_2003». Из-за этой ошибки утверждение «ret = QLatin1String(«Win2003″)» никогда не будет выполнено.

Уязвимости в коде

Конечно, ошибки уязвимости на самом деле это всё те же опечатки, неправильные условия, и некорректная работа с массивами. Но некоторые ошибки захотелось выделить в отдельную группу, так как они связаны с понятием уязвимости программ. Используя такие ошибки, злоумышленник может попытаться нарушить работоспособность программы, провести атаку с целью получения расширенных прав или выполнить некоторые нужные ему действия.

Пример 1. Проект Ultimate TCP/IP. Неправильная проверка пустой строки.

Ошибка найдена с помощью диагностики V528: It is odd that pointer to ‘char’ type is compared with the ‘\0’ value. Probably meant: *m_szPassword != ‘\0’. UTMail ut_crammd5.cpp 333

Приведенный участок кода должен проверить, что указатель на пароль не равен NULL и что строка не пустая. Но вместо этого два раза проверяет, что указатель не равен NULL. Проверка, что строка пустая не работает. Условие «if (m_szPassword != ‘\0’)» должно было проверять, что в самом начале строки располагается терминальный ноль и значит строка пустая. Но здесь забыто разыменование указателя, и с нулем сравнивается сам указатель. Корректный код:

Пример 2. Проект Chromium. Работа с нулевым указателем.

Ошибка найдена с помощью диагностики V522: Dereferencing of the null pointer ‘plugin_instance’ might take place. Check the logical condition. chrome_frame_npapi chrome_frame_npapi.cc 517

Условие, проверяющее нулевой указатель, написано неверно. В результате, при работе с нулевым указателем возникнет ошибка сегментации. Корректный код должен выглядеть так:

Пример 3. Проект SMTP Client with SSL/TLS. Неполная очистка буфера.

Ошибка найдена с помощью диагностики V512: A call of the ‘memset’ function will lead to a buffer overflow or underflow. CSmtp md5.cpp 212

Функция в целях безопасности пытается очистить буфер, содержащий деликатную (sensitive) информацию. Но получается это плохо. В буфере будут очищен только первый байт. Ошибка в том, что оператор ‘sizeof’ вычисляет размер типа ‘uint1’, а не размер буфера. Корректный код:

С неполноценной очисткой памяти вообще ошибки встречаются достаточно часто. Рассмотрим еще несколько подобных ситуаций.

Пример 4. Проект Chromium. Неполная очистка буфера.

Ошибка найдена с помощью диагностики V512: A call of the ‘memset’ function will lead to underflow of the buffer ‘(exploded)’. base time_win.cc 227

Функция ZeroMemory очищает только часть структуры Exploded. Причина, что оператор ‘sizeof’ возвращает размер указателя. Чтобы исправить ошибку, необходимо разыменовать указатель:

Пример 5. Проект Apache HTTP Server. Неполная очистка буфера.

Ошибка найдена с помощью диагностики V512: A call of the ‘memset’ function will lead to underflow of the buffer ‘(context)’. apr sha2.c 560

Ошибка полностью аналогична рассмотренной ранее. Оператор ‘sizeof’ вычисляет размер указателя. Чтобы исправить ситуацию, необходимо написать: «sizeof(*context)».

Пример 6. Проект Miranda IM. Некорректная обработка строк.

Ошибка найдена с помощью диагностики: V528 It is odd that pointer to ‘char’ type is compared with the ‘\0’ value. Probably meant: *str != ‘\0’. clist_modern modern_skinbutton.cpp 282

V528 It is odd that pointer to ‘char’ type is compared with the ‘\0’ value. Probably meant: *endstr != ‘\0’. clist_modern modern_skinbutton.cpp 283

Это весьма опасный код, так как он неправильно пытается определить конец строки. Такой код может привести к выходу за границы строк и, как следствие, к возникновению исключения Access Violation. Ошибка кроется здесь: «str!=’\0′» и здесь «endstr!=’\0′». Не хватает разыменования указателя. Корректный код:

Пример 7. Проект PNG library. Случайное обнуление указателя.

Ошибка найдена с помощью диагностики V527: It is odd that the ‘\0’ value is assigned to ‘char’ type pointer. Probably meant: *new_key [79] = ‘\0’. graphics3D pngwutil.c 1283

Этот пример демонстрирует ошибку, где случайно вместо обрезания длины строки, обнуляется указатель. Дело в том, что new_key это указатель на строку. А это значит, чтобы обрезать её до 79 символов, код должен выглядеть так:

Пример 8. Проект Intel AMT SDK. Непроверенное имя пользователя.

Ошибка найдена с помощью диагностики V501: There are identical sub-expressions ‘options->delivery_password’ to the left and to the right of the ‘||’ operator. OpenWsmanLib wsman-client.c 631

Из-за невнимательности два раза проверяется наличие пароля, но не проверяется наличие имени пользователя. Корректный код:

Пример 9. Проект Ultimate TCP/IP. Некорректная обработка пустых строк.

Ошибка найдена с помощью диагностики V547: Expression ‘(len — indx) >= 0’ is always true. Unsigned type value is always >= 0. UTDns utstrlst.cpp 58

Выражение «len — indx» имеет беззнаковый тип ‘size_t’ и всегда >= 0. Посмотрим, к чему это приведёт, если на вход будет подана пустая строка.

Если строка пустая, то: len = 0, indx = 1.

Выражение len — indx равно значению 0xFFFFFFFFu.

Так как 0xFFFFFFFFu > 0 и indx Пример 10. Проект Miranda IM. Неработающая защита от Underflow.

Ошибка найдена с помощью диагностики V547: Expression ‘nOldLength Пример 11. Проект Apache HTTP Server. Неправильная обработка отрицательных значений.

Ошибка найдена с помощью диагностики V547: Expression ‘len Пример 12. Проект Ultimate TCP/IP. Некорректное условие остановки цикла.

Ошибка найдена с помощью диагностики V547: Expression ‘loop >= 0’ is always true. Unsigned type value is always >= 0. UTDns utstrlst.cpp 430

Предположим, что вся строка состоит исключительно из пробелов. Перебирая символы, программа дойдёт до нулевого элемента строки и переменная ‘loop’ станет равна нулю. Затем выполнится очередное уменьшение переменной ‘loop’. Так как эта переменная имеет беззнаковый тип, то её значение станет равно 0xFFFFFFFFu или 0xFFFFFFFFFFFFFFFFu (в зависимости от разрядности). Естественно, что это значение >= 0 и начнется новая итерация цикла. Произойдет доступ к памяти по адресу szString[0xFFFFFFFFu], последствия чего хорошо знакомы каждому программисту на Си/Си++.

Copy-Paste

Программисты также зря недооценивают ошибки связанные с Copy-Paste, как и обыкновенные опечатки. Их очень и очень много. Программисты тратят на них много времени при отладке.

Опечатки и ошибки Copy-Paste, конечно, похожи, но есть отличие, из-за которого в этой статье они разделены. Опечатки часто приводят к тому, что вместо одной переменной используется другая. А при копировании, обычно просто забывают что-то поправить.

Пример 1. Проект Fennec Media Project. Промах при работе с элементами массива.

Ошибка найдена с помощью диагностики V525: The code containing the collection of similar blocks. Check items ’11’, ’12’, ’13’, ’13’ in lines 716, 717, 718, 719. id3 editor.c 716

Четыре схожие строчки, скорее всего, появились в коде программы с помощью копирования. Затем, при правке индексов допущена ошибка, из-за которой ноль записывается в ‘fhead[13] ‘ два раза и не записывается в ‘fhead[14] ‘.

Пример 2. Проект MySQL. Промах при работе с элементами массива.

Ошибка найдена с помощью диагностики V525: The code containing the collection of similar blocks. Check items ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘1’, ‘6’ in lines 680, 682, 684, 689, 691, 693, 695. sql records.cc 680

Ошибка сразу не видна, поэтому выделим её отдельно:

На самом деле, здесь должно быть написано:

Пример 3. Проект TortoiseSVN. Неисправленное имя файла.

Ошибка найдена с помощью диагностики V524: It is odd that the ‘GetDbgHelpVersion’ function is fully equivalent to the ‘GetImageHlpVersion’ function (SymbolEngine.h, line 98). symbolengine.h 105

Функция ‘GetImageHlpVersion’, скорее всего, получена копированием функции ‘GetInMemoryFileVersion’. Ошибка в том, что в скопированной функции забыли исправить имя файл. Корретный код:

Пример 4. Проект Clang. Одинаковые тела функций.

Ошибка найдена с помощью диагностики V524: It is odd that the body of ‘clearTopDownPointers’ function is fully equivalent to the body of ‘clearBottomUpPointers’ function (ObjCARC.cpp, line 1318). LLVMScalarOpts objcarc.cpp 1322

Видимо, тело функции clearBottomUpPointers некорректно, и эта функция должна была выглядеть так:

Пример 5. Проект QT. Неудачный swap.

Ошибка найдена с помощью диагностики V519: The ‘x1’ variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 2218, 2219. Qt3Support q3canvas.cpp 2219

Первая строчка совершенно корректна и обменивает значения в переменных x1 и x2. Во второй строке должны быть обменены переменные y1 и y2. Эта строка видима была копией предыдущей. В ней надо было заменить все буквы ‘x’ на буквы ‘y’. К сожалению, в одном месте это забыли сделать: «. x1=y2; . «.

Пример 6. Проект Crystal Space 3D SDK. Одинаковые подвыражения.

Ошибка найдена с помощью диагностики V501: There are identical sub-expressions to the left and to the right of the ‘&&’ operator. plgcsopcode icelss.h 69

Ошибка в том, что два раза используется переменная ‘lss.mP0.’ В правой части выражения следовало использовать ‘lss.mP1’.

Пример 7. Проект Notepad++. Установка некорректного стиля.

Ошибка найдена с помощью диагностики V525: The code containing the collection of similar blocks. Check items ‘7’, ‘7’, ‘6’, ‘7’ in lines 576, 580, 584, 588

Глазами найти здесь ошибку практически нереально, поэтому сократим текст, чтобы выделить интересное:

Случайно используется IDC_KEYWORD3_BOLD_CHECK вместо IDC_KEYWORD3_ITALIC_CHECK.

Пример 8. Проект ReactOS. Выбор не того объекта.

Ошибка найдена с помощью диагностики V523: The ‘then’ statement is equivalent to the ‘else’ statement. cardlib cardbutton.cpp 83

Объект ‘hsh’ не используется, зато ‘hhi’ используется два раза. Корректный код должен выглядеть так:

Пример 9. Проект IPP Samples. Неправильная проверка.

Ошибка найдена с помощью диагностики V501: There are identical sub-expressions ‘m_pContext->m_seqLayerHeader->heightMB’ to the left and to the right of the ‘&&’ operator. vc1_dec umc_vc1_video_decoder.cpp 1347

Пример 10. Проект ReactOS. Ошибка в имени переменной.

Ошибка найдена с помощью диагностики V537: Consider reviewing the correctness of ‘x’ item’s usage. win32k bitblt.c 670

Очень хороший пример, где видно, что строку скопировали. Потом одно имя ‘x’ поправили, а второе нет. Должно быть:

Разное

Пример 1. Проект Image Processing SDK. Восьмеричное число.

Ошибка найдена с помощью диагностики V536: Be advised that the utilized constant value is represented by an octal form. Oct: 0713, Dec: 459. IFF plugins pixelservices.inl 146

Если рассмотреть вторую функцию, то станет ясно, что хотелось использовать число 713, а вовсе на 0713. Число 0713 задано в восьмеричной системе счисления. Об этом легко забыть, если редко использовать восьмеричные константы.

Пример 2. Проект IPP Samples. Одна переменная для двух циклов.

Ошибка найдена с помощью диагностики V535: The variable ‘c’ is being used for this loop and for the outer loop. jpegcodec jpegdec.cpp 4652

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

Заключение и вывод

Вывод прост: купите PVS-Studio!

Различные ссылки

  • Главная страница статического анализатора PVS-Studio. http://www.viva64.com/ru/pvs-studio/
  • Скачать демонстрационную версию. http://www.viva64.com/ru/pvs-studio-download/
  • Купить PVS-Studio. http://www.viva64.com/ru/order/
  • Документация по PVS-Studio. http://www.viva64.com/ru/d/
  • Пообщаться с разработчиками PVS-Studio. http://www.viva64.com/ru/about-feedback/
  • Наш Twitter. http://twitter.com/Code_Analysis
Найдите ошибки в своем C, C++, C# и Java коде

Предлагаем попробовать проверить код вашего проекта с помощью анализатора кода PVS-Studio. Одна найденная в нём ошибка скажет вам о пользе методологии статического анализа кода больше, чем десяток статей.

Функция strspn

Функция strspn() возвращает длину начальной подстроки строки, адресуемой параметром str1 , которая состоит только из символов, содержащихся в строке, адресуемой параметром str2 . Другими словами, функция strspn() возвращает индекс первого символа в строке str1 , который не совпадает ни с одним из символов в строке str2 [1] .

Пример

Эта программа выводит число

Зависимые функции

[1] Или (что то же самое) функция strspn() возвращает индекс первого символа в строке str1 , который не входит в строку str2 .

Implementation of strspn( )

The definition of library function strspn is:

e.g. if str is «fecxdy» and chars is «abcdef» then the function would return 3 , since f , e and c all appear somewhere in chars , giving 3 leading characters of str , and x is the first character of str which is not a member of chars .

Could someone help me write an implementation of strspn in C. The only library function I am allowed to call from the implementation is strlen ?

7 Answers 7

The basic idea is to step through the string, one character at a time, and test if it’s in the character set. If it’s not, stop and return the answer. In pseudocode, that would look like:

The if c is not in chars test can be implemented by iterating through all of the characters of chars and testing if c matches any of the characters. Note that this is not the fastest implementation, since it involves stepping through the chars string for each character in str . A faster implementation would use a lookup table to test if c is not in chars .

I found this question while going over old exams. You weren’t allowed to use indexing or any standard functions. Here’s my attempt at a solution:

strspn

(PHP 4, PHP 5, PHP 7)

strspn — Возвращает длину участка в начале строки, полностью соответствующего маске

Описание

Возвращает длину участка от начала строки subject , содержащий только символы из mask .

Если параметры start и length не указаны, то будет исследована вся строка subject . Если они указаны, то эффект будет аналогичен вызову strspn(substr($subject, $start, $length), $mask) (подробнее см. функцию substr).

Список параметров

Список из разрешенных символов.

Позиция начала поиска в subject .

Если start указан и неотрицателен, то strspn() начнет искать в строке subject , начиная с позиции start . К примеру, в строке ‘abcdef‘, символом с позицией является ‘a‘, символом с позицией 2 является ‘c‘ и т.д.

Если start указан и отрицателен, то strspn() начнет поиск в строке subject с позиции, отстоящей на start символов с конца subject .

Длина исследуемого фрагмента subject .

Если length указан и неотрицателен, то строка subject будет исследована в течение length после стартовой позиции.

Если length указан и отрицателен, то строка subject будет исследована начиная со стартовой позиции до позиции, отстоящей на length символов с конца subject .

Возвращаемые значения

Возвращает длину первого участка subject , состоящего целиком из символов в mask .

Когда start указан, возращаемая длина строки определяется, начиная с этой позиции, а не с начала строки subject .

Примеры

Пример #1 Пример использования strspn()

// subject не начинается ни из какого символа из mask
var_dump ( strspn ( «foo» , «o» ));

// проверить два символа из subject, начиная с позиции 1
var_dump ( strspn ( «foo» , «o» , 1 , 2 ));

// проверить один символ из subject, начиная с позиции 1
var_dump ( strspn ( «foo» , «o» , 1 , 1 ));
?>

Результат выполнения данного примера:

Примечания

Замечание: Эта функция безопасна для обработки данных в двоичной форме.

Смотрите также

  • strcspn() — Возвращает длину участка в начале строки, не соответствующего маске

User Contributed Notes 7 notes

you can use this function with strlen to check illegal characters, string lenght must be the same than strspn (characters from my string contained in another)

if ( strlen ( $phone ) != strspn ( $phone , $digits ))
echo «illegal characters» ;

It took me some time to understand the way this function works…
I’ve compiled my own explanation with my own words that is more understandable for me personally than the official one or those that can be found in different tutorials on the web.
Perhaps, it will save someone several minutes…

( string $haystack , string $char_list [, int $start [, int $length ]])
?>

The way it works:
— searches for a segment of $haystack that consists entirely from supplied through the second argument chars
— $haystack must start from one of the chars supplied through $char_list, otherwise the function will find nothing
— as soon as the function encounters a char that was not mentioned in $chars it understands that the segment is over and stops (it doesn’t search for the second, third and so on segments)
— finally, it measures the segment’s length and return it (i.e. length)

In other words it finds a span (only the first one) in the string that consists entirely form chars supplied in $chars_list and returns its length

Quick way to check if a string consists entirely of characters within the mask is to compare strspn with strlen eg:

= $_SERVER [ ‘PATH_INFO’ ];
if ( strspn ( $path , ‘/’ ) == strlen ( $path )) <
//PATH_INFO is empty
>
?>

very dificult to get from the definition directly, while i search for that,i came to know that

strspn() will tell you the length of a string consisting entirely of the set of characters in accept set. That is, it starts walking down str until it finds a character that is not in the set (that is, a character that is not to be accepted), and returns the length of the string so far.

strcspn() works much the same way, except that it walks down str until it finds a character in the reject set (that is, a character that is to be rejected.) It then returns the length of the string so far.

= «aeiou» ;
$rejectSet = «y» ;

$str1 = «a banana» ;
$str2 = «the bolivian navy on manuvers in the south pacific» ;

echo $n = strspn ( $str1 , $acceptSet ); // $n == 1, just «a»

echo $n = strcspn ( $str2 , $rejectSet ); // n = 16, «the bolivian nav»
?>

hope this example will help in understanding the concept of strspn() and strcspn().

strspon and preg_match seem to be equally fast for validating numbers:

= ‘foobar123^^’ ;
$testValValid = ‘12346’ ;
$allowedChars = ‘1234567890’ ;

$t1 = microtime ( true );
for ( $i = 0 ; $i 1000000 ; $i ++) <
assert ( strspn ( $testValInvalid , $allowedChars ) != strlen ( $testValInvalid ));
assert ( strspn ( $testValValid , $allowedChars ) == strlen ( $testValValid ));
>
print ‘Time taken for strspon: ‘ . ( microtime ( true ) — $t1 );
print PHP_EOL ;

$t1 = microtime ( true );
for ( $i = 0 ; $i 1000000 ; $i ++) <
assert ( preg_match ( ‘/^[0-9]+$/’ , $testValInvalid ) === 0 );
assert ( preg_match ( ‘/^[0-9]+$/’ , $testValValid ));
>

print ‘Time taken for preg_match: ‘ . ( microtime ( true ) — $t1 );
print PHP_EOL ;

Забытые секреты кодинга №2: строковые операции в C

«…Я обнаружил, что понимание указателей в С — это не навык, а способность. При поступлении на факультет кибернетики набирается человек 200 вундеркиндов, писавших игрушки для Atari 800 на BASIC в возрасте 4 лет. Затем они весело проводят время, изучая Паскаль, но в один прекрасный день профессор заводит речь об указателях, и внезапно они не могут этого понять… 90% потока переходит на политехнический и становится отличниками, уверяя друзей, что на информатике мало девок. На самом же деле по неизвестной причине часть человечества просто рождается без той части мозга, которая понимает указатели».

Не знаю, есть ли в коре головного мозга зона, отвечающая за понимание указателей ;-). Но сегодня я расскажу тебе о том, как работают строки в Си, и о том, как ускорить их работу. Надеюсь, что будет понятно всем.

ВОПРОСЫ ПО СТАНДАРТНЫМ ФУНКЦИЯМ

Зачем нужны специальные функции для сравнения или копирования строк? Почему в Си не пользуются обычными операторами , +, =, как в других языках?
Да, Си работает со строками совсем по-другому. Строка в Си — это указатель на символ (char). В какой-то области памяти находятся символы строки, заканчивающиеся нулевым символом ‘\0’ — на рисунке это массив символов mom. Адрес этого массива можно передавать в строковые функции, можно извлекать из массива отдельные символы (например, mom[3] — четвертый символ) — словом, это обычная строка.

В другой части памяти может находиться указатель — переменная p, которая хранит адрес определенной части строки. Все операции со строками выполняются через указатели. Например, чтобы найти первую букву ‘А’, нужно вызвать функцию strchr(), которая вернет указатель на этот символ:

p = strchr(s, ‘А’); // p будет указывать на первый символ ‘А’ в строке s

Указатели можно увеличивать и сравнивать. Например, чтобы найти следующую за ‘А’ букву, прибавим к указателю (адресу!) единицу:

p = strchr(s, ‘А’) + 1;

Если нужно узнать, какая буква расположена ближе к началу строки, ‘А’ или ‘М’, сравним указатели:

if( strchr(s, ‘А’) char str[200] = «сто»;
strcat(str, «лица»);
if(str == «столица»)
printf(«Равны!»);

Ошибка заключается в том, что str и «столица» хотя и равны, но расположены в разных участках памяти. Поэтому указатели на них не равны. В этом примере строки будут расположены в памяти примерно так:

Чтобы сравнить «содержимое» строк, нужно вызвать функцию strcmp(). Обрати внимание, что эта функция возвращает 0, когда строки равны:

char str[200] = «сто»;
strcat(str, «лица»);
if(strcmp(str, «столица») == 0)
printf(«Равны!»);

Можно ли в C работать со строками как в Паскале или Бэйсике? То есть можно ли складывать строки плюсом, автоматически выделять под них память и все такое?
Да, можно. В MFC есть класс CString, кроме него можно назвать CStr из Snippets, и еще несколько похожих разработок. Но они всегда будут работать медленнее и занимать больше места в exe’шнике, чем обычные функции Си. Обобщенные функции выделения памяти страдают избыточностью, много времени уходит на создание промежуточных строк и бесполезное копирование между ними. Сишные функции strcmp, strchr и так далее не такие уж сложные, и если ты хорошо разберешься в них, твои программы станут быстрее и короче.

Как выделять память под строки и массивы? Прежде всего, память не выделяется автоматически, как в других языках программирования. Тебе придется подсчитать, сколько символов будет занимать строка, и создать массив нужного размера. Если строка выйдет за границы отведенного для нее массива, то возникнет баг, известный как «переполнение буфера». Взломщик может использовать эту ошибку, чтобы запустить свой вредительский exploit. Поэтому нужно проверять размер строк, полученных от юзера. Например, пользователь вводит текст в контрол, а нам нужно записать этот текст большими буквами. Это делается так:

char *s; int size;
size = SendMessage(hWndCtrl, WM_GETTEXTLENGTH, 0, 0) + 1; // Узнаем размер строки
s = (char*) malloc(size); // Выделяем size байт под строку
SendMessage(hWndCtrl, WM_GETTEXT, size, (LPARAM)s); // Копируем в этот буфер строку
CharUpper(s); // Меняем регистр
SendMessage(hWndCtrl, WM_SETTEXT, 0, (LPARAM)s); // Записываем в контрол
free(s); // Освобождаем память

Выделяя память, нужно помнить о последнем нулевом символе. Он также входит в массив, так что если длина строки — 10 символов, то нужно создавать массив длиной 11 байт. Вместо сишных функций malloc, free в C++ используют операторы new, delete. Разница только в синтаксисе, а по сути это одно и то же:

char *s; int size;
s = new char[size]; // Выделяем size байт под строку
. // Выполняем нужные операции
delete[] s; // Освобождаем память

Часто максимальный размер строки известен заранее. Например, путь к файлу в Windows не может быть длиннее MAX_PATH = 260 символов. Тогда проще ограничить длину вводимой строки (послать сообщение EM_SETLIMITTEXT), а затем использовать обычный статический массив:

Функции malloc/realloc/free и операторы new/delete обращаются к Windows, запрашивая у нее память. Если вместо них ты будешь пользоваться Windows’овскими функциями HeapAlloc, HeapRealloc, HeapFree, то сможешь выбросить из exe’шника стандартную библиотеку Си, и он станет короче примерно на 20 Кб. А код для выделения памяти будет очень похожим на
malloc/free:

HANDLE heap;
heap = GetProcessHeap(); // в начале программы
s = (char*) HeapAlloc(heap, 0, size); // Выделяем size байт под строку
HeapFree(heap, 0, s); // Освобождаем память

ПРОСТЫЕ ОПЕРАЦИИ СО СТРОКАМИ

Как выделить подстроку справа, например, имя файла? Просто найди символ, с которого начинается подстрока, и пользуйся указателем на него. На рисунке указатель filename показывает на строку «C:\WORK\FV.C», а указатель p — на часть этой строки «FV.C».

p = strrchr(filename, ‘\\’) + 1; // Символ, следующий за последней обратной косой чертой

Как выделить подстроку слева, например, путь к файлу? Подставь нулевой байт в конец подстроки:

p = strrchr(filename, ‘\\’);
*p = ‘\0’; // Теперь в filename записан путь к файлу

Чтобы вернуть имя файла, верни косую черту на прежнее место. Это гораздо быстрее, чем копировать путь в отдельную строку.

Как подсчитать, сколько раз символ встречается в строке? Можно каждый раз искать этот символ в оставшейся части строки и, если он был найден, увеличивать счетчик и сдвигаться к концу строки:

char *p = str; int n = 0;
while(p = strchr(p, ‘А’))
p++, n++; // По окончании цикла n == число вхождений символа ‘А’ в строку str

В более удобочитаемом виде та же программа записывается следующим образом:

Почему указатели быстрее, чем индексы массивов? Программеров, которые пришли из Паскаля или Бэйсика, может немного смутить код, приведенный выше. Для них привычнее обращаться к символу строки по индексу (его номеру в квадратных скобках):

Чем лучше указатели? Да тем, что процессору не нужно при каждом проходе цикла складывать адрес начала строки s и переменную i, чтобы вычислить адрес
s[i]:

xor ecx, ecx ; ecx — это i
xor edx, edx ; edx — это n
LOOP:
cmp DWORD PTR s[ecx], ‘A’
jne SHORT BYPASS
inc edx
BYPASS:
inc ecx
cmp ecx, eax
jb SHORT LOOP

Вместо этого хорошая программа просто увеличивает адрес, то есть указатель p, а не индекс i. Код тела цикла становится немного проще и быстрее:

mov ecx, offset s ; ecx — это p
xor edx, edx ; edx — это n
LOOP:
cmp BYTE PTR [ecx], ‘A’
jne SHORT BYPASS
inc edx
BYPASS:
inc ecx
cmp ecx, eax
jb SHORT LOOP

Здесь нужно сказать, что интеллектуальный компилятор может «додуматься» преобразовать код с индексом в код с указателем, но слишком надеяться на это не стоит. Компилятор все же не так умен, как использующий его программист :).

Как найти первую гласную или согласную букву в строке? Для этого случая лучше всего подходят функции strpbrk() и
strspn().

p = strpbrk(«стройка», «аеёиоуыэюя»); //
p – указатель на первую гласную
p = s + strspn(s, «аеёиоуыэюя»); // p – указатель на первую согласную

ПРОБЛЕМЫ С РУССКИМ ЯЗЫКОМ

Как преобразовать русские буквы в верхний или нижний регистр? Лучше всего использовать функции CharUpper() и CharLower() из библиотеки Windows (заголовочный файл windows.h). Во-первых, эти функции учитывают язык, установленный в Панели управления Windows, поэтому твоя программа будет работать правильно во всех странах и со всеми языками. Во-вторых, проще пользоваться этими функциями, чем пытаться настроить стандартные функции Си для работы с русским или изобретать что-то свое. Пример:

char *m=»министр», *p=»Президент»;
CharUpper(m);
CharLower(p);
printf(«%s %s», m, p); // Выведет «МИНИСТР президент»

Вместо указателя на строку можно передать отдельный символ или воспользоваться функциями с суффиксом -Buff, которые преобразуют указанное число символов строки:

char m[]=»министр»;
m[0] = (char)CharUpper((char*)(unsigned)(unsigned char)m[0]); // Министр
CharUpperBuff(m+2, 3); // МиНИСтр

Глядя на этот кусок кода, знатоки Си могут употребить немало интересных слов великого и могучего русского языка :). Но я пока не нашел другого работающего способа преобразовать тип (char) в (char*). Если кто-то найдет — пишите. Кстати, во многом это проблема программистов Microsoft. Им нужно было написать отдельное определение (макрос или inline-функцию) для CharUpper с аргументом типа
char.

В сравнении строк есть и еще одна тонкость, на которую обычно не обращают внимания. Дело в том, что коды символов не всегда соответствуют их порядку в алфавите. Например, русская буква «Ё» и специфические белорусские буквы «Ў» («у» краткое), «ї» («и» десятеричное) расположены в кодовой таблице Windows отдельно от всех остальных букв. И если ты сравниваешь слова «дерево» и «ёлка» с помощью strcmp (сравнение по кодам), то получишь, что «ёлка» меньше (ближе к началу алфавита), чем «дерево». Чтобы избежать этой ошибки, пользуйся функцией lstrcmp() из библиотеки Windows. Для примера приведу простейшую программу пузырьковой сортировки:

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
< char *a[]=<"кедр","дуб","желудь","дерево","ёлка","сосна">;
char *p, *x; unsigned i, j; char s[200];
#define N sizeof(a)/sizeof(a[0])
for(i=0; i Пузырьковая сортировка, strcmp
for(j=i+1; j 0 )
< x = a[i];
a[i] = a[j];
a[j] = x;
>
p = s;
p += sprintf(p, » strcmp:\n»);
for(i=0; i Пузырьковая сортировка, lstrcmp
for(j=i+1; j 0 )
< x = a[i];
a[i] = a[j];
a[j] = x;
>
p += sprintf(p, «\n lstrcmp:\n»);
for(i=0; i

Эта программа выведет (видно, что сортировка с использованием strcmp неверно обрабатывает слово «ёлка»):

strcmp:
ёлка
дерево
дуб
желудь
кедр
сосна

lstrcmp:
дерево
дуб
ёлка
желудь
кедр
сосна

УБРАТЬ И УДВОИТЬ

Как убрать из строки определенные символы, например, все пробелы и знаки препинания? Есть очень простой способ. Заведем два указателя: один (p) на ту часть строки, которую мы просматриваем, другой (p2) — на ту часть, в которую мы будем копировать символы, не являющиеся пробелами или знаками препинания. Если нам встретился знак препинания, пропускаем его, увеличивая только первый указатель. Таким образом, мы «собираем» все символы к началу строки (см. рисунок ниже).

char s[256], *p = s, *p2 = s;
gets(s);
while(*p) // Пока в строке есть символы
< if( !ispunct(*p) && !isspace(*p) )
*(p2++) = *p; // Если не знак препинания, копируем
p++; // Переходим к следующему символу
>
*p2 = ‘\0’;
puts(s);

Кстати, в Visual C++ функции ispunct, isspace довольно медленные, поэтому вместо них лучше написать условие типа

‘ ) //
примерно на 10% быстрее, чем !ispunct(p) &&
!isspace(*p)

Как убрать из строки все вхождения подстроки, например, убрать все переносы строк? Точно также, как и в случае с удалением символа, будем копировать нужные нам части строки в её начало. Можно искать подстроку с помощью strstr() или применить описанный в первой части статьи «Забытые секреты кодинга» макрос
toShort:

Илон Маск рекомендует:  Можно ли использовать иконку как картинку на кнопке tspeedbutton
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL