【问题标题】:What is memset doing to a float array?memset 对浮点数组做了什么?
【发布时间】:2022-01-02 08:21:38
【问题描述】:

我最近在网上找到了一段代码,看起来有点像这样:

#include <stdio.h>
#include <string.h>

int main() {
    float m[10];
    memset(m, 0, 20); 
}

我还看到了一个像这样的sn-p,我认为这是正确的:

memset(m, 0, sizeof m); 

当尝试使用这个 sn-p 打印出第一个示例的所有值时:

for (int i = 0; i < 20; i++) {
    printf("%f, \n", m[i]);
}

它会产生这样的输出:

0.000000, 
0.000000, 
0.000000, 
0.000000, 
0.000000, 
0.000000, 
0.000000, 
0.000000, 
0.000000, 
0.000000, 
-0.000000, 
-4587372491414098149376.000000, 
-0.000013, 
0.000000, 
0.000000, 
0.000000, 
0.000000, 
0.000000, 
0.000000, 
0.000000

重新编译时值发生变化的地方。

现在我有几个问题:

  • 为什么 memset 写入 float-array 的次数比分配的多,为什么你不能用 char-array 做到这一点?
  • 为什么如此不一致?
  • 为什么将第二个值更改为 memset1 的值不会改变输出?

【问题讨论】:

  • 数组中只有 10 个浮点数。您的 sn-p 尝试打印 20。
  • @gurkensaas 记录在案的原因是访问超出其边界的数组具有未定义的行为。
  • 这能回答你的问题吗? Array index out of bound behavior
  • @gurkensaas 是否有文档说明为什么这不会产生错误? 在 C 中,超出数组的边界有点像在繁忙的街道上乱穿马路。几乎任何事情都可能发生,但没有什么是可以保证的。通常,你会侥幸逃脱。有时,警察可能会给您写一张罚单(尽管这很不寻常,就像“超出数组边界”是 C 语言中的一个不寻常的错误消息一样)。偶尔会发生一些可怕的事情,比如你会被半人半撞。
  • 唯一明智的举动(用WarGames 解释计算机)是根本不玩——也就是说,不要超出数组的范围!

标签: c floating-point


【解决方案1】:

为什么 memset 写入 float-array 的次数比分配的多,为什么你不能用 char-array 这样做?

memset(m, 0, 20);,正如问题最初显示的那样,写入的内容不超过分配的内容。通常,float 在 C 实现中是 4 个字节,所以float m[10]; 分配 40 个字节,memset(m, 0, 20); 写入 20 个字节。

在新代码中,memset(m, 0, sizeof m);m 写入的字节数与它一样多,不多也不少。

如果memset 被要求写更多,您可以尝试这样做的原因是 C 实现通常不进行安全检查操作,而 C 标准不要求它们这样做。

为什么会如此不一致?

没有什么不一致的。 memset 将零写入m 的前 20 个字节,这是浮点零的编码,采用 float 常用的格式(IEEE-754 binary32,也称为“单精度”)。

之后的字节没有被写入,因此打印它们使用未初始化的数据。 C标准说未初始化对象的值是不确定的。一个常见的结果是程序使用了内存中已经存在的任何内容。这可能是零,也可能是别的东西。

但是,使用循环 for (int i = 0; i &lt; 20; i++),您超出了 m 中的 10 个元素。那么访问m[i] 的行为不是C 标准定义的。如上所述,一个常见的结果是程序访问计算的内存并使用那里发生的任何内容。但是,其他各种行为也是可能的,包括由于尝试访问未映射的内存或编译器在优化期间将未定义的代码替换为替代代码而导致的崩溃。

为什么更改memset 的第二个值不会改变输出?

它将取决于您将其更改为什么。字节的某些值可能会导致float 值非常小,它们仍然打印为“0.000000”。例如,如果字节被设置为1,使得32位0x01010101每个float,它们代表了float的8454401•2 值 - 148 SUP> = 2.36942782761723955384693006253917004604239556833255136345174597127722672385008451101384707726538181304931640625•10 −38.

如果您将 64 用作 memset 的第二个参数,则这些位将设置为 0x40404040,它对值 3.0039215087890625 进行编码,因此将打印“3.003922”。

【讨论】:

  • 抱歉 Eric,float 在当今的常见实现中只有 7 位以上的有效数字,因此打印您打印的数字是无意义的......而且根本没有用(未定义的行为)
  • @LuisColorado:实际上,我不小心遗漏了几个数字。 float 常用的格式,IEEE-754 binary32,根本没有十进制数字。它是基于二进制的。正如我在 C 2018 5.2.4.2.2 3 和 IEEE-754 2008 3.4 中展示的那样,表示的值是 完全。即使在具有低质量二进制到十进制转换例程的 C 实现中(即错误的printf 实现),当float 用于计算时,该精确值也控制着执行的算术,所以它应该是用于分析和理解浮点计算。
  • @LuisColorado:这是对浮点运算的常见误解。在浮点运算中,数据准确地表示特定数字(包括无穷大),但 NaN 除外。它们不是近似值。相反,浮点运算被定义为逼近实数运算。当进行运算时,进行舍入。了解执行近似值的是运算而不是数字,这对于理解、设计、分析、编写证明和调试浮点运算至关重要。
  • 是的...我明白这一点,但是显示的数字多于有意义的数字是没有意义的(尽管 glibc 应要求提供,我认为这就是你所做的),因为正如你所说,有这些额外数字中的任何一个具有任何值的机会均等。
  • @LuisColorado:不,数字具有任何价值的机会不均等。它们是确定性的;它们具有 IEEE-754 标准指定的值的概率为 1,它们具有其他值的概率为 0。这些数字是有意义的。
【解决方案2】:

Memsetfloat 数组没有任何作用。您只有一个包含 10 个浮点数的数组,并且使用了一个覆盖 20 个浮点数的循环,这意味着,由于 C 不检查数组边界……您已经从数组的末尾扩展了很远,并将其解释为浮点数不是。我不会在这里重复其他答案中已经说过的内容,但是您在调整 C 对象的大小时遇到​​了问题。 sizeof 运算符是您的朋友。在将参数传递给 memcpymalloc 等函数时使用它,而不是使用常量,因此如果您调整对象大小,您仍然可以,无需更改常量值。

【讨论】:

    猜你喜欢
    • 2020-09-08
    • 1970-01-01
    • 2010-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多