【问题标题】:Proper way to store and access variables/arrays of different data types in struct/array在结构/数组中存储和访问不同数据类型的变量/数组的正确方法
【发布时间】:2021-04-24 18:50:21
【问题描述】:

最近我一直在使用 .FIT 文件格式和 garmin 提供的 SDK。简而言之,我需要发送与消息定义相对应的消息定义和消息数据。有许多类型的消息,几乎所有时间我都需要使用其中的几个 - 而 SDK 不提供,它发送所有受支持的消息。 我需要存储不同数据类型的变量并将它们与特定消息相关联。目前,变量存储在如下结构中:

//Example
typedef struct
{
        uint32_t timestamp; // 1 * s + 0,
        uint16_t speed; // 1000 * m/s + 0,
        uint8_t heart_rate; // 1 * bpm + 0,
} FIT_RECORD_MESG;

但是,每个 FIT_(x)_MESG 的变量/数组数量以及数据类型都不同。这是 FIT_RECORD_MESG 的示例。

我已经创建了创建数组的函数,其中包含应该发送哪些变量/数组的信息。

void write_field(FIT_UINT8 field)
{
    fields_mesg.fields_number++;    //number of messages
    fields_mesg.fields_array = (uint8_t*) realloc(fields_mesg.fields_array, sizeof(uint8_t)*fields_mesg.fields_number); //append message type
    fields_mesg.fields_array[fields_mesg.fields_number-1] = field;                                  //array of gloabl field types
}
/*
Example array:
[0,2]; 
0 - timestamp
2 - heart_rate
variable speed should not be sent
*/

每个结构可以包含不同数量的变量/数组以及数据类型。如果结构中的所有数据类型都是相同的类型(并且没有数组),我可以创建一个指向数据结构的指针和在 write_field() 中创建的 fields_mesg.fields_array将是结构指针的偏移量。不幸的是,在我的情况下无法做到,因为数据类型可能会有所不同,并且结构可以包含数组。我正在考虑创建 3 个相应数据类型的数组/结构,但它会更复杂(并且需要弄清楚如何从结构中存储数组)。我想出了类似查找表的东西,其中每个位置都偏移(以字节为单位)到结构指针中的相应数据。

#define FIT_RECORD_MESG_32_BIT_START 0+3    
#define FIT_RECORD_MESG_16_BIT_START 1+3   
#define FIT_RECORD_MESG_8_BIT_START 2+3    
#define FIT_RECORD_MESG_ARRAY_SIZE_CNT 51//48+3
#define offsetof(s,m) ((size_t)&(((s*)0)->m))

/*Table of offsets to structure*/
 const FIT_UINT8 FIT_RECORD_MESG_SIZES[FIT_RECORD_MESG_ARRAY_SIZE_CNT] =
{
    FIT_RECORD_MESG_32_BIT_START,
    FIT_RECORD_MESG_16_BIT_START,
    FIT_RECORD_MESG_8_BIT_START,
    offsetof(FIT_RECORD_MESG, timestamp), 
    offsetof(FIT_RECORD_MESG, speed), 
    offsetof(FIT_RECORD_MESG, heart_rate), 
}

前三个位置是数据类型发生变化的信息。现在通过参考 fields_mesg.fields_array 我可以在结构中找到指向变量的指针并仅打印 1/2/4bytes 或 1/2/4bytes 的数组 - 取决于结构 FIT_(X )_MESG。

