【问题标题】:How to handle member padding in C struct when reading cffi.buffer with numpy.frombuffer?使用 numpy.frombuffer 读取 cffi.buffer 时如何处理 C 结构中的成员填充?
【发布时间】:2018-07-03 13:43:25
【问题描述】:

我必须读取从 dll 返回的 C 结构数组并将其转换为 Numpy 数组。代码使用 Python 的 cffi 模块。

到目前为止,代码可以工作,但我不知道如何处理 np.frombuffer 抱怨的结构中的成员填充:

ValueError: 缓冲区大小必须是元素大小的倍数

这是我的代码:

from cffi import FFI
import numpy as np

s = '''
    typedef struct
    {
        int a;
        int b;
        float c;
        double d;
    } mystruct;
    '''

ffi = FFI()
ffi.cdef(s)

res = []

#create array and fill with dummy data
for k in range(2):

    m = ffi.new("mystruct *")

    m.a = k
    m.b = k + 1
    m.c = k + 2.0
    m.d = k + 3.0

res.append(m[0])

m_arr = ffi.new("mystruct[]", res)

print(m_arr)

# dtype for structured array in Numpy
dt = [('a', 'i4'),
      ('b', 'i4'),
      ('c', 'f4'),
      ('d', 'f8')]

# member size, 20 bytes
print('size, manually', 4 + 4 + 4 + 8)

# total size of struct, 24 bytes
print('sizeof', ffi.sizeof(m_arr[0]))

#reason is member padding in structs

buf = ffi.buffer(m_arr)
print(buf)

x = np.frombuffer(buf, dtype=dt)
print(x)

任何想法如何以干净的方式处理这个问题?


编辑:

如果我在应该发生填充的 dtype 中添加一个额外的数字,它似乎可以工作:

dt = [('a', 'i4'),
      ('b', 'i4'),
      ('c', 'f4'),
      ('pad', 'f4'),
      ('d', 'f8')]

为什么填充发生在那里? (Win7,64 位,Python 3.4 64 位)。

但这不是最好的方法。真正的代码要复杂得多,动态得多,所以应该可以以某种方式处理它,对吧?

【问题讨论】:

  • 我发现“一般来说,结构实例将具有其最宽标量成员的对齐方式。” (catb.org/esr/structure-packing/#_structure_reordering) 但后来他们提到可以重新排序结构。但是,如果每个成员都占用相同的空间,为什么会这样呢?
  • 最有可能的是,正如您自己提到的,C 结构被填充。对于这实际上意味着什么,请查看this post。因此,当您定义编辑中提到的 dt 时,您可以通过自己插入“间隙”来对齐填充结构。 DLL 的程序员需要打包结构,以便您使用原始 dt 定义。
  • 您的成员 d 似乎是一个 64 位浮点数(可能是 C double)。您在编辑中呈现的布局可确保当整个结构同样对齐时,该成员在 8 字节边界上对齐,并且还确保该类型的相邻数组元素都可以是 8 字节对齐。
  • 但是,如果像通常建议的那样,将最大的成员放在第一位,那么仍然会有一个尾随填充。如果我明白了这个概念,那么如果排序顺序是double, int, int, float,那么仍然有四个字节可以将间隙填充到 double 的倍数。对吗?
  • 是的,乔,这是正确的。重新排序元素可能避免它们之间的填充,但如果编译器正在布局结构以确保double 可以在 8 字节边界上对齐,那么它必须要求整个结构具有8 字节(至少)对齐要求。并且类型表示的大小必须始终是其对齐要求的倍数,否则您无法形成该类型的数组。

标签: python c numpy struct python-cffi


【解决方案1】:

除了 cmets 中给出的其他答案外,您还可以强制 cffi 打包其结构(即不插入任何填充,类似于使用特定 C 编译器扩展可以执行的操作):

ffi.cdef("typedef struct { char a; int b; } foo_t;", packed=True)

【讨论】:

    【解决方案2】:

    可能最方便的方法是在numpy dtype constructor 中使用关键字align=True。这将自动进行填充。

    dt = [('a', 'i4'),
          ('b', 'i4'),
          ('c', 'f4'),
          ('d', 'f8')]
    
    dt_obj = np.dtype(dt, align=True)
    x = np.frombuffer(buf, dtype=dt_obj)
    

    (另请参阅结构化数组上的 Numpy doc

    【讨论】:

      猜你喜欢
      • 2019-03-27
      • 2011-11-05
      • 2020-10-09
      • 1970-01-01
      • 2015-04-25
      • 2018-02-11
      • 2016-09-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多