在 C 语言中具有编译时检查的 memcpy 的现代、可移植、安全的等价物是什么?



我正在查看一些简洁的示例代码,这些代码显示了如何在特定硬件平台上复制浮点数而不会遇到内存对齐问题。我注意到它没有使用sizeof()

uint8_t mybuffer[4];
float f;
memcpy(&f, mybuffer, 4)

下一个问题是使用什么sizeof()导致Microsoft的部分答案memcpy_s但这是一种运行时方法,需要返回检查。

稍微超出我引用的代码片段,对于可以在编译时正确确定源和目标大小的特定情况,是否有一个紧凑/单行(例如保持示例代码简短)等效于memcpy(),以确保长度相同,如果不是,则终止编译并带有有用的消息?一种避免了ARR01-C陷阱的方法。不要将 sizeof 运算符应用于指针,因为取数组的大小会很好。

这里与 memcpy() 有一些重叠,大小参数的值应该是多少?

  • C 语言中具有编译时检查的 memcpy 的现代、可移植、安全的等价物是什么?

    memcpy现代、便携且安全。不存在应改用的替代项。

    如果你使用Microsoft逻辑,那么不称职的程序员将空指针传递给它memcpy错。但是这样的错误应该通过在传递输入之前检查输入来修复,而不是通过更改memcpy来修复。

    memcpy_s将被视为已弃用,应避免使用C11边界检查接口中的所有内容,因为不存在编译器对它的支持。像Visual Studio这样的危险编译器也使它不可移植,因为它们甚至不遵循C标准Annex K,而是发明了自己的不兼容版本。我的建议是将每个以_s结尾的功能视为安全隐患。

  • 是否有一个紧凑/单行(例如,保持示例代码简短)等效于 memcpy(),以确保长度相同,如果没有,则终止编译并显示有用的消息?

    是的。memcpy(&f, mybuffer, sizeof(f))

  • 一种避免了ARR01-C陷阱的方法。不要将 sizeof 运算符应用于指针,当取数组的大小会很好

    CERT规则仅适用于不知道函数参数数组衰减如何工作的程序员。解决方案是教育人们,而不是假设每个使用你的代码库的程序员都是不称职的。此外,这是静态分析器会捕获的典型错误 - 启用静态分析实际上可能是 CERT 规则存在的原因。

    如果您知道自己在做什么,则可以在内部函数中使用sizeof。每个将数组作为参数的函数也需要一个大小。使用该大小。因此,如果您有这个:void foo (size_t n, float array[n]);那么您可以在该函数中执行sizeof(float[n])就可以了。

    作为替代方案,还有另一个有点晦涩的版本,有时由安全标准推广。如果将函数更改为使用数组指针,void foo (size_t n, float(*array)[n]),则可以在该函数内执行sizeof(*array)。我们还可以使用数组指针来提高类型安全性:

    void foo (float(*array)[5]);
    ...
    float arr[4];
    foo(&arr); // compiler error
    

    缺点:语法复杂得多,我们失去了制作正确参数的能力const

memcpy()函数完全没问题。引入 C11 边界检查功能是为了减少缓冲区溢出缺陷的可能性。

文档 N1967,附件 K 的现场经验 — 边界检查接口,在不必要的使用一节中这样说:

一个普遍的谬误源于Microsoft对 标准函数 [DEPR],以努力增加采用 API 是每次调用标准函数都是必然的 不安全,应替换为"更安全"的 API。结果, 注重安全的团队有时会天真地开展长达数月的项目 重写他们的工作代码并尽职尽责地替换所有实例 具有相应 API 的"已弃用"函数。这不仅如此 导致不必要的流失,并增加注入新错误的风险 进入正确的代码,它也使重写的代码效率降低。

sizeof运算符是编译时运算符,这意味着大小计算由编译器在编译时可用的任何信息完成。这就是为什么使用运算符可能会有问题的原因,并且是与指针和数组一起使用时运行时错误的根源。提高编译器的警告级别并使用静态代码分析工具可以帮助找到这些问题区域。

由于sizeof运算符是编译时运算符,因此对于编译器错误或警告,您可以执行太多操作,尽管您的特定编译器可能具有可以设置的警告级别来执行此类检查。

我所做的是拥有一个预处理器宏,我可以将其用于调试版本中的运行时检查或用于验证的特殊发布版本,这些宏将在为生产环境执行发布版本时删除,以消除检查开销。

因此,如果您有以下来源:

uint8_t mybuffer[4];
float f;
memcpy(&f, mybuffer, sizeof(mybuffer));

这将具有正确的字节数,因为编译器可以使用数组mybuffer[4]及其实际大小。

但是,我实际上更喜欢通过指定memcpy()目标的大小来进行以下修改。这可确保即使源大小不正确,也不会发生缓冲区溢出。

uint8_t mybuffer[4];
float f;
memcpy(&f, mybuffer, sizeof(f));

sizeof运算符出现问题的地方是编译器无法推断数组的大小或地址在指针中的对象的大小。将它与函数参数的数组声明一起使用也不安全。

如果你有一个函数int xxx (int a[5]),并且在该函数中你尝试使用sizeof运算符来获取以字节为单位的数组大小,你可能会得到一个int *的大小。

最新更新