КаталогИндекс раздела
НазадОглавлениеВперед


LINT

СОДЕРЖАНИЕ

1. Введение

2. Запуск утилиты lint

3. Типы сообщений lint'а
    3.1. Неиспользуемые переменные и функции
    3.2. Информация об определениях/использованиях значений
    3.3. Поток управления
    3.4. Значения функций
    3.5. Контроль типов
    3.6. Явные преобразования типов
    3.7. Машинно-зависимое использование символов
    3.8. Присваивание целым переменным long-значений
    3.9. Странные конструкции
    3.10. Устаревший синтаксис
    3.11. Выравнивание указателей
    3.12. Многократные использования и побочные эффекты


 

1. ВВЕДЕНИЕ

Утилита lint исследует тексты на языке C, выявляя некоторые ошибки и сомнительные конструкции. lint более строго, чем C-компилятор, контролирует правила типизации. lint можно применять и для того, чтобы проверить выполнение ряда ограничений, повышающих мобильность программ, если имеется в виду перенос на другой компьютер или в другую операционную среду. lint также позволяет выявить использование формально корректных, но расточительных или рискованных конструкций. Наконец, совокупность файлов с исходными текстами и спецификаций библиотек проверяется lint'ом на совместимость.

Утилита lint является мощным средством отладки, поскольку зачастую нарушенные правила типизации или употребленные рискованные конструкции являются источником коварных ошибок. Следует настоятельно рекомендовать верификацию комплексов C-программ до их первого запуска, поскольку это существенно сократит время отладки, да и приучит к аккуратному стилю программирования.

 

2. ЗАПУСК УТИЛИТЫ LINT

Вызов утилиты lint имеет вид

       lint  [опции] файлы описатели_библиотек

Опции определяют набор проверок, выполняемых lint'ом, и степень подробности выдаваемых сообщений. Имена верифицируемых файлов должны оканчиваться на .c или .ln. Описатели-библиотек - это список имен библиотек, которые должны использоваться при анализе программы.

Опциям утилиты lint приписан следующий смысл:

 -a  Не выдавать сообщений о присваиваниях long-значений переменным, не описанным как long.
 -b  Не выдавать сообщений о недостижимых операторах break.
 -c  Ограничиться проверкой ошибок в пределах каждого из .c-файлов; поместить информацию о внешних свойствах в файлы, оканчивающиеся на .ln.
 -h  Не применять эвристики (предназначенные для того, чтобы попытаться обнаружить ошибки, улучшить стиль и уменьшить сложность программы).
 -n  Не проверять совместимость со стандартной или мобильной lint-библиотеками.
 -o библ  Создать из исходных файлов lint-библиотеку с именем llib-lбибл.ln.
 -p  Попытаться проверить мобильность программы.
 -u  Не выдавать сообщения о функциях и внешних переменных, используемых, но не определенных, или определенных, но не используемых.
 -v  Не выдавать сообщения о неиспользуемых аргументах функций.
 -x  Не сообщать о внешних переменных, которые нигде не используются.

Если требуется указать более одной опции, их следует объединять в единый аргумент, такой как -ab или -xha.

Имена файлов, содержащих исходные C-тексты, должны оканчиваться на .c, что обязательно не только для lint'а, но и для C-компилятора.

Помимо перечисленных, утилита lint поддерживает опции вида

       -lбибл

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

       /* LINTLIBRARY */

и содержат серию фиктивных определений функций. Наиболее существенными частями в этих определениях являются указания, возвращает ли функция значение и если да, то какого типа, каковы число и тип аргументов. Чтобы специфицировать особые свойства некоторых библиотечных функций, можно воспользоваться комментариями VARARGS и ARGSUSED. Как это сделать, написано в сле- дующем разделе, ТИПЫ СООБЩЕНИЙ LINT'А.

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

По умолчанию lint проверяет программы совместно со стандартной lint-библиотекой, содержащей описания функций, обычно загружаемых при выполнении C-программ. Если указана опция -p, проверяется еще один файл, содержащий описания функций из стандартной библиотеки, которая считается мобильной. Чтобы подавить всякую проверку библиотек, можно воспользоваться опцией -n.

Дополнительную информацию об опциях команды lint можно найти в статье lint(1) Справочника пользователя.

 

3. ТИПЫ СООБЩЕНИЙ LINT'А

В последующих пунктах описываются основные группы сообщений, выдаваемых lint'ом.

3.1. Неиспользуемые переменные и функции

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

lint печатает сообщения о переменных и функциях, определенных, но нигде не используемых, в случае, если не указаны опции -u или -x, подавляющие подобные сообщения.

Программисты иногда прибегают к приему, заключающемуся в определении функций с интерфейсом, в котором часть аргументов функции является необязательной. Обычно lint печатает сообщение о неиспользуемых аргументах, однако предусмотрена опция -v, подавляющая вывод таких сообщений. Если опция -v указана, сообщения о неиспользуемых аргументах не выдаются, если только такой аргумент не является регистровым. Считается, что в последнем случае имеет место напрасная (хотя и не фатальная) трата машинных ресурсов.

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

       /* ARGSUSED */

