在下面的示例中:
void 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);
}
报告了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++编程并且根本不使用任何演员?该标准没有说明必须如何进行指针转换。
在我的生产代码中,我的问题在于使用从预定义数据结构中的文件中使用 std::ifstream 读取的文件读取操作:
if (file.read((char_t*)&info, (int32_t)sizeof(INFO)).gcount() != (int32_t)sizeof(INFO)
{
LOG("ERROR: Couldn't read the file info headern");
res = GENERAL_FAILURE;
}
根据MISRA应该如何做?
那么有什么解决方案吗?
编辑:彼得和Q.Q.的答案都是正确的,看来MISRA真的想在没有任何演员的情况下做所有事情,如果项目处于最后阶段,这很难完成。特蕾娅有两种选择:
1 - 逐个记录 MISRA 偏差并解释为什么铸件是可以的,解释它是如何测试的(Q.Q. 建议(
2 - 将char 类型的字节数组用于 file.read((,然后在安全读取文件内容后将字节数组转换为标头内容,必须为每个成员一一执行此操作,因为如果您将 char* 转换为int32_t这再次违反规则 5-2-7。有时工作量太大。
MISRA 规则的基本原因是,将任何指针/地址转换为任何非 void 指针都允许使用该地址,就好像它是一个与实际不同的对象一样。 在这些情况下,编译器会抱怨隐式转换。 使用类型转换(或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 headern");
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 headern");
res = GENERAL_FAILURE;
}
是的,我意识到这比简单地使用类型转换以及允许二进制保存和读取结构体要多得多。 但他们是休息时间。 除了通过MISRA检查器之外,如果您做得对,这种方法还有其他优点,例如文件格式完全独立于用于构建代码的编译器。 您的代码依赖于实现定义的数量(结构中成员的布局、sizeof
的结果(,因此您的代码(如果使用编译器 A 构建(可能无法读取由使用编译器 B 构建的代码生成的文件。 MISRA要求的一个共同主题是减少或消除任何可能对实现定义的数量敏感的行为的代码。
注意:您还将char_t *
传递给std::istream::read()
作为第一个参数,将int32_t
作为第二个参数。 两者实际上都是不正确的。 实际参数的类型是 char *
和 std::streamsize
(可能是,但不需要是int32_t
(。
将不相关的指针转换为char*
不是一个好的做法。但是,如果您有一个大型遗留代码库经常执行此操作,则可以通过添加特殊注释来禁止显示规则。
fread
是一个非常好的文件输入C++函数,它使用MISRA允许的void*
。
它还擅长读取二进制数据,不像fstream
通过本地化字符转换逻辑处理所有数据(这是iostream上的"分面",它是可配置的,但标准没有定义任何可移植的方式来实现无操作转换(。
fopen
/fclose
的C风格在C++程序中是不幸的,因为您可能会忘记清理文件。 幸运的是,我们有这个std::unique_ptr
可以将 RAII 功能添加到任意指针类型。 使用 std::unique_ptr<FILE*, decltype(&fclose)>
并在 C++ 中具有快速异常安全的二进制文件 I/O。
注意:一个常见的误解是std::ios::binary
提供二进制文件 I/O。 它没有。 它只影响换行符转换和(在某些系统上(文件结束标记处理,但对分面驱动的字符转换没有影响。