6

我想将使用 64 位整数的 C 函数移植到 Perl 代码。为此,我使用 Perl XS。

我的问题是 Perl XS 类型中没有 64 位整数(只有 U32、U16 和 U8)。

那么,在 Perl XS 代码中使用 64 位整数的最佳方法是什么?

这是我想要做的代码示例:

uint64_t
foo(integer)
   uint64_t integer

   CODE:
      RETVAL = foo(integer);

   OUTPUT:
      RETVAL

foo() 具有 C 原型:

uint64_t foo(uint64_t);

我在 perlxs 文档和 stackoverflow.com 中没有发现任何有用的东西。

4

3 回答 3

6

Perl 的无符号整数的内部类型是UV. 如果你的平台有 64 位 UV,你应该没问题。

使用 32 位 UV(32 位操作系统和 Perl 编译时没有使用 use64bitint),您可以将大整数与浮点数(NV通常是 a double)相互转换。但由于 IEEE 双精度数只有 53 位尾数,这将导致大数的精度损失。

uint64_t integer;

// Convert uint64_t to SV.

if (integer <= UV_MAX) {
    RETVAL = newSVuv((UV)integer);
}
else {
    RETVAL = newSVnv((NV)integer);
}

// Convert SV to uint64_t with range checks.

if (SvUOK(sv)) {
    integer = (uint64_t)SvUV(sv);
}
else if (SvIOK(sv)) {
    IV iv = SvIV(sv);
    if (iv < 0) croak(...);
    integer = (uint64_t)iv;
}
else if (SvNOK(sv)) {
    NV nv = SvNV(sv);
    if (nv < 0.0 || nv >= 18446744073709551616.0) croak(...);
    integer = (uint64_t)nv;
}
else {
    // Parse a uint64_t from the string value, or whatever.
}

如果您无法忍受精度损失并希望支持 32 位 UV,您可以使用以下的C APIMath::Int64另请参阅Module::CAPIMaker):

#define MATH_INT64_NATIVE_IF_AVAILABLE
#include "perl_math_int64.h"

MODULE = ...

BOOT:
    PERL_MATH_INT64_LOAD_OR_CROAK;

SV*
foo(args...)
CODE:
    uint64_t integer = ...
    RETVAL = newSVu64(integer);
OUTPUT:
    RETVAL

使用类似的类型图

TYPEMAP
uint64_t T_UINT64

INPUT
T_UINT64
    $var = SvU64($arg)

OUTPUT
T_UINT64
    $arg = newSVu64($var);

你可以uint64_t直接在你的 XS 中使用:

uint64_t
foo(arg)
    uint64_t arg
于 2015-07-19T10:09:23.983 回答
5

uint64_t类型未在默认中定义typemap,它随 Perl 发行版一起提供。根据perlxstypemap

xsubpp在更实际的术语中,类型映射是编译器用来将 C 函数参数和值映射到 Perl 值的代码片段的集合。

您可以定义自己的typemap(在与文件相同的目录中.xs)。它可能看起来像:

TYPEMAP
unsigned long long T_U_LONG_LONG
uint64_t T_U_LONG_LONG              # equivalent to typedef unsigned long long uint64_t;

INPUT
T_U_LONG_LONG
    $var = (unsigned long long)SvUV($arg)

OUTPUT
T_U_LONG_LONG
    sv_setuv($arg, (UV)$var);

我不确定这些映射,尤其是SvUV部分映射,UV因此您需要在任何真实代码中仔细测试它。我怀疑它们只是“普通”整数,因此uint64_t仅在内部使用时才具有完整功能,即在函数定义范围内。

请注意,根据 C 标准(自 C99 起),unsigned long long类型至少为64 位宽,但在我知道的每个实现中它都是 64 位的。

test.xs

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"

MODULE = test       PACKAGE = test

unsigned int
u64_mul_high(a, b)
    unsigned int a
    unsigned int b
CODE:
    RETVAL = ((uint64_t) a * b) >> 32;
OUTPUT:
    RETVAL

test.pl定义为:

#!/usr/bin/perl
use ExtUtils::testlib;
use test;

print test::u64_mul_high(2147483647, 1000), "\n";

您会得到以下结果:

499 (0001 1111 0011)

这是32-bit x 32-bit乘法的较高 32 位肢体,结果为 64 位整数。

我在 32 位 GNU/Linuxsizeof(long) = 4和 Perl 5.10 上检查了这一点。

于 2015-07-18T12:46:08.823 回答
4
$ perl -MConfig -le 'print $Config{use64bitint}'

将显示您是否perl被编译为使用 64 位整数。如果是这样,IV 是 64 位的。

另请参阅perldoc perlguts

什么是“IV”?

Perl 使用一种特殊的 typedef IV,它是一种简单的有符号整数类型,保证足够大以容纳指针(以及整数)。此外,还有 UV,它只是一个无符号 IV。

Perl 还使用两个特殊的 typedef,I32 和 I16,它们总是分别至少 32 位和 16 位长。(同样,还有 U32 和 U16。)它们通常正好是 32 位和 16 位长,但在 Cray 上它们都是 64 位。

另请参阅Math::Int64

该模块向 Perl 添加了对 64 位整数(有符号和无符号整数)的支持。

...

如果可用,回退到本机 64 位支持

如果您的程序中使用了 lexical pragmaMath::Int64::native_if_available并且使用的 perl 版本具有对 64 位整数的本机支持,则从创建 64 位整数的模块导入的函数(即uint64int64string_to_int64native_to_int64等)将返回常规的 perl 标量。

于 2015-07-18T12:11:07.307 回答