繁体   English   中英

为什么我可以将 int 和 BOOL 转换为 void*,而不是 float?

[英]Why can I cast int and BOOL to void*, but not float?

void*是 C 和衍生语言的一个有用的特性。 例如,可以使用void*将 objective-C object 指针存储在 C++ class 中。

我最近在研究一个类型转换框架,由于时间限制有点懒——所以我使用了void* ……这就是这个问题的来源:

为什么我可以将 int 类型转换为 void*,但不能将 float 类型转换为 void*?

BOOL不是C ++类型。 它可能是typedef或在某处定义的,在这种情况下,它与int相同。 例如,Windows在Windef.h中具有以下功能:

    typedef int                 BOOL;

因此,您的问题简化为,为什么可以将int转换为void *,而不能将float转换为void *?

从int到void *是可以的,但是通常不建议这样做(某些编译器会警告它),因为它们在表示上本质上是相同的。 指针基本上是一个整数,它指向内存中的地址。

float到void *是不正确的,因为float值的解释和表示它的实际位是不同的。 例如,如果您这样做:

   float x = 1.0;

它所做的是将32位存储器设置为00 00 80 3f(IEEE单精度中浮点值1.0的实际表示)。 当将浮点数转换为void *时,解释会很模糊。 您是指指向内存中位置1的指针吗? 还是您的指针指向内存中的3f800000位置(假定为小端)?

当然,如果您确定要在两种情况中的哪一种情况下,总有办法解决该问题。 例如:

  void* u = (void*)((int)x);        // first case
  void* u = (void*)(((unsigned short*)(&x))[0] | (((unsigned int)((unsigned short*)(&x))[1]) << 16)); // second case

指针通常在计算机内部以整数表示。 C允许您在指针类型和整数类型之间来回转换。 (指针值可以转换为足以容纳它的整数,然后返回。)

使用void*将非常规的整数值保存。 语言不能保证它能正常工作,但是如果您想草率地将自己限制在Intel和其他常见的平台上,那基本上就可以解决。

实际上,您正在执行的操作是使用void*作为通用容器,但是机器将许多字节用于指针。 这在32位和64位计算机之间有所不同。 因此,将long long转换为void*将在32位平台上丢失位。

对于浮点数, (void*) 10.5f的含义不明确。 是否要将10.5舍入为整数,然后将其转换为无意义的指针? 不,您希望将FPU使用的位模式放置在无意义的指针中。 这可以通过指定float f = 10.5f; void *vp = * (uint32_t*) &f;来完成float f = 10.5f; void *vp = * (uint32_t*) &f; float f = 10.5f; void *vp = * (uint32_t*) &f; ,但请注意,这只是胡说八道:指针不是位的通用存储。

顺便说一下,最好的通用存储是char数组。 语言标准保证可以通过char*操作内存。 但是您必须注意数据对齐要求。

标准752一个整数可以转换为任何指针类型 什么也没说指针浮点转换。

这个问题基于一个错误的前提,即void *在某种程度上是 C 或 C++ 中的“通用”或“包罗万象”类型。它不是。 它是一个通用的object 指针类型,这意味着它可以安全地存储指向任何类型数据的指针,但它本身不能包含任何类型的数据。

您可以使用void *指针通过分配足够的 memory 来容纳任何给定类型的 object,然后使用void *指针指向它来一般地操作任何类型的数据。 在某些情况下,您还可以使用联合,它当然被设计为能够包含多种类型的对象。

现在,因为指针可以被认为是整数(实际上,在传统寻址架构上,通常是整数),在某些圈子中,将 integer 填充到指针中是可能的并且很流行。 一些库 API 甚至记录并支持这种用法——一个著名的例子是 X Windows。

指针和整数之间的转换是实现定义的,现在通常会发出警告,因此通常需要显式转换,与其说是强制转换,不如说是简单地消除警告。 例如,下面的两个代码片段都打印77 ,但第一个可能会引起编译器警告。

/* fragment 1: */
int i = 77;
void *p = i;
int j = p;
printf("%d\n", j);

/* fragment 2: */
int i = 77;
void *p = (void *)(uintptr_t)i;
int j = (int)p;
printf("%d\n", j);

在这两种情况下,我们都没有真正将void *指针p用作指针:我们只是将它用作某些位的容器。 这依赖于这样一个事实,即在常规寻址的体系结构上,指针/整数转换的实现定义行为是显而易见的,对于汇编语言程序员或老派 C 程序员来说,这看起来不像是“转换”。 如果您可以将int填充到指针中,那么如果您也可以填充其他整数类型(例如bool )也就不足为奇了。

但是尝试将浮点值填充到指针中呢? 这问题要大得多。 将 integer 值填充到指针中,虽然是实现定义的,但如果您正在进行裸机编程,则非常有意义:您正在获取 integer 的数值,并将其用作 memory 地址。 但是尝试将浮点值填充到指针中意味着什么?

C 标准甚至 label 都没有“未定义”,这毫无意义。 它毫无意义,典型的编译器甚至不会尝试它。 如果你仔细想想,它应该做什么甚至都不明显。 您想使用数值或位模式作为尝试填充到指针中的东西吗? 填充数值更接近于浮点到整数转换的工作方式,但您会丢失小数部分。 使用位模式可能是您想要的,但是访问浮点值的位模式从来都不是 C 变得容易的事情,因为几代程序员都尝试过类似的事情

uint32_t hexval = (uint32_t)3.0;

发现了。

然而,如果您被绑定并决定将浮点值存储在void *指针中,您可能可以使用足够的强力强制转换来完成它,尽管结果可能是未定义的和机器相关的。 (也就是说,我认为这里存在严格的别名冲突,如果指针大于浮点数,当然它们在 64 位架构上,我认为这可能仅在架构为小端时才有效。)

float f = 77.75;
void *p = (void *)(uintptr_t)*(uint32_t *)&f;
float f2 = *(float *)&p;
printf("%f\n", f2);

dmr 帮助我,这实际上在我的机器上打印了77.75

考虑到你们中的任何人都希望将float值作为void *传输,有一个使用类型双关的解决方法。

这是一个例子;

    struct mfloat {
        union {
            float fvalue;
            int ivalue;
        };
    };

    void print_float(void *data)
    {
        struct mfloat mf;

        mf.ivalue = (int)data;
        printf("%.2f\n", mf.fvalue);
    }


    struct mfloat mf;
    mf.fvalue = 1.99f;
    print_float((void *)(mf.ivalue));

我们使用 union 将我们的浮点值(fvalue)转换为整数(ivalue)到 void*,反之亦然

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM