C sizeof(some_structure) 返回的值与 python struct.calcsize(struct_string) 不同

C sizeof(some_structure) returns different value in compare with python struct.calcsize(struct_string)

提问人:Mika 提问时间:7/20/2023 最后编辑:Mika 更新时间:7/21/2023 访问量:120

问:

我有一个 C 结构。

typedef struct
{
    double cycle_time; 
    double cycle_duty; 
    double state; 
    double servo_mode; 
    double motion_mode; 
    double jcond; 
    
    struct
    {
        uint16_t buff_sz; 
        uint16_t buff_fill; 
        uint16_t cmd_cntr; 
        uint16_t res; 
    } wpi;
    double move_des_q[6]; 
    double move_des_qd[6]; 
    double move_des_x[6]; 
    double move_des_xd[6]; 
    double act_q[6]; 
    double act_qd[6]; 
    double act_x[6]; 
    double act_xd[6]; 
    double act_tq[6]; 
    double frict_tq[6]; 
    double ne_tq[6];
    double act_force_e[6];
    double act_force_0[6]; 
    double des_trq[6]; 
    double des_qd[6]; 
    double temp_m[6]; 
    double temp_e[6]; 
    double arm_current;
    double arm_voltage;
    double psu_voltage;
    struct
    {
        uint8_t dig_in_count; 
        uint8_t an_in_count;
        uint8_t dig_in[8];
        uint8_t an_in_curr_mode[4];
        double an_in_value[4];

        uint8_t dig_out_count; //number of bits
        uint8_t an_out_count;
        uint8_t dig_out[8];
        uint8_t an_out_curr_mode[4];
        double an_out_value[4];
    } io;

    struct
    {
        uint32_t jointState;
        float joint_volt;       
        float joint_amp;        
        uint8_t joint_window;
        float joint_des_iq;
                                 
        float joint_des_vel;
    } jointInfo[6];
} some_structure;

运行计算代码后,我发现了 C 结构的大小(1136 字节)(至少我希望它是正确的)。代码如下:

int main()
{
    printf("Size of struct ABC: %lu\n", sizeof(some_structure));
}

但是,在检查 python 结构大小后,我发现了一些差异(python 大小为 1114 字节)。这是 python 代码:

    struct_string = "<6d4H105d14B4d14B4dL2fB2fL2fB2fL2fB2fL2fB2fL2fB2fL2fB2f"
    struct_byte_size = struct.calcsize(struct_string)
    print(struct_byte_size)

是什么导致了这种尺寸变化? 我怎样才能从套接字接收数据并避免这种偏移? 我在创建struct_string时可能会出错吗?

UPD这里是 struct-module 规则表和 calcsize() 描述。

python c python-3.x 套接字 类型

评论

1赞 Ted Lyngmo 7/20/2023
不相关:应该是%lu%zu
3赞 Aconcagua 7/20/2023
我不知道到底做了什么——但我认为它总结了单个成员的规模——如果需要的话,递归地。 相比之下,在 C 语言中,测量结构在内存中占用的总字节数——这可以包括填充字节!例如,您放置了 ,下一个空闲地址是 4 的倍数,但不是 8 的倍数——但在 64 位系统上,随后的双精度需要以 8 的倍数对齐,因此插入了四个填充字节。类似地,在单点后跟一个浮点数后的三个填充字节。struct.calcsizesizeofuint8_tuint8_t
1赞 Jabberwocky 7/20/2023
为什么它应该是相同的大小?不同的语言,可能有不同的对齐/填充等......
5赞 dimich 7/20/2023
如果从 socket 接收数据,切勿将其直接用作 C 结构。可能有不同的填充,不同的字节顺序。使用序列化/反序列化。
1赞 mch 7/20/2023
你在 C-struct 中有填充字节:对于普通系统:在 6 个元素的数组中,前 2 个字节,前 2 个字节,前 3 个字节,所以总共 18 个字节。所以整个结构有 22 个字节。1114 + 22 = 1136。加起来。double an_in_value[4];double an_out_value[4];float joint_des_iq;

答:

3赞 Mark Tolonen 7/20/2023 #1

这种差异是由于 C 编译器添加了填充字节以进行对齐。如果变量位于变量类型大小的倍数的地址,则代码效率更高。当使用 Python 的模块时,除了 native(@) 对齐(默认值)之外,它不使用填充字节。struct

例如,如果通过以下方式启用所有警告,则 Microsoft 编译器会指示填充:cl /Wall test.c

test.c(43): warning C4820: '<unnamed-tag>': '2' bytes padding added after data member 'an_in_curr_mode'
test.c(49): warning C4820: '<unnamed-tag>': '2' bytes padding added after data member 'an_out_curr_mode'
test.c(56): warning C4820: '<unnamed-tag>': '3' bytes padding added after data member 'joint_window'

可以使用 禁用此填充。示例如下:#pragma pack

测试.c

#include <stdio.h>
#include <stdint.h>

//#pragma pack(push, 1)
typedef struct {
    double cycle_time;
    double cycle_duty;
    double state;
    double servo_mode;
    double motion_mode;
    double jcond;
    struct {
        uint16_t buff_sz;
        uint16_t buff_fill;
        uint16_t cmd_cntr;
        uint16_t res;
    } wpi;
    double move_des_q[6];
    double move_des_qd[6];
    double move_des_x[6];
    double move_des_xd[6];
    double act_q[6];
    double act_qd[6];
    double act_x[6];
    double act_xd[6];
    double act_tq[6];
    double frict_tq[6];
    double ne_tq[6];
    double act_force_e[6];
    double act_force_0[6];
    double des_trq[6];
    double des_qd[6];
    double temp_m[6];
    double temp_e[6];
    double arm_current;
    double arm_voltage;
    double psu_voltage;
    struct {
        uint8_t dig_in_count;
        uint8_t an_in_count;
        uint8_t dig_in[8];
        uint8_t an_in_curr_mode[4];  // 2 bytes padding after
        double an_in_value[4];

        uint8_t dig_out_count; //number of bits
        uint8_t an_out_count;
        uint8_t dig_out[8];
        uint8_t an_out_curr_mode[4];  // 2 bytes padding after
        double an_out_value[4];
    } io;
    struct {
        uint32_t jointState;
        float joint_volt;
        float joint_amp;
        uint8_t joint_window;  // 3 bytes padding after
        float joint_des_iq;

        float joint_des_vel;
    } jointInfo[6];  // 3 * 6 = 18 bytes total padding
} some_structure;  // 18 + 2 + 2 = 22 extra bytes due to padding
//#pragma pack(pop)

int main(void) {
    printf("Size of struct ABC: %zu\n", sizeof(some_structure));
}

使用本机填充的输出:

1136

语句未注释的输出:#pragma pack

1114

从 Python 代码中删除 以使用本机填充,或考虑填充:<

import struct

struct_string = '6d4H105d14B4d14B4dL2fB2fL2fB2fL2fB2fL2fB2fL2fB2fL2fB2f'
print(struct.calcsize(struct_string)) # native alignment
print(struct.calcsize('<' + struct_string)) # no alignment

struct_string = '<6d4H105d14B2x4d14B2x4dL2fB3x2fL2fB3x2fL2fB3x2fL2fB3x2fL2fB3x2fL2fB3x2f'
print(struct.calcsize(struct_string)) # explicit padding

输出:

1136
1114
1136

解压缩结构将返回一个 187 元组。可能很难找出特定值的正确偏移量。

请考虑使用该模块,您可以在其中指定命名字段和嵌套结构,以便更自然地查找特定值。例:ctypes

import ctypes as ct

PACK = 1  # use 8 for native or remove the _pack_ lines

class wpi(ct.Structure):
    _pack_ = PACK
    _fields_ = (('buff_sz', ct.c_uint16),
                ('buff_fill', ct.c_uint16),
                ('cmd_cntr', ct.c_uint16),
                ('res', ct.c_uint16))

