【问题标题】:How does Perl store integers in-memory?Perl 如何在内存中存储整数?
【发布时间】:2013-10-17 06:49:32
【问题描述】:
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;
}

【问题讨论】:

  • 您的程序对内存对齐限制做出了错误的假设(或者更确切地说,它假设缺乏这些限制)。您的程序将在某些机器上崩溃。请参阅我的答案以了解在 C 中进行解包的正确方法
  • 您的程序对short 的大小做出了错误的假设。你应该使用int16_t

标签: perl


【解决方案1】:

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

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

$ perl -le 'print unpack("s", "BA")'
16706

【讨论】:

    【解决方案2】:

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

    你需要先解压。

    • 为了能够在 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 接口。

    【讨论】:

    • 已更新/添加到我的答案中。
    【解决方案3】:

    我将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
    

    【讨论】:

    • 如果 pack() 返回一个字符串的事实是一个实现细节,那么 print() 那个字符串有多安全?我应该改用 syswrite() 吗?除了运行 binmode() 之外,我还应该将任何标志传递给 open() 吗?
    • 这是一个细节,“它必须是 Perl 可以管理的东西,所以字节串是最有意义的”。 AFAICT,作为一个字节串,无论binmode如何,都可以安全地打印到STDOUT,尽管将它与依赖于编码的数据混合可能是个坏主意。您可以安全地syswrite 它,但这不是必需的。
    • 在 Windows 上可能不安全,因为如果是文本文件,\r 会在 \n 之前添加。
    • 我认为这更像是一个say 问题而不是pack 问题,虽然:-)
    • @JB., No print(和syswrite,这里没有理由使用)会有同样的问题。
    猜你喜欢
    • 2013-09-02
    • 1970-01-01
    • 1970-01-01
    • 2011-01-16
    • 2012-10-25
    • 2010-12-22
    • 1970-01-01
    • 2015-09-19
    相关资源
    最近更新 更多