如何将`Ptr`转换为`NTuple`的元素?

时间:2019-02-26 15:35:31

标签: julia

说我有一个Cchar元组,例如

str = ('f', 'o', 'o', '\0', '\0')

,我想将其转换为更传统的字符串。如果strVector,我可以创建Ptr并用它做各种各样的事情。我尝试了各种方法将str传递给pointerPtrRefunsafe_string的方法,因为它们通常在数组而不是数组上工作,元组。有什么建议吗?

注意:我真正拥有的是一个看起来像C的结构

typedef struct foo {
    char str[FOO_STR_MAX_SZ];
    ...
} foo_t;

其中Clang.jl包装为

struct foo_t
    str :: NTuple{FOO_STR_MAX_SZ, UInt8}
    ...
end

我还玩了NTuple中的Cchar(即Int8)而不是UInt8,并且我尝试使用SVector而不是{{ 1}}。但是我仍然找不到从NTuple字段生成Ptr的方法。我想念什么吗?

2 个答案:

答案 0 :(得分:2)

自从您提出问题以来,我认为将其收集到数组a = collect(x.str)并不是您所期望的答案...

即使ccall(:jl_value_ptr, Ptr{Cvoid}, (Any,), a)不可变,也可以使用a获取a的指针。但是,盲目使用它会产生一些令人困惑的结果:

julia> struct foo_t
         str::NTuple{2, UInt8}
       end

julia> a = foo_t((2, 3))
foo_t((0x02, 0x03))

julia> ccall(:jl_value_ptr, Ptr{Cvoid}, (Any,), a.str)
Ptr{Nothing} @0x00007f4302c4f670

julia> ccall(:jl_value_ptr, Ptr{Cvoid}, (Any,), a.str)
Ptr{Nothing} @0x00007f4302cc47e0

我们从同一个对象获得了两个不同的指针!原因是由于NTuple是不可变的,因此编译器将对其进行许多“优化”,例如,每次使用它时都会对其进行处理。这就是为什么在source code中明确禁止从不可变对象获取指针的原因:

function pointer_from_objref(@nospecialize(x))
    @_inline_meta
    typeof(x).mutable || error("pointer_from_objref cannot be used on immutable objects")
    ccall(:jl_value_ptr, Ptr{Cvoid}, (Any,), x)
end

但是,有几种解决方法。首先,由于表达式a.str复制了元组,因此您可以避免使用此expressoin并直接使用afieldoffset(typeof(a), 1)的地址来计算它的地址。 (1表示strfoo_t的第一个字段)

julia> p = Ptr{UInt8}(ccall(:jl_value_ptr, Ptr{UInt8}, (Any,), a)) + fieldoffset(typeof(a), 1)
Ptr{UInt8} @0x00007f4304901df0

julia> p2 = Ptr{UInt8}(ccall(:jl_value_ptr, Ptr{UInt8}, (Any,), a)) + fieldoffset(typeof(a), 1)
Ptr{UInt8} @0x00007f4304901df0

julia> p === p2
true

julia> unsafe_store!(p, 5)
Ptr{UInt8} @0x00007f4304901df0

julia> a
foo_t((0x05, 0x03))

现在可以使用了。但是,仍然有一些警告:当您尝试将代码包装到一个函数中时,它又变成了错误:

julia> mut!(a) = unsafe_store!(Ptr{UInt8}(ccall(:jl_value_ptr, Ptr{UInt8}, (Any,), a)) + fieldoffset(typeof(a), 1), 8)
mut! (generic function with 1 method)

julia> mut!(a)
Ptr{UInt8} @0x00007f42ec560294

julia> a
foo_t((0x05, 0x03))

a未被更改,因为foo_t本身也是不可变的,并且将被复制到mut!,因此在函数内部所做的更改在外部将不可见。为了解决这个问题,我们需要将a包装在一个可变对象中,以使其在堆中具有稳定的地址。 Base.RefValue可用于此目的:

julia> b = Base.RefValue(a)
Base.RefValue{foo_t}(foo_t((0x05, 0x03)))

julia> mut!(b) = unsafe_store!(Ptr{UInt8}(ccall(:jl_value_ptr, Ptr{UInt8}, (Any,), b)) + fieldoffset(typeof(b), 1) + fieldoffset(typeof(a), 1), 8)
mut! (generic function with 1 method)

julia> mut!(b)
Ptr{UInt8} @0x00007f43057b3820

julia> b
Base.RefValue{foo_t}(foo_t((0x08, 0x03)))

julia> b[]
foo_t((0x08, 0x03))

答案 1 :(得分:1)

如@张实唯所解释,str是一个常量数组,它是堆栈分配的,因此您需要使用指针算术来访问该字段。为此,有一个名为Blobs.jl的软件包。至于可变性,为方便起见,您也可以使用Setfield.jl

顺便说一句,Clang.jl支持通过ctx.options["is_struct_mutable"] = true生成可变结构。