【问题标题】:Strange result with pointer casting指针转换的奇怪结果
【发布时间】:2021-01-29 03:06:36
【问题描述】:

我正在测试是否可以在同一个数组中包含 int 和 float 值,我编写了以下代码:

#include <stdio.h>
#include <stdlib.h>

int main()

{
    int v[4]={0,0,0,0};
    *((float*)(&v[1]))=45.6;
    printf("%f\n",*((float*)(&v[1])));

    printf("%f\n",v[1]);
    return 0;



}

我期望有 45.599998 和 0 或在两个 printf 上具有相同的值,但我得到不同的结果: 45.599998 45.599983 为什么?,发生了什么?

编辑
顺便说一句,我想强调一下,对于这个问题,我对替代方案不感兴趣,我更感兴趣的是理解为什么不起作用。

【问题讨论】:

  • 代码有两种不同类型的未定义行为。因此,尝试理解或预测输出是没有意义的。里面可以是任何东西。使用-Wall -Wextra -Werror 编译至少会迫使您修复其中一个错误。
  • 我在我的回答下将您评论的部分内容添加到您的原始帖子中,因为我认为让其他人知道您真正想要学习的内容非常相关。

标签: c pointers casting


【解决方案1】:

在示例代码中,正如 cmets 中所指出的,undefined behavior 被调用。 (在这种情况下,我认为 strict aliasing 被违反了。)

“[是否] 可以在同一个数组中拥有 intfloat 值”

不在C 中,任何type 中的array 只能包含一种类型。但是,如果这种安排的变体对您有用,则可以在compound type 的单个实例中定义这两种类型,例如struct 的数组。例如:

typedef struct {
   int iVal;
   float fVal;
} val_s;

val_s val[10];

现在您有一个10 类型的val_s 元素数组,每个元素都包含intfloat 类型成员。

C 中复合类型的另一种变体(在 cmets 中已向我指出,这可能是您正在寻找的更多内容)是内置的 C 类型,它允许多种类型 共享相同的内存,是union。需要注意的是,由程序员来跟踪最后写入的成员中的哪些成员,因为在任何给定时间只有一个成员可以包含一个值......

例子:

typedef union {
   int iVal;
   float fVal;
} val_s;

val_s val;

【讨论】:

  • 我认为,或者使用 union 而不是 struct 会更接近 OP 所说的他们想要做的事情。
  • @JohnBollinger - 您想在答案中使用它吗?如果没有,我很乐意在此处添加。
  • 我正在写一个答案,但它侧重于问题的不同方面。随意添加 union 替代此答案。
  • 谢谢你的回答。我对这样做的替代方案并不感兴趣,我更感兴趣的是理解为什么不起作用。所以如果我理解正确,问题出在 printf("%f\n",v[1]);,但上代码可以吗?。
  • @user14450516 - - 关于“我对这样做的替代方案并不感兴趣,我更想了解原因”...,这很公平。下次如果您将其作为您的目标放在原始帖子中,那会很好:) 我相信如果您查看 John Bollinger 的答案,您就会在这里清楚地理解您的问题的答案。 (为什么它不起作用......)正如我在上面的回答中简要指出的那样,您正在尝试做的是充满了 undefined behavior 。因此,您的代码可以做任何事情,但您不能信任它所做的任何事情。 :)
【解决方案2】:

我期望有 45.599998 和 0 或在两个 printf 上具有相同的值,但我得到不同的结果:45.599998 45.599983 为什么?,发生了什么?

假设我们通过使用这种变体消除了您代码中的一些未定义行为:

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

int main() {
    int v[4]={0,0,0,0};
    float f = 45.6;
    memcpy(&v[1], &f, sizeof f);

    printf("%f\n", f);
    printf("%f\n", v[1]);

    return 0;
}

这仍然有未定义的行为,因为格式指令%f 没有与v[1] 正确类型匹配,但否则只要float 不大于int 就可以,并且int 没有陷阱表示(两者都适用于大多数 C 实现)。

即使fv[1] 的值可能具有相同的字节序列表示,它们的类型之间的差异也会对这段代码产生重要影响。 printf 等可变参数函数的可变参数受“默认参数提升”的约束。这些保持int 类型的值不变,但它们将floats 提升为double 类型。因此,如果您的 floatdouble 在实践中有所不同(它们通常会这样做),那么

  1. printf 在这两种情况下接收到不同的参数值,即使只考虑每个参数的字节序列,并且
  2. v[1] 的情况下,printf 可能没有收到足够宽的值。

因此,如果您想沉迷于假设程序在这种未定义行为的情况下实际做了什么的可疑做法,那么更有可能的一种可能性是,在v[1] 情况下,它查看的字节序列a float,再加上恰好在内存中的一些额外随机字节,将它们解释为好像它们是 double 的字节,并且,幸运的是和所选测试值的详细信息,它得出的数值接近但不完全匹配您的float 提升到的double

【讨论】:

  • “沉迷于假设、...、未定义行为...的可疑实践” 喜欢它。
  • 所以如果我不使用 int 和 float 我使用 long long 和 double 未定义的行为是固定的?
  • 不,@user14450516,即使假设long longdouble 的大小相同,也无法修复 UB,这是无法保证的。无论类型的大小如何,参数类型和字段指令之间的不匹配都会导致未定义的行为。如果使用的整数类型与 double 的大小相同,那么 UB 显示为相同的输出被打印两次的机会会更好,但该结果甚至没有条件保证。
【解决方案3】:

使用浮点说明符并传递一个整数值来满足格式字符串中的那个参数是未定义的行为,也就是说,所有的赌注都没有。

许多系统以完全不同的方式传递整数参数和浮点参数;有些没有。 “为什么”不能完全回答。您碰巧在一个调用约定似乎没有以不同方式传递整数和浮点参数的系统上。

【讨论】:

    猜你喜欢
    • 2014-02-16
    • 2011-10-07
    • 1970-01-01
    • 2014-08-13
    • 2017-01-24
    • 1970-01-01
    • 1970-01-01
    • 2019-12-10
    • 2011-09-20
    相关资源
    最近更新 更多