/*
    local_mesg_number - used by FIT protocol
    *array_sizes - pointer to array of offsets from structure address (example. FIT_RECORD_MESG_SIZES)
    *mesg_pointer - pointer to data structure (example. FIT_RECORD_MESG)
    *fields_mesg - pointer to structure which contains information which messages to send
*/
void WriteMessageRecord(FIT_UINT8 local_mesg_number, FIT_UINT8* array_sizes, const void* mesg_pointer, FIELDS_TO_SEND* fields_mesg, FILE* fp)
{
    void *pointer_offset = mesg_pointer; //begining of the structure
    uint16_t message_to_send;
    uint16_t offset_diference=0;
    
    /*Write begining of the message*/
    WriteData(&local_mesg_number, FIT_HDR_SIZE, fp);

    /*Go through every message in fields_mesg.fields_array*/
    for (uint16_t cnt = 0; cnt < fields_mesg->fields_number;cnt++)
    {
        message_to_send = fields_mesg->fields_array[cnt];
        /*If data type is 1 byte*/
        if (message_to_send >= (array_sizes[2]-3))
        {
            offset_diference = array_sizes[message_to_send + 4] - array_sizes[message_to_send + 3];
            /*Check if this isn't array and print as many as array length*/
            if (offset_diference > sizeof(uint8_t))
            {               
                for (uint8_t x = 0;x < offset_diference; x++)
                {
                    pointer_offset = ((char*)mesg_pointer) + array_sizes[message_to_send + 3]+x;
                    WriteData(((uint8_t*)pointer_offset), 1, fp);

                    printf("\nARRAY: %d, %d", (*(uint8_t*)pointer_offset), message_to_send);
                }
            }
            else 
            {
                pointer_offset = ((char*)mesg_pointer) + array_sizes[message_to_send + 3];
                WriteData(((uint16_t*)pointer_offset), 1, fp);

                printf("\nNOT   : %d, %d", (*(uint8_t*)pointer_offset), message_to_send);

            }
        }
        /*If data type is 2 bytes*/
        else if (message_to_send >= (array_sizes[1] - 3))
        {
            offset_diference = (array_sizes[message_to_send + 4] - array_sizes[message_to_send + 3]);

            /*Check if this isn't array and print as many as array length*/
            if (offset_diference > sizeof(uint16_t))
            {
                for (uint8_t x = 0;x < offset_diference; x+=2)
                {
                    pointer_offset = ((char*)mesg_pointer) + array_sizes[message_to_send + 3]+x;
                    WriteData(((uint16_t*)pointer_offset), 2, fp);

                    printf("\nARRAY   : %d, %d", (*(uint16_t*)pointer_offset), message_to_send);
                }
            }
            else //Print only 2 bytes
            {
                    pointer_offset = ((char*)mesg_pointer) + array_sizes[message_to_send + 3];
                    WriteData(((uint16_t*)pointer_offset), 2, fp);

                    printf("\nNOT   : %d, %d", (*(uint16_t*)pointer_offset), message_to_send);
            }

        }
        /*If data type is 4 bytes*/
        else if (message_to_send >= (array_sizes[0]-3))
        {
            offset_diference = (array_sizes[message_to_send + 4] - array_sizes[message_to_send + 3]);
            /*Check if this isn't array and print as many as array length*/
            if (offset_diference > sizeof(uint32_t))
            {
                for (uint8_t x = 0;x < offset_diference; x += 4)
                {
                    pointer_offset = ((char*)mesg_pointer) + array_sizes[message_to_send + 3] + x;
                    WriteData(((uint32_t*)pointer_offset), 4, fp);
                    printf("\nARRAY   : %d, %d", (*(uint32_t*)pointer_offset), message_to_send);
                }
            }
            else //Print only 4 bytes
            {
                pointer_offset = ((char*)mesg_pointer) + array_sizes[message_to_send + 3];
                WriteData(((uint32_t*)pointer_offset), 4, fp);
                printf("\nNOT   : %d, %d", (*(uint32_t*)pointer_offset), message_to_send);
            }
        }
    }
}

在主函数中:

// Write message of type: Record
        FIT_RECORD_MESG record_mesg;
        write_field(FIT_RECORD_MESG_TIMESTAMP); //will be sending timestamp variable
        write_field(FIT_RECORD_MESG_ENHANCED_SPEED);  //will be sending enhanced_speed variable
        record_mesg.timestamp = 5;
        record_mesg.enhanced_speed = 6;
        WriteMessageDefinitionSelection(0, fit_mesg_defs[FIT_MESG_RECORD], &fields_mesg, fp); //send message definition
        WriteMessageRecord(0, FIT_RECORD_MESG_SIZES ,&record_mesg, &fields_mesg, fp); //send message data
        clear_fields();

