Ошибка: «***» не было объявлено в этой области
Эта ошибка возникает, если используется неизвестный объект.
переменные
Не компиляция:
#include <iostream>
int main(int argc, char *argv[])
{
{
int i = 2;
}
std::cout << i << std::endl; // i is not in the scope of the main function
return 0;
}
Fix:
#include <iostream>
int main(int argc, char *argv[])
{
{
int i = 2;
std::cout << i << std::endl;
}
return 0;
}
функции
В большинстве случаев эта ошибка возникает, если нужный заголовок не включен (например, с помощью
std::cout
без#include <iostream>
)
Не компиляция:
#include <iostream>
int main(int argc, char *argv[])
{
doCompile();
return 0;
}
void doCompile()
{
std::cout << "No!" << std::endl;
}
Fix:
#include <iostream>
void doCompile(); // forward declare the function
int main(int argc, char *argv[])
{
doCompile();
return 0;
}
void doCompile()
{
std::cout << "No!" << std::endl;
}
Или же:
#include <iostream>
void doCompile() // define the function before using it
{
std::cout << "No!" << std::endl;
}
int main(int argc, char *argv[])
{
doCompile();
return 0;
}
Примечание . Компилятор интерпретирует код сверху вниз (упрощение). Перед использованием все должно быть объявлено (или определено) .
неопределенная ссылка на `*** ‘
Эта ошибка компоновщика возникает, если компоновщик не может найти используемый символ. В большинстве случаев это происходит, если используемая библиотека не связана.
QMAKE:
LIBS += nameOfLib
CMake:
TARGET_LINK_LIBRARIES(target nameOfLib)
g ++ call:
g++ -o main main.cpp -Llibrary/dir -lnameOfLib
Также можно забыть компилировать и связывать все используемые .cpp
файлы ( .cpp
определяет нужную функцию):
g++ -o binName main.o functionsModule.o
Неустранимая ошибка: ***: Нет такого файла или каталога
Компилятор не может найти файл (исходный файл использует #include "someFile.hpp"
).
QMAKE:
INCLUDEPATH += dir/Of/File
CMake:
include_directories(dir/Of/File)
g ++ call:
g++ -o main main.cpp -Idir/Of/File
Компилятор GNU может выдавать два вида диагностики:ошибки и предупреждения.Каждый вид имеет свое назначение:
- Ошибки сообщают о проблемах, которые делают невозможным компиляцию вашей программы. GCC сообщает об ошибках с указанием имени исходного файла и номера строки, в которой проблема очевидна.
- Предупреждения сообщают о других необычных условиях в вашем коде, которые могут указывать на проблему, хотя компиляция может (и продолжается) продолжаться. Предупреждающие сообщения также сообщают имя исходного файла и номер строки, но содержат текст ‘warning:‘, чтобы отличить их от сообщений об ошибках.
Предупреждения могут указывать на опасные моменты,в которых вам следует проверить,действительно ли ваша программа делает то,что вы задумали;или на применение устаревших возможностей;или на применение нестандартных возможностей GNU C или C++.Многие предупреждения выдаются только в том случае,если вы их запрашиваете,с одним из следующих пунктов-Wопции (например,-Wallзапрашивает различные полезные предупреждения).
GCC всегда пытается скомпилировать вашу программу, если это возможно; он никогда не отвергает необоснованно программу, смысл которой ясен, просто потому, что (например) она не соответствует стандарту. Однако в некоторых случаях стандарты C и C ++ указывают, что определенные расширения запрещены, и соответствующий компилятор должен выдать диагностику .-pedanticопция подсказывает GCC выдавать предупреждения в таких случаях;-pedantic-errorsвместо этого говорит делать им ошибки. Это не означает, что все конструкции, не относящиеся к ISO, получают предупреждения или ошибки.
Дополнительные сведения об этих и связанных параметрах командной строки см. В разделе « Параметры запроса или подавления предупреждений» .
Предыдущая: Без ошибок , Вверх: Проблемы [ Содержание ][ Индекс ]
GCC
12.2
-
3.19.59 Опции VxWorks
Опции в этом разделе определены для всех целей VxWorks.
-
3.8 Варианты запроса или подавления предупреждений
Предупреждения-это диагностические сообщения,которые сообщают о конструкциях,не являющихся по своей сути ошибочными,но указывающих на возможную ошибку.
-
6.62.12 Слабые прагмы
Для совместимости с SVR4 GCC поддерживает набор директив #pragma,объявляющих символы слабыми и определяющих псевдонимы.
-
8.2.1 Что ты можешь и не можешь сделать в +загрузке.
+нагрузка используется только в крайнем случае.
Я регулярно проверяю различные открытые проекты, чтобы продемонстрировать возможности статического анализатора кода PVS-Studio (C, C++, C#). Настало время компилятора GCC. Бесспорно, GCC — это очень качественный и оттестированный проект, поэтому найти в нём хотя бы несколько ошибок уже большое достижение для любого инструмента. К моей радости, PVS-Studio справился с этой задачей. Никто не застрахован от опечаток и невнимательности. Именно поэтому PVS-Studio может стать вашей дополнительной линией обороны на фронте бесконечной войны с багами.
GCC
GNU Compiler Collection (обычно используется сокращение GCC) — набор компиляторов для различных языков программирования, разработанный в рамках проекта GNU. GCC является свободным программным обеспечением, распространяется фондом свободного программного обеспечения на условиях GNU GPL и GNU LGPL и является ключевым компонентом GNU toolchain. Проект написан на языке C и C++.
Компилятор GCC имеет хорошие встроенные диагностики, помогающие выявлять многие ошибки на этапе компиляции. Естественно, GCC собирается с помощью GCC и, соответственно, может выявлять ошибки в собственном коде. Дополнительно исходный код GCC проверяется с помощью анализатора Coverity. Да и вообще, думаю GCC проверялся энтузиастами с помощью многих анализаторов и других инструментов. Это делает поиск ошибок в GCC большим испытанием для анализатора кода PVS-Studio.
Для анализа была взята trunk версия из git-репозитория: (git) commit 00a7fcca6a4657b6cf203824beda1e89f751354b svn+ssh://gcc.gnu.org/svn/gcc/trunk@238976
Примечание. Статья задержалась с выходом, и возможно какие-то ошибки уже исправлены. Но это не имеет значения: постоянно появляются новые ошибки, старые исчезают. Главное — статья показывает, что статический анализ может помогать программистам выявлять ошибки после их появления.
Предвидя дискуссию
Как я сказал во введении, я считаю GCC проектом с высоким качеством кода. Уверен, многие захотят поспорить. В качестве примера приведу цитату из Wikipedia на русском языке:
Некоторые разработчики OpenBSD, например Тео де Раадт и Отто Мурбек (Otto Moerbeek), критикуют GCC, называя его «громоздким, глючным, медленным и генерирующим плохой код».
Я считаю такие заявления необоснованными. Да, возможно, код GCC содержит много макросов, которые затрудняют его чтение. Но я никак не могу согласиться с заявлением о его глючности. Если бы GCC глючил, вообще бы нигде ничего не работало. Вы только вспомните, как много программ им компилируется и успешно работает. Создатели GCC делают огромную, сложную работу с большим профессионализмом. Спасибо им. Я рад, что могу протестировать работу PVS-Studio на таком высококачественном проекте.
Для тех, кто скажет, что код компилятора Clang всё равно круче, напомню: в нём PVS-Studio также находил ошибки: 1, 2.
PVS-Studio
Я проверил код GCC с помощью Alpha-версии анализатора PVS-Studio for Linux. Мы планируем начать выдавать заинтересовавшимся программистам Beta-версию анализатора в середине сентября 2016 года. Инструкцию о том, как стать одним из первых, кто сможет попробовать Beta-версию PVS-Studio for Linux на своём проекте, вы найдете в статье «PVS-Studio признаётся в любви к Linux».
Если вы читаете эту статью гораздо позже, чем сентябрь 2016, и хотите попробовать PVS-Studio for Linux, то приглашаю вас на страницу продукта: http://www.viva64.com/ru/pvs-studio/
Результаты проверки
Мы добрались до самого интересного раздела, который, я думаю, с нетерпением ждут наши постоянные читатели. Рассмотрим участки кода, где анализатор нашел ошибки или крайне подозрительные моменты.
К сожалению, я не могу выдать разработчикам компилятора полный отчёт. В нем пока слишком много мусора (ложных срабатываний), связанных с тем, что анализатор не полностью готов к встрече с миром Linux. Нужно проделать работу по уменьшению количества ложных предупреждений на типовые используемые конструкции. Попробую пояснить на одном простом примере. Многие диагностики не должны ругаться на выражения, относящиеся к макросам assert. Эти макросы бывают устроены весьма творчески и надо научить анализатор не обращать на них внимание. Но дело в том, что определяется макрос assert очень по-разному, и надо обучить анализатор всем типовым вариантам.
Поэтому разработчиков GCC прошу подождать выхода по крайней мере Beta-версии анализатора. Я не хочу испортить впечатление отчетом, сгенерированным недоделанной версией.
Классика (Copy-Paste)
Начнем мы с самой классической и распространённой ошибки, которая выявляется с помощью диагностики V501. Как правило, такие ошибки появляются из-за невнимательности при Copy-Paste или просто являются опечатками, допускаемыми при наборе нового кода.
static bool
dw_val_equal_p (dw_val_node *a, dw_val_node *b)
{
....
case dw_val_class_vms_delta:
return (!strcmp (a->v.val_vms_delta.lbl1,
b->v.val_vms_delta.lbl1)
&& !strcmp (a->v.val_vms_delta.lbl1,
b->v.val_vms_delta.lbl1));
....
}
Предупреждение анализатора PVS-Studio: V501 There are identical sub-expressions ‘!strcmp(a->v.val_vms_delta.lbl1, b->v.val_vms_delta.lbl1)’ to the left and to the right of the ‘&&’ operator. dwarf2out.c 1428
Быстро увидеть ошибки проблематично и следует внимательно присмотреться. Именно поэтому ошибка и не была выявлена при обзорах кода и рефакторинге.
Функция strcmp дважды сравнивает одни и те же строки. Мне кажется, второй раз следовало сравнивать не члены класса lbl1, а lbl2. Тогда корректный код должен выглядеть так:
return (!strcmp (a->v.val_vms_delta.lbl1,
b->v.val_vms_delta.lbl1)
&& !strcmp (a->v.val_vms_delta.lbl2,
b->v.val_vms_delta.lbl2));
Хочу отметить, что код, приведённый в статье, немного отформатирован, чтобы он занимал мало места по оси X. На самом деле, код выглядит так:
Ошибки, возможно, удалось бы избежать, если использовать «табличное» выравнивание кода. Например, ошибку было бы легче заметить, если отформатировать код так:
Подробнее я рассматривал такой подход в электронной книге «Главный вопрос программирования, рефакторинга и всего такого» (см. главу N13: Выравнивайте однотипный код «таблицей»). Рекомендую всем, кто заботится о качестве своего кода, познакомиться с приведённой здесь ссылкой.
Давайте рассмотрим ещё одну ошибку, которая, я уверен, появилась из-за Copy-Paste:
const char *host_detect_local_cpu (int argc, const char **argv)
{
unsigned int has_avx512vl = 0;
unsigned int has_avx512ifma = 0;
....
has_avx512dq = ebx & bit_AVX512DQ;
has_avx512bw = ebx & bit_AVX512BW;
has_avx512vl = ebx & bit_AVX512VL; // <=
has_avx512vl = ebx & bit_AVX512IFMA; // <=
....
}
Предупреждение анализатора PVS-Studio: V519 The ‘has_avx512vl’ variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 500, 501. driver-i386.c 501
В переменную has_avx512vl дважды подряд записываются различные значения. Это не имеет смысла. Я изучил код и обнаружил переменную has_avx512ifma. Скорее всего, именно она и должна инициализироваться выражением ebx & bit_AVX512IFMA. Тогда корректный код должен быть таким:
has_avx512vl = ebx & bit_AVX512VL;
has_avx512ifma = ebx & bit_AVX512IFMA;
Опечатка
Продолжу испытание вашей внимательности. Посмотрите на код и, не подсматривая ниже, попробуйте найти ошибку.
static bool
ubsan_use_new_style_p (location_t loc)
{
if (loc == UNKNOWN_LOCATION)
return false;
expanded_location xloc = expand_location (loc);
if (xloc.file == NULL || strncmp (xloc.file, "1", 2) == 0
|| xloc.file == '' || xloc.file[0] == 'xff'
|| xloc.file[1] == 'xff')
return false;
return true;
}
Предупреждение анализатора PVS-Studio: V528 It is odd that pointer to ‘char’ type is compared with the » value. Probably meant: *xloc.file == ». ubsan.c 1472
Здесь программист случайно забыл разыменовать указатель в выражении xloc.file == ». В результате указатель просто сравнивается с 0, т.е. с NULL. Никакого эффекта это не имеет, так как ранее такая проверка уже выполнялась: xloc.file == NULL.
Хорошо, что терминальный ноль программист записал как ». Это помогает быстрее понять, что код ошибочен и как его надо исправить. Про это я также писал в книге (см. главу N9: Используйте для обозначения терминального нуля литерал »).
Правильный вариант кода:
if (xloc.file == NULL || strncmp (xloc.file, "1", 2) == 0
|| xloc.file[0] == '' || xloc.file[0] == 'xff'
|| xloc.file[1] == 'xff')
return false;
Хотя, давайте ещё немного улучшим код. Я рекомендую отформатировать выражение так:
if ( xloc.file == NULL
|| strncmp (xloc.file, "1", 2) == 0
|| xloc.file[0] == ''
|| xloc.file[0] == 'xff'
|| xloc.file[1] == 'xff')
return false;
Обратите внимание: теперь, если допустить ту же ошибку, шанс её заметить будет чуть-чуть выше:
if ( xloc.file == NULL
|| strncmp (xloc.file, "1", 2) == 0
|| xloc.file == ''
|| xloc.file[0] == 'xff'
|| xloc.file[1] == 'xff')
return false;
Потенциальное разыменование нулевого указателя
Ещё этот раздел можно было бы назвать «стотысячный пример, почему макросы — это плохо». Я очень не люблю макросы и всегда призываю поменьше их использовать. Макросы затрудняют чтение кода, провоцируют появление ошибок, усложняют работу статическим анализаторам. Как мне показалось из недолгого общения с кодом GCC, его авторы очень любят макросы. Я замучался изучать, во что раскрывается тот или иной макрос и возможно поэтому пропустил немало интересных ошибок. Признаюсь, я иногда бываю ленив. Но пару ошибок, связанных с макросами, я всё-таки продемонстрирую.
odr_type
get_odr_type (tree type, bool insert)
{
....
odr_types[val->id] = 0;
gcc_assert (val->derived_types.length() == 0);
if (odr_types_ptr)
val->id = odr_types.length ();
....
}
Предупреждение анализатора PVS-Studio: V595 The ‘odr_types_ptr’ pointer was utilized before it was verified against nullptr. Check lines: 2135, 2139. ipa-devirt.c 2135
Видите здесь ошибку? Думаю, нет, и сообщение анализатора ясности не вносит. Всё дело в том, что odr_types — это не имя переменной, а макрос, объявленным следующим образом:
#define odr_types (*odr_types_ptr)
Если раскрыть макрос и убрать всё не относящееся к делу, мы получим следующий код:
(*odr_types_ptr)[val->id] = 0;
if (odr_types_ptr)
В начале указатель разыменовывается, а потом проверяется. Приведёт это к беде на практике или нет, сказать сложно. Все зависит от того, может ли возникнуть ситуация, когда указатель действительно будет равен nullptr. Если такая ситуация невозможна, то следует удалить лишнюю проверку, которая будет вводить в заблуждение людей, поддерживающих код и анализатор кода. Если указатель может быть нулевым, то это серьёзная ошибка, которая требует ещё большего внимания и исправления.
Рассмотрим ещё один аналогичный случай:
static inline bool
sd_iterator_cond (sd_iterator_def *it_ptr, dep_t *dep_ptr)
{
....
it_ptr->linkp = &DEPS_LIST_FIRST (list);
if (list)
continue;
....
}
Предупреждение анализатора PVS-Studio: V595 The ‘list’ pointer was utilized before it was verified against nullptr. Check lines: 1627, 1629. sched-int.h 1627
Чтобы увидеть ошибку, нам опять потребуется показать устройство макроса:
#define DEPS_LIST_FIRST(L) ((L)->first)
Раскрываем макрос и получаем:
it_ptr->linkp = &((list)->first);
if (list)
continue;
И сейчас многие воскликнут: «Стоп, стоп! Здесь нет ошибки. Мы ведь просто получаем указатель на член класса. Никакого разыменования нулевого указателя здесь нет. Да, возможно код не аккуратен, но ошибки здесь нет!».
Всё не так просто. Здесь возникает неопределённое поведение. И то, что такой код может работать на практике, это просто везение. На самом деле, так писать нельзя. Например, оптимизирующий компилятор, увидев list->first, может удалить проверку if (list). Раз мы выполняли оператор ->, значит предполагается, что указатель не равен nullptr. Если это так, то проверять указатель не нужно.
Я написал целую статью на эту тему: «Разыменовывание нулевого указателя приводит к неопределённому поведению». Там как раз рассматривается аналогичный случай. Прежде чем спорить, прошу внимательно познакомиться с этой статьёй.
Впрочем, рассмотренная ситуация действительно сложна и неочевидна. Я допускаю, что могу быть всё-таки неправ и ошибки здесь нет. Однако, до сих пор мне никто не смог это доказать. Будет интересно услышать комментарии разработчиков GCC, если они обратят внимание на эту статью. Уж они-то точно должны знать, как работает компилятор и следует ли интерпретировать такой код как ошибочный, или нет.
Использование разрушенного массива
static void
dump_hsa_symbol (FILE *f, hsa_symbol *symbol)
{
const char *name;
if (symbol->m_name)
name = symbol->m_name;
else
{
char buf[64];
sprintf (buf, "__%s_%i", hsa_seg_name (symbol->m_segment),
symbol->m_name_number);
name = buf;
}
fprintf (f, "align(%u) %s_%s %s",
hsa_byte_alignment (symbol->m_align),
hsa_seg_name(symbol->m_segment),
hsa_type_name(symbol->m_type & ~BRIG_TYPE_ARRAY_MASK),
name);
....
}
Предупреждение анализатора PVS-Studio: V507 Pointer to local array ‘buf’ is stored outside the scope of this array. Such a pointer will become invalid. hsa-dump.c 704
Строка формируется во временном буфере buf. Адрес этого временного буфера сохраняется в переменной name и используется далее в теле функции. Ошибка в том, что после записи буфера в переменную name, сам этот буфер будет уничтожен.
Использовать указатель на разрушенный буфер нельзя. Формально мы имеем дело с неопределённым поведением. На практике этот код может вполне успешно работать. Корректная работа программы — это один из вариантов проявления неопределенного поведения.
В любом случае, этот код содержит ошибку и её необходимо исправить. Работать код может по той причине, что компилятор может посчитать ненужным использование временного буфера для последующего хранения других переменных или массивов. И тогда, хотя массив, созданный на стеке, считается разрушенным, на самом деле его может никто не трогать, и функция правильно выполнит свою работу. Вот только такое везение в любой момент может кончиться и код, который работал 10 лет, вдруг, при переходе на новую версию компилятора, начинает вести себя удивительнейшим образом.
Чтобы исправить ошибку, достаточно объявить массив buf в той же области видимости, что и указатель name:
static void
dump_hsa_symbol (FILE *f, hsa_symbol *symbol)
{
const char *name;
char buf[64];
....
}
Выполнение одинаковых действий, независимо от условия
Анализатор выявил участок кода, который однозначно я не могу идентифицировать как ошибочный. Однако, крайне подозрительно выполнить проверку, а потом, независимо от её результата, выполнять одни и те же действия. Конечно, возможно, это задел на будущее и пока всё корректно, но проверить этот участок кода явно стоит.
bool
thread_through_all_blocks (bool may_peel_loop_headers)
{
....
/* Case 1, threading from outside to inside the loop
after we'd already threaded through the header. */
if ((*path)[0]->e->dest->loop_father
!= path->last ()->e->src->loop_father)
{
delete_jump_thread_path (path);
e->aux = NULL;
ei_next (&ei);
}
else
{
delete_jump_thread_path (path);
e->aux = NULL;
ei_next (&ei);
}
....
}
Предупреждение анализатора PVS-Studio: V523 The ‘then’ statement is equivalent to the ‘else’ statement. tree-ssa-threadupdate.c 2596
Если этот код ошибочный, я, к сожалению, не догадываюсь как его следует исправить. Это тот случай, когда надо быть знакомым с проектом, чтобы внести правку.
Избыточное выражение вида (A == 1 || A != 2)
static const char *
alter_output_for_subst_insn (rtx insn, int alt)
{
const char *insn_out, *sp ;
char *old_out, *new_out, *cp;
int i, j, new_len;
insn_out = XTMPL (insn, 3);
if (alt < 2 || *insn_out == '*' || *insn_out != '@')
return insn_out;
....
}
Предупреждение анализатора PVS-Studio: V590 Consider inspecting this expression. The expression is excessive or contains a misprint. gensupport.c 1640
Нас интересует условие: (alt < 2 || *insn_out == ‘*’ || *insn_out != ‘@’)
Его можно сократить до: (alt < 2 || *insn_out != ‘@’)
Рискну предположить, что оператор != следует заменить на ==. Тогда код примет более осмысленный вид:
if (alt < 2 || *insn_out == '*' || *insn_out == '@')
Обнуление не того указателя
Рассмотрим функцию, занимающуюся освобождением ресурсов:
void
free_original_copy_tables (void)
{
gcc_assert (original_copy_bb_pool);
delete bb_copy;
bb_copy = NULL;
delete bb_original;
bb_copy = NULL;
delete loop_copy;
loop_copy = NULL;
delete original_copy_bb_pool;
original_copy_bb_pool = NULL;
}
Предупреждение анализатора PVS-Studio: V519 The ‘bb_copy’ variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 1076, 1078. cfg.c 1078
Обратите внимание на эти 4 строчки кода:
delete bb_copy;
bb_copy = NULL;
delete bb_original;
bb_copy = NULL;
Случайно дважды обнуляется указатель bb_copy. Правильный вариант:
delete bb_copy;
bb_copy = NULL;
delete bb_original;
bb_original = NULL;
Assert, который ничего не проверят
Неправильное условие, являющееся аргументом макроса gcc_assert не повлияет на корректность работы программы, но усложнит поиск ошибки, если таковая возникнет. Рассмотрим код:
static void
output_loc_operands (dw_loc_descr_ref loc, int for_eh_or_skip)
{
unsigned long die_offset
= get_ref_die_offset (val1->v.val_die_ref.die);
....
gcc_assert (die_offset > 0
&& die_offset <= (loc->dw_loc_opc == DW_OP_call2)
? 0xffff
: 0xffffffff);
....
}
Предупреждение анализатора PVS-Studio: V502 Perhaps the ‘?:’ operator works in a different way than it was expected. The ‘?:’ operator has a lower priority than the ‘<=’ operator. dwarf2out.c 2053
Приоритет тернарного оператора ?: ниже, чем у оператора сравнения <=. Это значит, что мы имеем дело с условием вида:
die_offset > 0 &&
((die_offset <= (loc->dw_loc_opc == DW_OP_call2)) ?
0xffff : 0xffffffff);
Таким образом, второй операнд оператора && может принимать значение 0xffff или 0xffffffff. Оба эти значения обозначают истину, поэтому выражение можно упростить до:
(die_offset > 0)
Это явно не то, что задумывал программист. Чтобы исправить ситуацию, следует добавить пару круглых скобок:
gcc_assert (die_offset > 0
&& die_offset <= ((loc->dw_loc_opc == DW_OP_call2)
? 0xffff
: 0xffffffff));
Оператор ?: очень коварен и его лучше не использовать в сложных выражениях. Уж очень легко допустить ошибку. У нас собрано большое количество примеров таких ошибок, найденных анализатором PVS-Studio в различных открытых проектах. Подробнее об операторе ?: я писал в уже упомянутой ранее книге (см. главу N4: Бойтесь оператора ?: и заключайте его в круглые скобки).
Кажется, забыли про «cost»
Структура alg_hash_entry объявлена следующим образом:
struct alg_hash_entry {
unsigned HOST_WIDE_INT t;
machine_mode mode;
enum alg_code alg;
struct mult_cost cost;
bool speed;
};
В функции synth_mult программист решил проверить, тот ли это объект, который ему нужен. Для этого ему требуется сравнить поля структуры. Однако, кажется в этом месте допущена ошибка:
static void synth_mult (....)
{
....
struct alg_hash_entry *entry_ptr;
....
if (entry_ptr->t == t
&& entry_ptr->mode == mode
&& entry_ptr->mode == mode
&& entry_ptr->speed == speed
&& entry_ptr->alg != alg_unknown)
{
....
}
Предупреждение анализатора PVS-Studio: V501 There are identical sub-expressions ‘entry_ptr->mode == mode’ to the left and to the right of the ‘&&’ operator. expmed.c 2573
Два раза подряд проверяется mode, но зато нет проверки cost. Возможно, одно из сравнений нужно просто удалить, а возможно, нужно сравнивать cost. Мне сложно судить, но код явно стоит поправить.
Дубликаты присваиваний
Следующие участки кода, на мой взгляд, не представляют опасности и, кажется, дублирующееся присваивание можно просто удалить.
Случай N1
type_p
find_structure (const char *name, enum typekind kind)
{
....
structures = s; // <=
s->kind = kind;
s->u.s.tag = name;
structures = s; // <=
return s;
}
Предупреждение анализатора PVS-Studio: V519 The ‘structures’ variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 842, 845. gengtype.c 845
Случай N2
static rtx
ix86_expand_sse_pcmpistr (....)
{
unsigned int i, nargs;
....
case V8DI_FTYPE_V8DI_V8DI_V8DI_INT_UQI:
case V16SI_FTYPE_V16SI_V16SI_V16SI_INT_UHI:
case V2DF_FTYPE_V2DF_V2DF_V2DI_INT_UQI:
case V4SF_FTYPE_V4SF_V4SF_V4SI_INT_UQI:
case V8SF_FTYPE_V8SF_V8SF_V8SI_INT_UQI:
case V8SI_FTYPE_V8SI_V8SI_V8SI_INT_UQI:
case V4DF_FTYPE_V4DF_V4DF_V4DI_INT_UQI:
case V4DI_FTYPE_V4DI_V4DI_V4DI_INT_UQI:
case V4SI_FTYPE_V4SI_V4SI_V4SI_INT_UQI:
case V2DI_FTYPE_V2DI_V2DI_V2DI_INT_UQI:
nargs = 5; // <=
nargs = 5; // <=
mask_pos = 1;
nargs_constant = 1;
break;
....
}
Предупреждение анализатора PVS-Studio: V519 The ‘nargs’ variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 39951, 39952. i386.c 39952
Случай N3
Последний случай более странный, чем остальные. Возможно, тут есть какая-то ошибка. Переменной steptype значение присваивается 2 или 3 раза. Это подозрительно.
static void
cand_value_at (....)
{
aff_tree step, delta, nit;
struct iv *iv = cand->iv;
tree type = TREE_TYPE (iv->base);
tree steptype = type; // <=
if (POINTER_TYPE_P (type))
steptype = sizetype; // <=
steptype = unsigned_type_for (type); // <=
....
}
Предупреждение анализатора PVS-Studio: V519 The ‘steptype’ variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 5173, 5174. tree-ssa-loop-ivopts.c 5174
Заключение
Я рад, что написал эту статью. Теперь мне есть что отвечать на комментарии вида «PVS-Studio не нужен, так как все те же предупреждения выдаёт и GCC». Как видите, PVS-Studio очень мощный инструмент и превосходит по диагностическим возможностям GCC. Я не отрицаю, что в GCC реализованы отличные диагностики. Этот компилятор, при должной настройке, действительно выявляет много проблем в коде. Но PVS-Studio — это специализированный и быстро развивающийся инструмент, а это значит, он всегда будет лучше выявлять ошибки в коде, чем это делают компиляторы.
Приглашаю познакомиться с проверками других известных открытых проектов, посетив этот раздел нашего сайта. А также, тем, кто использует Twitter, последовать за мной @Code_Analysis. Я регулярно публикую ссылки на интересные статьи по программированию на языке C и C++, а также рассказываю о новых достижениях нашего анализатора.
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey karpov. Bugs found in GCC with the help of PVS-Studio.
Решение ошибок сборки⚓︎
При сборке пакетов иногда происходят ошибки.
В данном разделе будет описано решение наиболее распространённых ошибок.
Стадии, на которых может произойти ошибка⚓︎
Ошибка может произойти на любой стадии, однако чаще всего это случается после ввода make.
При этом в первую очередь определите действие, которое завершилось ошибкой — сделать это можно, просмотрев команду, завершившуюся с ошибкой.
В частности, если команда даётся компилятору (cc, gcc или clang), то произошла ошибка компиляции. Эти ошибки, обычно, наиболее трудны в решении.
Если команда даётся ld, то ошибка произошла при линковке.
Также ошибка может произойти, например, при построении документации. В этом случае самым простым вариантом будет отключение выполнения этого шага.
Общие принципы решения ошибок⚓︎
Убедитесь что ошибка воспроизводима — выполните make clean
, а потом повторите make
.
Если ошибка не исчезла, то прочитайте лог (хотя бы последние 30 строк).
Практически всегда там будет сказано о том, что за ошибка произошла.
Попробуйте поискать в интернете по частям лога, возможно, решение этой ошибки уже было где-либо описано.
Ошибки компиляции⚓︎
Ошибки компиляции — наиболее сложные в своём решении.
gcc всегда сообщает строку, в которой произошла ошибка — проверьте её.
Не найден заголовок⚓︎
Весьма простая ошибка.
Вывод⚓︎
dummy.c:1:10: fatal error: blablabla: No such file or directory
1 | #include <blablabla>
| ^~~~~~~~~~~
compilation terminated.
Имя заголовка и файла может быть другим.
Решение⚓︎
Поищите этот заголовок в папке /usr/include
и директории с исходным кодом пакета. Если он существует, то добавьте в переменную CPPFLAGS
параметр -I/путь/к/директории/с/этим/заголовком
. Если он не существует — установите пакет, который его предоставляет.
Ошибки линковки⚓︎
В процессе линковки несколько объектных файлов соединяются в один, и к ним подключаются библиотеки.
undefined reference to …⚓︎
Данная ошибка вызвана тем, что необходимая библиотека не была подключена.
Решение⚓︎
Попытайтесь определить, исходя из лога, какая библиотека не была подключена. Добавьте в переменную CFLAGS
параметр -lsomelib (не надо указывать название файла библиотеки), например, -lcurses
.
Ошибки configure⚓︎
Обычно они происходят из-за отсутствия зависимостей или их неработоспособности.
Your compiler just tried to compile the file named foo.cc
. Upon hitting line number line
, the compiler finds:
#include "bar"
or
#include <bar>
The compiler then tries to find that file. For this, it uses a set of directories to look into, but within this set, there is no file bar
. For an explanation of the difference between the versions of the include statement look here.
How to tell the compiler where to find it
g++
has an option -I
. It lets you add include search paths to the command line. Imagine that your file bar
is in a folder named frobnicate
, relative to foo.cc
(assume you are compiling from the directory where foo.cc
is located):
g++ -Ifrobnicate foo.cc
You can add more include-paths; each you give is relative to the current directory. Microsoft’s compiler has a correlating option /I
that works in the same way, or in Visual Studio, the folders can be set in the Property Pages of the Project, under Configuration Properties->C/C++->General->Additional Include Directories.
Now imagine you have multiple version of bar
in different folders, given:
// A/bar
#include<string>
std::string which() { return "A/bar"; }
// B/bar
#include<string>
std::string which() { return "B/bar"; }
// C/bar
#include<string>
std::string which() { return "C/bar"; }
// foo.cc
#include "bar"
#include <iostream>
int main () {
std::cout << which() << std::endl;
}
The priority with #include "bar"
is leftmost:
$ g++ -IA -IB -IC foo.cc
$ ./a.out
A/bar
As you see, when the compiler started looking through A/
, B/
and C/
, it stopped at the first or leftmost hit.
This is true of both forms, include <>
and incude ""
.
Difference between #include <bar>
and #include "bar"
Usually, the #include <xxx>
makes it look into system folders first, the #include "xxx"
makes it look into the current or custom folders first.
E.g.:
Imagine you have the following files in your project folder:
list
main.cc
with main.cc
:
#include "list"
....
For this, your compiler will #include
the file list
in your project folder, because it currently compiles main.cc
and there is that file list
in the current folder.
But with main.cc
:
#include <list>
....
and then g++ main.cc
, your compiler will look into the system folders first, and because <list>
is a standard header, it will #include
the file named list
that comes with your C++ platform as part of the standard library.
This is all a bit simplified, but should give you the basic idea.
Details on <>
/""
-priorities and -I
According to the gcc-documentation, the priority for include <>
is, on a «normal Unix system», as follows:
/usr/local/include
libdir/gcc/target/version/include
/usr/target/include
/usr/include
For C++ programs, it will also look in /usr/include/c++/version, first. In the above, target is the canonical name of the system GCC was configured to compile code for; […].
The documentation also states:
You can add to this list with the -Idir command line option. All the directories named by -I are searched, in left-to-right order, before the default directories. The only exception is when dir is already searched by default. In this case, the option is ignored and the search order for system directories remains unchanged.
To continue our #include<list> / #include"list"
example (same code):
g++ -I. main.cc
and
#include<list>
int main () { std::list<int> l; }
and indeed, the -I.
prioritizes the folder .
over the system includes and we get a compiler error.