Perl中的unpack "S*"在C中的等效语句是什么?

4

我在阅读一些代码时遇到了一些理解Perl的unpack函数的困难,特别是在使用S*模板时。

$data = "FF";
print "$data - ", unpack("S*", $data), "\n";
# > FF - 17990
  1. 在C语言中,这个与之等价的是什么?
  2. 为什么?

非常感谢您的帮助。


3
运行perldoc -f unpack可以获取有关unpack函数的信息,并指向perldoc -f pack以了解不同格式的解释。另外,需要注意的是,如果你期望的是十六进制值0xFF,那么"FF"并不是它的表示方式。 - Andy Lester
1
啊,阅读有关“pack”的内容正是我所需要的。谢谢! - matthewpalmer
3个回答

3

unpack 'S' 表示将两个字节转化为一个 uint16_t

#include <stdint.h>
const char *data = "\x46\x41";
uint16_t n;
memcpy(&n, data, sizeof(n));  // n = 0x4146 or 0x4641

在执行此操作之前,请不要忘记检查 data 中的字节数!


请注意,它可以根据系统给出两个不同的结果。

在小端系统(例如 x86、x64)上,unpack 'S' 也等同于

uint16_t n = (data[1] << 8) | data[0];  // 0x4146

在大端系统上,unpack 'S'也等同于
uint16_t n = (data[0] << 8) | data[1];  // 0x4641

顺便提一下,你可能会想做以下操作,但由于内存对齐问题,它并不可移植:
uint16_t n = *((const uint16_t *)data);

这个答案中间的位移部分是唯一安全的方法。最好不要做像顶部或底部位的傻瓜转换部分。 - LeoNerd
memcpy本身没问题,但现在内存中有一些随机的字节位模式,而编译器却期望一个有效的uint16_t。你怎么知道这个位模式作为该类型时是有效的呢? 当你让编译器通过数值位移操作构造uint16_t时,就不会有这种危险,因为它知道那时它将是有效的。 - LeoNerd
@ikagami - 可能是各种类型。 C语言不要求所有可能的字节位模式都是有效的整数表示形式。 它们只是在Linux x86_64 gcc上偶然如此,但这是一个特定于平台的保证。 - LeoNerd
在 Perl 支持的平台上,这可能是正确的。但是 C 本身可以支持一些非常奇怪的地方,这些地方可能无法有效地保持您只需复制位并使其正常工作。 - LeoNerd
@LeoNerd,“unpack 'S'”使用uint16_t本机表示。因此,您实际上是在争论memcpy是唯一正确的解决方案。 - ikegami
@LeoNerd,我对你声称存在一种使用非补码或二进制补码的系统表示方式表示怀疑。这是你第四次机会来证明你所说的其他方法的存在性。 - ikegami

3

您在C中的代码(大致)如下:

const char *data = "FA";
unsigned short s;
memcpy( &s, data, strlen(data) );
printf("%s = %d\n", data, s);

这仅处理了两个字符的情况,而unpack('S*',...)将返回与其输入对应的短整数列表。

为什么?pack和unpack的主要动机之一是使二进制数据与C结构更轻松地进行交换。

perlpacktut 是一个很好的起点。


我们同时发布了答案:)。感谢您提供的C语言示例,这将非常有帮助。我会将您的答案标记为被采纳的答案,因为它更直接地回答了问题。 - matthewpalmer
我同意,也许在最后一次编辑中应该将其删除,但是我还是保留了它,因为答案已经接受了那段代码。 - Ben Grimm

1
我是一个帮助翻译文本的助手。以下是需要翻译的内容:

我在回答自己的问题,所以可能有些地方不正确,但我会把这个留在这里供将来需要的人参考。

首先,让我们将我的例子改为

$data = "FA";
print "$data - ", unpack("S*", $data), "\n";
# > FA - 16710

由于拥有“FF”并没有什么特别的作用。

问题是:我们是如何从“FA”到16710的?

首先,字符“F”被转换为其ASCII值-70。以二进制表示为0100 0110(请注意,我填充了一个前导零,以便清楚地表示它是一个完整的字节)。

然后,我们需要“A”的ASCII值-65。以二进制表示为0100 0001

因此,我们有F对应于0100 0110A对应于0100 0001

然后我们将这两个二进制值粘在一起,除了我们将A放在第一位:

0100 0001 0100 0110

0100 0001 0100 0110转换为十进制得到16,710

注意:我认为在不同的计算机上,将字节拼合在一起的顺序可能会有所不同,因此虽然这里的原理应该适用于任何地方,但数字可能会有所不同。

字节顺序很重要——“S”是按照系统的字节顺序无符号短整型。如果你需要大端或小端,分别使用“n”或“v”。 - Ben Grimm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接