这可行,但我不喜欢它的一些东西:

  • 我需要创建额外的偏移数组,这会占用 ROM 中的额外内存。
  • 我需要计算每种数据类型的开始位置。
  • 对结构的任何更改都会产生错误。

那么.. 有没有更优雅的方式来存储不同数据类型的变量/数组并轻松访问它们?

【问题讨论】:

  • 所有这些都是完全错误的。 (1) 如果你需要存储几种不同类型的东西,那么数组不是你想要的。使用结构。 (2)如果需要写struct,就写struct,不要单独写每个字段。扔掉write_field 和它周围的一切,你不需要它。使用FIT_RECORD_MESG record_mesg; ... WriteData(&amp;record_mesg, sizeof(record_mesg), fp);
  • 你所展示的已经在SDK中实现了。但问题是我不想写整个结构“record_mesg”,因为很多时候我只需要从结构中写几个变量/数组。
  • (*(uint16_t*)pointer_offset) 等代码是 strict aliasing violation,因此行为未定义。除了严格的别名违规之外,它还可能违反任何对齐限制并且因此可能是未定义的行为。
  • 如果要写变量,就写变量。 WriteData(&amp;record_mesg.timestamp, sizeof(record_mesg.timestamp), fp);。为什么这个特定变量位于结构中很重要?
  • @n.'pronouns'm 事实上,在应用程序工作期间要写入的消息/变量的数量可能会发生变化,我只是不想列出if 语句检查要发送的变量。而且我需要为每种大量的消息类型执行此操作:P

标签: arrays c pointers struct structure


【解决方案1】:

这是一个如何构建它的基本工作示例。请注意,无需为每种字段类型设置单独的代码。

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>

typedef struct 
{
    size_t size;
    size_t offset;
} field_descriptor;

#define FIELD_SIZE(type, field) (sizeof(((type*)0)->field))

#define FIELD(type, field) { FIELD_SIZE(type, field), offsetof(type, field)}

#define LENGTH(x) (sizeof(x)/sizeof(x[0]))

typedef struct
{
    int a;
    float b;
    int c[3];
} example;

field_descriptor example_descriptors[] = 
{
    FIELD(example, a), 
    FIELD(example, b), 
    FIELD(example, c)
};


example x = { 1, 2.34, {-1, -2, 42 }};

int write_struct(FILE* f, void* s, field_descriptor* fields, size_t nfields)
{
    for (size_t i = 0; i < nfields; ++i)
    {
        size_t c = fwrite((char*)s+fields[i].offset, fields[i].size, 1, f);
        if (c != 1) 
        {
            return c;
        }
    }
    return nfields;
}

int read_struct(FILE* f, void* s, field_descriptor* fields, size_t nfields)
{
    for (size_t i = 0; i < nfields; ++i)
    {
        size_t c = fread((char*)s+fields[i].offset, fields[i].size, 1, f);
        if (c != 1) 
        {
            return c;
        }
    }
    return nfields;
}

// example use, error checking omitted
int main()
{
    example x = { 1, 2.34, {-1, -2, 42 }};
    example y = {0, 0, {0, 0, 0}};
    FILE* f = fopen("test.dat", "w");
    write_struct(f, &x, example_descriptors, LENGTH(example_descriptors));
    fclose(f);
    f = fopen("test.dat", "r");
    read_struct(f, &y, example_descriptors, LENGTH(example_descriptors));
    printf ("%d %f %d %d %d\n", y.a, y.b, y.c[0], y.c[1], y.c[2]);
}

【讨论】:

  • 好的,我明白了,我搞砸了创建描述符的方法。总的来说,描述符似乎会占用我一些不那么小的内存,但我认为这是我能做到的最好方法。谢谢:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-09-11
  • 1970-01-01
  • 2014-12-14
  • 2013-06-18
  • 1970-01-01
  • 2023-03-26
  • 1970-01-01
相关资源
最近更新 更多