【问题标题】:Porting C endianness & pointers black magic to Swift将 C 字节顺序和指针黑魔法移植到 Swift
【发布时间】:2015-06-20 18:39:49
【问题描述】:

我正在尝试翻译这个 sn-p :

ntohs(*(UInt16*)VALUE) / 4.0

还有其他一些看起来相似的,从 C 到 Swift。 问题是,我对 Swift 知之甚少,我就是不明白这个 sn-p 是做什么的......这就是我所知道的:

  • ntohs 将字节序交换为主机字节序
  • VALUEchar[32]
  • 我刚刚发现 Swift : (UInt(data.0) << 6) + (UInt(data.1) >> 2) 做同样的事情。能解释一下吗?
  • 我愿意返回 Swift Uint (UInt64)

谢谢!

【问题讨论】:

  • char 指针(或数组)转换为UInt16* 是相当不安全的,并且可能导致未定义的行为。
  • 这不是指针黑魔法。这只是重新诠释演员阵容。你应该看到一些真正的指针魔术......
  • @cmaster 这是未定义的行为,所以它不是好的魔法;-)
  • @zwol 我同意它没有以最好的方式编写。但是,如果您不能保证所使用的 char 缓冲区的对齐,则它只是未定义的行为。如果该缓冲区是使用malloc() 分配的,则它已充分对齐并且不会导致UB。但是,如果缓冲区在堆栈上,情况就不同了:编译器不需要以任何方式对齐 char 数组,这确实会导致理论上最糟糕的 UB。在实践中,UB 可能不会表现出来:缓冲区很有可能最终对齐,并且大多数 CPU 可以处理 16 位未对齐的读取。
  • @cmaster 根据基于类型的别名规则,这是未定义的行为,无论VALUE 分配在何处或如何对齐。它不会是 UB 的唯一方法是,如果 VALUE 本身已经 转换为指向 (array-of-)UInt16 的指针,并且指向的数据在两者之间没有被修改两个转换。但如果是这样的话,就没有必要打电话给ntohs了。

标签: c swift pointers endianness


【解决方案1】:
  1. VALUE 是一个指向 32 字节 (char[32]) 的指针。
  2. 指针被强制转换为UInt16 指针。这意味着VALUE 的前两个字节被解释为UInt16(2 个字节)。
  3. * 将取消引用指针。我们得到VALUE 的两个字节作为一个16 位数字。但是它具有净字节序(净字节顺序),因此我们不能对其进行整数运算。
  4. 我们现在将字节顺序交换到主机,我们得到一个普通整数。
  5. 我们将整数除以4.0

要在 Swift 中做同样的事情,我们只需将字节值组合成一个整数。

let host = (UInt(data.0) << 8) | UInt(data.1)

请注意,要除以4.0,您必须将整数转换为Float

【讨论】:

  • 编辑:我们同时写作。您的解决方案看起来比我的更好、更安全(使用 UnsafePointer),所以我很乐意接受您的解决方案。也感谢您的精彩解释!
  • 不是指向 32 字节的指针。指向预期会找到 Uint16 的区域的指针。这是网络顺序中 16 位(2 个字节)的无符号整数。它的值除以 4.0。
  • 如果 data.0 包含一个 16 位整数的 MSB 和 data.1 LSB,那么 (UInt(data.0) &lt;&lt; 8) | UInt(data.1) 已经是正确的数字并且必须是字节-订单已转换。
  • @MartinR 你是绝对正确的。我不得不累了。
【解决方案2】:

您引用的 C 在技术上是不正确的,尽管它会按照大多数生产 C 编译器的预期进行编译。¹实现相同效果的更好方法,也应该更容易翻译成 Swift,是

unsigned int val = ((((unsigned int)VALUE[0]) << 8) |  // ² ³
                    (((unsigned int)VALUE[1]) << 0));  // ⁴

double scaledval = ((double)val) / 4.0;                // ⁵

第一个语句读取VALUE的前两个字节,将它们解释为网络字节顺序的16位无符号数,并将它们转换为主机字节顺序(无论这些字节顺序是否不同)。第二条语句将数字转换为double 并对其进行缩放。

¹ 具体来说,*(UInt16*)VALUE 会引发未定义的行为,因为它违反了基于类型的别名规则,即不对称:具有字符类型的指针可用于访问具有任何类型的对象,但任何其他类型的指针都可能用于访问(数组)字符类型的对象。

² 在 C 中,必须在此处强制转换为 unsigned int,以使后续的移位和或运算以无符号类型发生。如果您转换为看起来更合适的uint16_t,那么“通常的算术转换”将在进行左移之前将其转换为带符号的int。这将在int 仅 16 位宽的系统上引发未定义的行为(不允许移入符号位)。对于小范围类型的算术运算,Swift 几乎肯定有完全不同的规则。您可能需要在轮班之前转换为 something,但我无法告诉您是什么。

⁴ 左移零位无效;它仅包含在并行结构中。

⁵ 在 C 中不需要在除法运算之前显式转换为 double,但在 Swift 中是这样,所以我在这里这样写。

【讨论】:

    【解决方案3】:

    看起来代码正在使用单字节值[0]。然后取消引用,这应该从低内存地址检索一个数字,从 1 到 127(可能是 255)。 然后将任何数字除以 4。

    我真的不敢相信我的解释是正确的,也无法确认因为我没有笔记本电脑。我真的认为您的代码中可能存在拼写错误,因为这不是一件好事。便携,可重复使用

    我必须强调,字符串不会转换为数字。然后使用哪个

    【讨论】:

    • UInt16 是 2 个字节,而不是 1 个字节。没有字符串。
    • @Sulthan 是对的。此外...肯定有 no 错字。它是生产代码,经过大量开源项目的实战测试和使用,可能还有其他一些付费项目;-)
    猜你喜欢
    • 2013-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多