Комментарий действует так же, как и опция -v, но только в пределах одной функции. Другой комментарий

       /* VARARGS */

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

       /* VARARGS2 */

вызывает проверку двух первых аргументов.

Если lint применить к подмножеству полной сосвокупности файлов, составляющих единую программу, в его сообщениях может быть выражено неудовольствие по поводу якобы неиспользуемых или неопределенных переменных. Такая информация, конечно, скорее сбивает с толку, чем помогает. Функции и переменные, определенные в этих файлах, вполне могут в них не использоваться; и наоборот, могут использоваться функции и переменные, определенные где-то в другом месте. Ложные сообщения подавляет опция -u.

3.2. Информация об определениях/использованиях значений

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

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

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

3.3. Поток управления

lint стремится выявлять недостижимые фрагменты программы. Он выдает сообщения об операторах без меток, следующих непосредственно за операторами goto, break, continue и return. lint пытается выявлять бесконечные циклы (частные случаи - циклы с заголовками while(1) и for(;;)) и трактует следующие за ними операторы как недостижимые. lint также выдает сообщения о циклах, в которые нельзя попасть через заголовок. В корректных программах могут быть такие циклы, однако подобный стиль считается плохим. Для подавления сообщений о недостижимых фрагментах программы служит опция -b.

lint не обладает информацей, достаточной для того, чтобы определить, какие из вызываемых функций никогда не возвращают управление. Так, вызов exit() может стать причиной недостижимости некоторого фрагмента, чего lint, однако, не обнаружит. Наиболее серьезные последствия этого связаны с опредлением возвращаемых функцией значений (см. пункт Значения функций). Если некоторый фрагмент в программе мыслится как недостижимый, причем lint'у это непонятно, в соответствующее место программы можно вставить комментарий

       /* NOTREACHED */

который будет информировать lint о том, что данный фрагмент недостижим (без выдачи диагностического сообщения).

Программы, сгенерированные при помощи yacc'а и, в особенности, lex'а, могут содержать сотни недостижимых операторов break, но пользы от подобных сообщений немного. Обычно с такими операторами ничего поделать нельзя, и сообщения загромождали бы выдачу lint'а. Рекомендуется при работе с подобными исходными текстами вызывать lint с опцией -b.

3.4. Значения функций

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

Если в теле функции встречается как оператор

       return (выражение);

так и оператор

       return ;

это настораживает; lint выдаст сообщение

       function name has return(e) and return

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

       f (a)
       {
         if (a)
           return (3);
         g ();
       }

Если условие ложно, f вызовет g, а затем завершится, не возвра- тив никакого определенного результата; такая конструкция будет причиной сообщения lint'а. Если g, подобно exit, никогда управ- ления не возвращает, сообщение тем не менее будет выдаваться, хотя на самом деле никакой ошибки нет. Комментарий

       /* NOTREACHED */

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

При анализе программы в целом lint выявляет ситуации, когда функция возвращает значения, которые иногда (или никогда) не используются. Если возвращаемые значения никогда не используются, в определении функции имеется некоторая неэффективность. Можно "легально" проигнорировать результат функции при помощи явного преобразования к типу void, например

    (void) fprintf (stderr, "Файл занят. Попробуйте позже!\n");

Игнорирование результата может быть просто проявлением дурного стиля (например, частичное отсутствие проверок неудачного завершения функции).

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

3.5. Контроль типов

lint, по сравнению с C-компилятором, выполняет более строгий контроль типов. Дополнительный контроль затрагивает четыре основные области:

Некоторые операции выполняют определенные преобразования, приводящие в соответствие типы операндов. Этим свойством обладают присваивания, условная операция (?:) и операции отношения. Аргумент оператора return и выражения, используемые для инициализации, допускают аналогичные преобразования. В этих конструкциях типы char, short, int, long, unsigned, float и double можно без ограничений смешивать. Указательные типы должны в точности согласовываться, конечно же, за исключением того, что массивы x можно смешивать с указателями на x.

Правила контроля типов требуют, чтобы при работе со структурами левый операнд операции -> являлся указателем на структуру, ле- вый операнд операции . - структурой, а правый операнд этих операций - элементом, предусмотренным для структурного типа, соответствующего левому операнду. Аналогичные проверки выполняются при работе с объединениями.

Контролируется соответствие типов аргументов функций и возвращаемых значений. Без ограничений сопоставляются типы float и double, так же, как и типы char, short, int и unsigned. Аналогично, указатели могут сопоставляться с соответствующими массивами. Во всех остальных случаях типы фактических аргументов должны согласовываться с описаниями соответствующих им формальных.

