В следующем примере:
bool bad_function()
{
char_t * ptr = 0;
// MISRA doesn't complains here, it allows cast of char* to void* pointer
void* p2 = ptr;
// the following 2 MISRA violations are reported in each of the casts bellow (two per code line)
// (1) Event misra_violation: [Required] MISRA C++-2008 Rule 5-2-7 violation: An object with pointer type shall not be converted to an unrelated pointer type, either directly or indirectly
// (1) Event misra_violation: [Required] MISRA C++-2008 Rule 5-2-8 violation: An object with integer type or pointer to void type shall not be converted to an object with pointer type
ptr = (char_t*) (p2);
ptr = static_cast<char_t*> (p2);
ptr = reinterpret_cast<char_t*> (p2);
return true;
}
MISRA 5-2-8 и 5-2-7 нарушений сообщается.
Как я могу устранить это нарушение?
Мне нужен кто-то опытный со статическим анализом C ++, чтобы помочь мне. Я бью себя по голове этими глупыми правилами за несколько дней.
Согласно стандарту MISRA C ++ (MISRA-Cpp-2008.pdf: Правило 5-2-7 (обязательно): объект с типом указателя не должен быть преобразован в несвязанный тип указателя, прямо или косвенно.
Хорошо, но у нас есть много кода, который, например, должен преобразовать адрес в char*
а затем использовать его с std::ifstream
, который read(char* buffer, int length)
Функция требует, чтобы набрать адрес (char_t*
). Так как же, по словам ребят из MISRA, кто-то может программировать на C ++ и вообще не использовать приведения? Стандарт не говорит, КАК преобразование указателя тогда должно быть сделано.
В моем рабочем коде мои проблемы заключаются в операциях чтения файлов с использованием чтения с станд: ifstream из файлов в предопределенных структурах данных:
if (file.read((char_t*)&info, (int32_t)sizeof(INFO)).gcount() != (int32_t)sizeof(INFO)
{
LOG("ERROR: Couldn't read the file info header\n");
res = GENERAL_FAILURE;
}
Как предполагается сделать это в соответствии с MISRA?
Так есть ли какие-либо решения вообще?
РЕДАКТИРОВАТЬ: Питер и Q.Q. оба ответа верны, кажется, что MISRA действительно хочет сделать все без каких-либо забросов, что трудно сделать, если проект находится на завершающей стадии. Есть два варианта:
1 — документируйте отклонения MISRA один за другим и объясните, почему приведение в порядке, объясните, как это было проверено (предложение Q.Q.)
2 — используйте байтовый массив из типа char для file.read (), затем после безопасного чтения содержимого файла приведите байтовый массив к содержимому заголовков, это должно быть сделано для каждого элемента по одному, потому что если вы приведете char * к int32_t this Это снова нарушение правила 5-2-7. Иногда это слишком много работы.
Основная причина применения правила MISRA заключается в том, что преобразование любого указателя / адреса в любой не пустой указатель позволяет использовать этот адрес так, как если бы он отличался от того, чем он является на самом деле. Компилятор будет жаловаться на неявное преобразование в этих случаях. Использование Typecast (или C ++ _cast
операторы) по существу останавливает компиляцию и, при слишком большом количестве обстоятельств, для разыменования этот указатель дает неопределенное поведение.
Другими словами, путем принудительного преобразования типов вы вводите потенциально неопределенное поведение и отключаете все возможности компилятора, предупреждающего вас о такой возможности. MISRA считают, что это плохая идея … не смотря на то, что многие программисты, которые думают с точки зрения простоты кодирования, считают это хорошей идеей в некоторых случаях.
Вы должны понимать, что философия проверок MISRA меньше заботится о простоте программирования, чем обычные программисты, и больше заботится о предотвращении обстоятельств, при которых неопределенное (или определяемое реализацией, неопределенное и т. Д.) Поведение проходит все проверки и приводит к коду » дикий «, который может причинить вред.
Дело в том, что в вашем реальном случае вы полагаетесь на file.read()
правильно заполнить (предположительно) структуру данных с именем info
,
if (file.read((char_t*)&info, (int32_t)sizeof(INFO)).gcount() != (int32_t)sizeof(INFO)
{
LOG("ERROR: Couldn't read the file info header\n");
res = GENERAL_FAILURE;
}
Что вам нужно сделать, так это немного усерднее работать над созданием правильного кода, который пройдет проверку MISRA. Что-то вроде
std::streamsize size_to_read = whatever();
std::vector<char> buffer(size_to_read);
if (file.read(&buffer[0], size_to_read) == size_to_read)
{
// use some rules to interpret contents of buffer (i.e. a protocol) and populate info
// generally these rules will check that the data is in a valid form
// but not rely on doing any pointer type conversions
}
else
{
LOG("ERROR: Couldn't read the file info header\n");
res = GENERAL_FAILURE;
}
Да, я понимаю, что это больше, чем просто использовать преобразование типов и разрешать двоичное сохранение и чтение структур. Но их перерывы. Помимо прохождения проверки MISRA, у этого подхода есть и другие преимущества, если вы все сделаете правильно, например, формат файла полностью независим от того, какой компилятор используется для сборки вашего кода. Ваш код зависит от заданных реализацией величин (расположение элементов в структуре, результаты sizeof
) поэтому ваш код — если он построен с помощью компилятора A — может быть не в состоянии прочитать файл, сгенерированный кодом, созданным с помощью компилятора B. И одна из общих тем требований MISRA — сокращение или устранение любого кода с поведением, которое может быть чувствительным к реализации. определенные количества.
Примечание: вы также проходите char_t *
в std::istream::read()
в качестве первого аргумента и int32_t
как второй аргумент. Оба на самом деле неверны. Фактические аргументы имеют тип char *
а также std::streamsize
(который может быть, но не обязательно должен быть int32_t
).
Преобразование несвязанных указателей в char*
это не хорошая практика.
Но если у вас большая унаследованная кодовая база, делающая это часто, вы можете подавить правила, добавив специальные комментарии.
fread
это очень хорошая функция C ++ для ввода файлов, и она использует void*
, что позволяет MISRA.
Это также хорошо для чтения двоичных данных, в отличие от fstream
который обрабатывает все данные с помощью локализованной логики преобразования символов (это «фасет» в iostream, который настраивается, но в стандарте не определен какой-либо переносимый способ достижения преобразования без операции).
С-стиль fopen
/fclose
неудачно в программе на C ++, так как вы можете забыть очистить ваши файлы. К счастью, у нас есть это std::unique_ptr
который может добавить функциональность RAII к произвольному типу указателя. использование std::unique_ptr<FILE*, decltype(&fclose)>
и иметь быстрый исключительный бинарный файловый ввод / вывод в C ++.
NB: распространенным заблуждением является то, что std::ios::binary
дает бинарный файл ввода / вывода. Это не. Все это влияет на преобразование новой строки и (в некоторых системах) обработку маркера конца файла, но это не влияет на преобразование символов, управляемое фасетами.