问题描述
我正在尝试在 kernel void
函数内用 Metal Shading Language 编写以下 C 代码:
float f = 2.1;
uint8_t byteArray[sizeof(f)];
for (int a = 0; a < sizeof(f); a++) {
byteArray[a] = ((uint8_t*)&f)[a];
}
该代码应该获取浮点值的字节数组。当我尝试用 Metal Shader Language 编写相同的代码时,出现以下构建时错误:
指针类型必须有明确的地址空间限定符
我知道 Metal 限制了指针的使用,并要求内核函数的参数提供显式地址空间属性,例如 device
、constant
等。我将如何执行类型-用金属着色语言投射?
解决方法
这不是 C++,所以你必须用另一种方式来做。
您不能在 MSL 中正确使用联合或 infoLoading
。
相反,有一个类型为 4 reinterpret_cast
的向量,它是 uint8_t
。
为了做你想做的事,你会写成这样。
uchar4
请参阅 MSL spec,第 2.19 节类型转换和重新解释数据。
至于地址空间限定符:
Metal Shading Language 中的每个指针都有一个地址限定符。它可以是 float f = 2.1;
uchar4 ff = as_type<uchar4>(f);
、device
、constant
、thread
等。请参阅规范中的第 4 章。
这些地址限定符来自内存空间不同的事实。您可以在上面的文档中阅读有关它们的更多信息。
由于 threadgroup
是局部变量,它位于 f
空间中,因此您的 thread
指针将具有 uint8_t
的类型,而不仅仅是 thread uint8_t*
。
所以你可能会这样做:
uint8_t
但我认为 float f = 2.1;
thread uint8_t* ff = (thread uint8_t*)&f;
方法要清晰得多。
不知道具体的 metal
,但在普通 C 中,您想将 f
和 byteArray
放在 union
中
这是一些示例代码:
#include <stdio.h>
#include <stdint.h>
union float_byte {
float f;
uint8_t byteArray[sizeof(float)];
};
union float_byte u;
void
dotest(float f)
{
u.f = f;
printf("%.6f",u.f);
for (int idx = 0; idx < sizeof(u.byteArray); ++idx)
printf(" %2.2X",u.byteArray[idx]);
printf("\n");
}
int
main(void)
{
dotest(2.1);
dotest(7.6328);
return 0;
}
这是程序输出:
2.100000 66 66 06 40
7.632800 E6 3F F4 40
更新:
即使在今天,阅读一个不是最后一个写信的工会成员在技术上仍然是 UB 吗?虽然,这听起来现在已经通过实现定义的行为得到了广泛的支持。许多相关问题之一:stackoverflow.com/questions/2310483/... – yano
不,出于多种原因,它不是UB。
它可能是“实现定义的”行为,但只是因为 CPU/处理器字节序重新。内存中 [32 位] float
的格式,如果我们希望解释 byteArray
但是,AFAICT,这并不影响 OP 的问题,因为重点只是获得一个字节缓冲区 [用于数据的二进制/序列化?]。
如果想要解释数据(例如设计 DIY F.P. S/W 实现),则必须知道 float
的格式和字节序。这 [可能] 是 IEEE 784 格式和处理器字节序。
但是,使用 union
只是得到一个字节指针,没有问题。它甚至不是“实现定义”的行为。
这与以下内容大致相同:
float my_f = f;
uint8_t *byteArray = (uint8_t *) &my_f;
而且,它之所以有效,是因为它必须起作用。
此外,union
[此处使用的] 是一个常见的习语,可以追溯到 1970 年代,因此必须得到支持。
此外,它只是工作[因为它必须按照设计]。
如果我们有:
void
funcA(float f)
{
u.f = f;
}
void
funcB(void)
{
for (int idx = 0; idx < sizeof(u.byteArray); ++idx)
printf(" %2.2X",u.byteArray[idx]);
printf("\n");
}
为了好玩,假设 funcA
和 funcB
位于单独的 .c
文件中。当调用 funcA
时,它会[以可预测的方式]更改 u
的内存。
中间的任何内容都不会改变 u
的布局。
然后,我们调用 funcB
。 byteArray
中的字节布局将是相同/可预测的数据。
这与将 float
作为 二进制 数据写入文件的方式相似:
#include <unistd.h>
int fd;
void
writefloat(float f)
{
float my_f = f;
write(fd,&my_f,sizeof(float));
}
void
writefloat2(float f)
{
write(fd,&f,sizeof(float));
}
void
writefloat3(float f)
{
write(fd,sizeof(f));
}
如果我们使用 uint32_t
而不是 float
,也许这会更容易看出。我们可以做一个字节序测试。 [注意:这是粗略的,并没有考虑像 pdp11/vax 这样的奇怪的字节序]:
#include <stdio.h>
#include <stdint.h>
union uint_byte {
uint32_t i;
uint8_t b[sizeof(uint32_t)];
};
union uint_byte u;
int
main(void)
{
u.i = 0x01020304;
if (u.b[0] == 0x04)
printf("cpu is little-endian\n");
else
printf("cpu is big-endian\n");
return 0;
}
,
在 C++ (20) 中,您有 bit_cast
。
在“注释”部分,您将看到,它只是作为对 memcpy
的调用实现的。
因此,为了避免“未定义行为”并确保安全(除了之前的所有方法,它也是最快的),只需执行以下操作:
memcpy(byteArray,sizeof f); //size of byteArray must be at least: sizeof f