Для перечислимых типов контролируется, что переменные и значения таких типов не употребляются вместе с переменными других типов, в том числе и перечислимых, и что единственные операции, к ним применяемые, - это =, инициализация, ==, !=, передача в качестве фактических аргументов и возврат в качестве результата функции.

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

       /* NOSTRICT */

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

3.6. Явные преобразования типов

Возможность явного преобразования типов в языке C предназначена в значительной степени для того, чтобы повысить мобильность программ. Рассмотрим присваивание

       p = 1;

в котором p - это указатель на char. lint, обнаружив такую конструкцию, будет выдавать диагностическое сообщение. В присваивании

       p = (char *) 1;

использована операция явного преобразования целого значения в указатель на символ. У программиста наверняка были веские причины написать именно такую конструкцию и явно об этом сигнализировать. Тем не менее, если указана опция -p, lint, как и прежде, будет выдавать сообщение о несоответствии типов.

3.7. Машинно-зависимое использование символов

В ряде систем символы представляются величинами со знаком в диапазоне от -128 до 127. В других реализациях языка C символы принимают только положительные значения. Поэтому lint выдает сообщения о некоторых ситуациях сравнения и присваивания как о некорректных или машинно-зависимых. Например, фрагмент

       char c;
       
       if ((c = getchar ()) < 0)
         . . .

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

       nonportable character comparison

Лучший выход - описать c как целую переменную, поскольку getchar() в действительности возвращает целочисленные значения.

Аналогичные трудности возникают в связи с битными полями. При присваивании константного значения битному полю последнее может оказаться слишком маленьким, чтобы вместить значение. Чаще всего это происходит из-за того, что некоторые машины рассматривают битные поля как знаковые величины. Поскольку естественно считать, что поле из двух бит, описанное как int, не может вместить значение 3, проблема разрешается, если описать битное поле как unsigned.

3.8. Присваивание целым переменным long-значений

Присваивание переменным, описанным как int, значений типа long, может вызвать ошибки, поскольку оно сопровождается усечением значения. Это может произойти в программах, которые не полностью переработаны применительно к использованию конструкций typedef. Когда тип переменной, определяемый посредством typedef, изменяется с int на long, программа может перестать работать, если часть промежуточных результатов заносится в int-переменные и при этом усекается. Чтобы подавить сообщения о присваивании long-значений int-переменным, можно использовать опцию -a.

3.9. Странные конструкции

lint выявляет ряд конструкций, являющихся вполне корректными, но несколько странными. Предполагается, что сообщения, выдаваемые при этом, должны поощрять лучшее качество программ, более ясный стиль, а иногда и способствовать обнаружению ошибок. (Чтобы отменить эти проверки, используется опция -h). Например, в операторе

       *p++;

* ничего не дает. Такая конструкция вызывает сообщение lint'а:

       null effect

В следующем фрагменте программы

       unsigned x;
         . . .
       if (x < 0)
         . . .

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

       degenerate unsigned comparison

Аналогично, для того же x, проверка

       if (x > 0)
         . . .

эквивалентна

       if (x != 0)
         . . .

Поэтому lint спрашивает:

       unsigned comparison with 0?

Если программа содержит нечто, подобное фрагменту

       if (1 != 0)
         . . .
lint выдает сообщение
       constant in conditional context

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

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

       if (x&077 == 0)
         . . .

и

       x<<2 + 40

имеют, скорее всего, совсем не тот смысл, который имелся в виду. Лучшее решение - использовать в таких выражениях скобки; к этому и призывает соответствующее сообщение lint'а.

3.10. Устаревший синтаксис

Ряд конструкций более ранней версии языка C теперь недопустимы. Они распадаются на два класса: операции присваивания и конструкции инициализации.

Устаревшие формы операций присваивания (=+, =- и т.д.) могли привести к неоднозначным выражениям, например, таким:

       a =-1;

Это выражение можно трактовать как

       a =- 1;

или как

       a = -1 ;

Ситуация становится в особенности запутанной, если неоднозначные конструкции данного сорта появляются в результате макроподстановки. Более новые и предпочтельные операции (например, +=, -=, ...) не приводят к таким неоднозначностям. lint выдает сообщения, призывающие отказаться от устаревших конструкций.

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

       int x 1;

инициализировавшее x единицей. Это приводило к трудностям в интерпретации других конструкций. Например, инициализация

       int x (-1);

до некоторой степени походит на начало определения функции:

       int x (y)
         . . .

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

       int x = -1;

В этом уже нет никакой синтаксической неоднозначности.

3.11. Выравнивание указателей

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

       possible pointer alignment problem

3.12. Многократные использования и побочные эффекты

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

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

lint проверяет важный частный случай - манипуляции со скалярными переменными. Например, оператор

       a [i] = b [i++];

вызовет следующее сообщение lint'а:

       warning: i evaluation order undefined

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


НазадОглавлениеВперед
КаталогИндекс раздела