[英]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.