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

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

本文关键字:void float 转换 int 我可以 BOOL 为什么      更新时间:2023-10-16

void*是C及其派生语言的一个有用的特性。例如,可以使用void*在c++类中存储objective-C对象指针。

我最近在一个类型转换框架上工作,由于时间限制有点懒-所以我使用void*…这就是这个问题出现的原因:

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

BOOL不是c++类型。它可能在某处定义了类型定义,在这些情况下,它和int是一样的。例如,Windows在Windef.h:

中有此设置。
    typedef int                 BOOL;

所以你的问题减少到,为什么你可以将int类型转换为void*,但不能浮动到void*?

int to void*是可以的,但通常不推荐(有些编译器会发出警告),因为它们在表示上本质上是相同的。指针基本上是一个指向内存地址的整数。

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

   float x = 1.0;

的作用是将32位内存设置为00 00 80 3f(浮点值1.0在IEEE单精度中的实际表示)。当将float类型强制转换为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*保存整数值。语言不能保证它能工作,但如果你想马虎一点,把自己限制在英特尔和其他常见的平台上,它基本上可以勉强通过。

实际上,你正在做的是使用void*作为一个通用容器,无论机器使用多少字节的指针。这在32位和64位机器之间是不同的。因此,将long long转换为void*将在32位平台上丢失位。

对于浮点数,(void*) 10.5f的意图是不明确的。您是否希望将10.5舍入为整数,然后将其转换为无意义指针?不,您希望将FPU使用的位模式放入无意义指针中。这可以通过分配float f = 10.5f; void *vp = * (uint32_t*) &f;来实现,但要注意,这只是无稽之谈:指针不是位的通用存储。

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

标准规定752整数可以转换为任何指针类型。没有提到指针-浮点数转换。

考虑到您想要将float值转换为void *,有一个使用类型双关语的解决方案。

下面是一个例子;

    struct mfloat {
        union {
            float fvalue;
            int ivalue;
        };
    };
    void print_float(void *data)
    {
        struct mfloat mf;
        mf.ivalue = (int)data;
        printf("%.2fn", mf.fvalue);
    }

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

我们已经使用联合将浮点值(fvalue)转换为整数(ivalue)为void*,反之亦然

这个问题是基于一个错误的前提,即void *在某种程度上是"通用的";或";catch-all"输入C或c++。事实并非如此。它是一个泛型对象指针类型,这意味着它可以安全地存储指向任何类型数据的指针,但它本身不能包含任何类型的数据。

您可以使用void *指针通过分配足够的内存来保存任何给定类型的对象,然后使用void *指针指向该对象,从而通用地操作任何类型的数据。在某些情况下,您也可以使用联合,它当然是为了能够包含多种类型的对象而设计的。

现在,因为指针可以被认为是整数(实际上,在传统寻址的体系结构中,通常是整数),所以有可能并且在一些圈子中流行将整数塞进指针。一些库API甚至记录并支持这种用法——一个值得注意的例子是X Windows。

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

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

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

但是如果把浮点值塞进指针中呢?这就更成问题了。如果您正在进行裸机编程,那么将整数值填充到指针中(虽然是实现定义的)是非常有意义的:您正在获取整数值,并将其用作内存地址。但是,尝试将浮点值塞进指针意味着什么呢?

它是如此没有意义,以至于C标准甚至没有给它贴上"未定义"的标签。它是如此无意义,以至于一个典型的编译器甚至不会尝试它。如果你仔细想想,它应该做什么都不明显。你是想使用数字值,还是位模式,作为尝试填充到指针中的东西?填充数字值更接近于浮点到整数转换的工作方式,但会丢失小数部分。使用位模式可能是您想要的,但是访问浮点值的位模式从来都不是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("%fn", f2);

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