class io(ct.Structure):
    _pack_ = PACK
    _fields_ = (('dig_in_count', ct.c_uint8),
                ('an_in_count', ct.c_uint8),
                ('dig_in', ct.c_uint8 * 8),
                ('an_in_curr_mode', ct.c_uint8 * 4),
                ('an_in_value', ct.c_double * 4),
                ('dig_out_count', ct.c_uint8),
                ('an_out_count', ct.c_uint8),
                ('dig_out', ct.c_uint8 * 8),
                ('an_out_curr_mode', ct.c_uint8 * 4),
                ('an_out_value', ct.c_double * 4))

class jointInfo(ct.Structure):
    _pack_ = PACK
    _fields_ = (('jointState', ct.c_uint32),
                ('joint_volt', ct.c_float),
                ('joint_amp', ct.c_float),
                ('joint_window', ct.c_uint8),
                ('joint_des_iq', ct.c_float),
                ('joint_des_vel', ct.c_float))

class some_structure(ct.Structure):
    _pack_ = PACK
    _fields_ = (('cycle_time', ct.c_double),
                ('cycle_duty', ct.c_double),
                ('state', ct.c_double),
                ('servo_mode', ct.c_double),
                ('motion_mode', ct.c_double),
                ('jcond', ct.c_double),
                ('wpi', wpi),
                ('move_des_q', ct.c_double * 6),
                ('move_des_qd', ct.c_double * 6),
                ('move_des_x', ct.c_double * 6),
                ('move_des_xd', ct.c_double * 6),
                ('act_q', ct.c_double * 6),
                ('act_qd', ct.c_double * 6),
                ('act_x', ct.c_double * 6),
                ('act_xd', ct.c_double * 6),
                ('act_tq', ct.c_double * 6),
                ('frict_tq', ct.c_double * 6),
                ('ne_tq', ct.c_double * 6),
                ('act_force_e', ct.c_double * 6),
                ('act_force_0', ct.c_double * 6),
                ('des_trq', ct.c_double * 6),
                ('des_qd', ct.c_double * 6),
                ('temp_m', ct.c_double * 6),
                ('temp_e', ct.c_double * 6),
                ('arm_current', ct.c_double),
                ('arm_voltage', ct.c_double),
                ('psu_voltage', ct.c_double),
                ('io', io),
                ('jointInfo', jointInfo * 6))

print(ct.sizeof(some_structure))
# Construct the structure from some byte data
s = some_structure.from_buffer_copy(b'\x01' * ct.sizeof(some_structure))
print(s.jointInfo[2].joint_window)  # example to view a value

输出 (PACK=1):

1114
1

输出 (PACK=8):

1136
1

评论

0赞 Mika 7/21/2023
谢谢你的回答非常好。我现在正在尝试使用它。但我这里有一些问题。在我的 Ubuntu 23.04 系统中,使用第 11 代英特尔(R)酷睿™ i5-1135G7 2.40GHz,我有以下输出(用于比较 3 个计算大小的变体的代码部分):和 .在谈论 Ctypes 时,所有输出都与您的输出相同。native alignment 1180no alignment 1114explicit padding 1136
0赞 Mika 7/21/2023
此外,为了澄清这个话题(对我来说),我们应该在哪里以及为什么要使用填充?我非常感谢我能获得的关于这个主题的每一个值得信赖的信息线索。
0赞 Mark Tolonen 7/21/2023
@Mika 我的第二句话回答了这个问题。“如果变量位于变量类型大小的倍数的地址上,则代码效率更高。”从奇数地址读取 4 字节整数所需的 CPU 周期比可被 4 整除的地址需要更多的 CPU 周期,因此填充用于将变量与适当的地址对齐。
0赞 Mark Tolonen 7/21/2023
@Mika我不知道为什么你会在本机模式下出现如此大的差异。查看编译器是否有可以启用的填充警告,例如 Microsoft。