5
say pack "A*", "asdf";           # Prints "asdf"
say pack "s", 0x41 * 256 + 0x42; # Prints "BA" (0x41 = 'A', 0x42 = 'B')

第一行是有道理的:你正在获取一个 ASCII 编码的字符串,将它作为一个 ASCII 字符串打包成一个字符串。在第二行中,由于我的机器上短整数的小字节序,打包形式是“\x42\x41”。

但是,我无法摆脱这样一种感觉,即我应该能够将第二行中的打包字符串视为一个数字,因为(我假设)Perl 就是这样存储数字的,作为小端字节序。有没有办法在不打开包装的情况下这样做?我试图为 pack() 返回的东西获得正确的心理模型。

例如,在 C 中,我可以这样做:

#include <stdio.h>

int main(void) {
    char c[2];
    short * x = c;
    c[0] = 0x42;
    c[1] = 0x41;

    printf("%d\n", *x); // Prints 16706 == 0x41 * 256 + 0x42
    return 0;
}
4

3 回答 3

4

如果您真的对 Perl 如何在内部存储数据感兴趣,我会推荐PerlGuts Illustrated。但通常情况下,您不必关心这些东西,因为 Perl 不让您访问这些低级细节。仅当您使用 C 编写 XS 扩展时,这些内部结构才重要。

如果您想将一个两字节字符串“转换”为 C short,您可以使用如下unpack函数:

$ perl -le 'print unpack("s", "BA")'
16706
于 2013-10-17T10:41:05.833 回答
3

但是,我无法摆脱这种感觉,我应该能够将第二行中的打包字符串视为一个数字,

您需要先将其解压缩。

  • 为了能够在 C 中将其用作数字,您需要

    char* packed = "\x42\x41";
    int16_t int16;
    memcpy(&int16, packed, sizeof(int16_t));
    
  • 为了能够在 Perl 中将其用作数字,您需要

    my $packed = "\x42\x41";
    my $num = unpack('s', $packed);
    

    这基本上是

    use Inline C => <<'__EOI__';
    
       SV* unpack_s(SV* sv) {
          STRLEN len;
          char* buf;
          int16_t int16;
    
          SvGETMAGIC(sv);
          buf = SvPVbyte(sv, len);
          if (len != sizeof(int16_t))
             croak("usage");
    
          Copy(buf, &int16, 1, int16_t);
          return newSViv(int16);
       }
    
    __EOI__
    
    my $packed = "\x42\x41";
    my $num = unpack_s($packed);
    

因为这就是(我假设)perl 将数字存储为 little-endian 字节序列的方式。

Perl 将数字存储在标量的以下三个字段之一中:

  • IV,一个有符号大小的整数perl -V:ivsize(以字节为单位)。
  • UV, 大小的无符号整数perl -V:uvsize(以字节为单位)。(ivsize=uvsize)
  • NV,一个浮点数的大小perl -V:nvsize(以字节为单位)。

在所有情况下,都使用本地字节序。

我试图为 pack() 返回的东西获得正确的心理模型。

pack用于构造“二进制数据”以与外部 API 交互。

于 2013-10-17T16:20:57.960 回答
2

我认为pack是一个序列化函数。它将 Perl 值作为输入,并输出一个序列化的形式。输出序列化形式恰好是 Perl 字节串这一事实更多的是实现细节而不是核心功能。

因此,您真正希望对结果字符串做的所有事情就是将其提供给解包,尽管序列化形式很方便让它在进程、主机、行星周围移动。

如果您有兴趣将其序列化为数字,请考虑使用vec

say vec "BA", 0, 16;  # prints 16961

要仔细查看字符串的内部表示,请查看Devel::Peek,尽管对于纯 ASCII 字符串,您不会看到任何令人惊讶的东西。

use Devel::Peek;
Dump "BA";

SV = PV(0xb42f80) at 0xb56300
  REFCNT = 1
  FLAGS = (POK,READONLY,pPOK)
  PV = 0xb60cc0 "BA"\0
  CUR = 2
  LEN = 16
于 2013-10-17T07:07:35.343 回答