【问题标题】:C same structure different sizeC 相同结构不同尺寸
【发布时间】:2017-09-07 17:46:45
【问题描述】:

我的问题与此有关: c define arrays in struct with different sizes

但是,我不想使用动态分配(嵌入式目标)。

  • 问题回顾:

C 中,我想要两个版本的相同结构,每个版本的 static 数组都有不同的大小。 两个结构都将通过指针参数被相同的函数使用。

    typedef struct {
        short isLarge;         //set 0 at initialization
        short array[SIZE_A];
        //more arrays
    } doc_t;

    typedef struct {
        short isLarge;         //set 1 at initialization
        short array[SIZE_B];
        //more arrays
    } doc_large_t;

    void function( doc_t* document ) {
        if ( document->isLarge ) {
             //change document into doc_large_t* [1]
        } 
        //common code for both doc_t and doc_large_t
    }
  • 问题:

(1) 上面的描述需要一种将指针doc_t*指针动态转换为doc_large_t*文档[1]的方法。那可能吗 ?怎么样?

(2) 我提出的另一个解决方案是为这两个结构提供一个通用的标头数据部分,不仅包括 isLarge 标志,还包括指向以下静态数组的指针。有多丑?

(3) 另外,您有什么好的技巧或解决方法吗?

编辑:

  • 更多上下文:

我的应用程序是嵌入式 MCU 上的路径查找。

我有几何对象,例如多边形。多边形可以描述简单的矩形障碍物,也可以描述更复杂的形状(例如可到达的区域)。

复杂的多边形可以有大量的顶点,但数量很少。简单的多边形很常见。

两者都将使用相同的算法。 我提前知道哪个多边形需要更多的顶点。

我正在尝试做的是优化工作内存以使其适合 MCU。 (即小形状得到小数组;复杂的得到大数组)

【问题讨论】:

  • 您可能想查看类似这个答案的内容,似乎您想用 c 中的结构做一些多态的事情(能够将不同的对象类型传递给同一个函数)stackoverflow.com/a/18422235/7299836
  • 对于选项 2,您应该有足够行的矩阵来为固件上的所有结构提供数组,因此 UB 就在拐角处...
  • 如果SIZE_ASIZE_B(我想)不同,使用c,你不能“转换”值,你必须编写一个转换函数。这意味着您必须为每种情况预先声明 2 个结构...
  • 顺便说一句,这似乎是XY problem。你想达到什么目标?描述您的目标并告诉我们所有限制条件。
  • 您更喜欢如何分配这些结构?使用灵活的数组成员做你想做的事情相对简单,但是在使用malloc() et al 的动态分配之外使用它并不容易。

标签: c arrays pointers embedded structure


【解决方案1】:

全局创建数组并使用指向大数组或小数组的指针。

【讨论】:

    【解决方案2】:

    您应该尝试保持单一结构,并针对不同的数组大小将它们放在union 中。我不知道以下结构对您的情况是否有意义。

        typedef struct {
            short isLarge;         //manually set to 0 or 1 after creating structure 
                                   //and accordingly initialize the arrays in below union
            union my_varying_arrays {
                short array_A[SIZE_A];
                short array_B[SIZE_B];
            };
            //more arrays
        } doc_t;
    

    如果isLarge0,则设置array_A 数组的值,如果1 设置数组array_B 的值。

    【讨论】:

    • 这不会有用,因为这个结构与较大的结构一样大(对吗?)。我想做的是内存优化(见编辑)
    • 嗯。如果您尝试根据输入大小分配内存,那么unions 可能不是正确的方法,因为始终根据其中较大的元素分配内存。
    【解决方案3】:

    类似于您在问题中提到的想法(指向数组的指针),但只有一个指针:

    typedef struct
    {
         short array[SIZE_B - SIZE_A];
         // more arrays alike...
    } Extension;
    typedef struct
    {
        short array[SIZE_A];
        //more arrays (all the small ones!)
        Extension* extraData;
    } doc_t;
    

    如果 extraData 为 NULL,则您有一个小多边形,否则,您会在引用的结构中找到附加数据。承认,对大多边形的所有值进行迭代有点讨厌......

    如果您可以为每种对象类型使用预定义大小的全局数组(正如 Dominic Gibson 提出的 - 顺便说一句,这是一个很好的建议),您可以通过将 isLarge 标志替换为函数来省去它:

    int isLarge(void* ptr)
    {
        return
            (uintptr_t)globalLargeArray <= (uintptr_t)ptr
            &&
            (uintptr_t)ptr < (uintptr_t)globalLargeArray + sizeof(globalLargeArray);
    }
    

    当然,所有个多边形(在上面的例子中:至少是大的)必须存在于这个数组中才能使其工作。如果您动态地或以其他方式创建至少一个(堆栈,另一个全局变量) - 我们就出局了......

    【讨论】:

      【解决方案4】:

      您可以通过对特定数组使用void * 来实现数据为const。 然后,您只需根据结构中的属性将 void * 转换为您需要的值。

      当您需要运行时的结构时,它会变得更加复杂。 尤其是在嵌入式目标上。

      typedef struct {
          short size;
          void *array;
      } doc_t;
      

      其中array 指向由内存管理器分配的内存块。

      您现在必须根据最大块大小决定是使用 C 标准 malloc 还是使用一些池化内存系统。 一个例子是ChibiOS Memory pools。 如果您随机分配和释放可变大小的内存块,则存在内存碎片的风险。

      如果您以增量方式分配,则不必担心内存问题。只需创建一个大块并跟踪您的位置。有点像栈。

      【讨论】:

        【解决方案5】:

        在编辑之后,我认为您可以做的最好的事情是分析您的需求,定义您的目标可以管理的最大简单和复杂多边形,然后声明一个单一和公共多边形池,例如:

        #include <stdio.h>
        #include <stdint.h>
        #include <stdbool.h>
        
        #define MAX_COMPLEX 16
        #define MAX_SIMPLE  16
        
        uint16_t g_Simple_Poly_set[MAX_COMPLEX][SIZE_A];
        uint16_t g_Complex_Poly_set[MAX_COMPLEX][SIZE_B];
        
        uint16_t g_Simple_Poly_used = 0;
        uint16_t g_Complex_Poly_used = 0;
        
        struct poly
        {
            bool     isLarge;
            uint16_t *vetexes;
        };
        
        
        bool create_poly_simple (struct poly *p)
        {
            bool retVal = false; // default: not more space for poly
        
            if (g_Simple_Poly_used < MAX_SIMPLE)
            {
                p->isLarge = false;
                p->vetexes = &g_Simple_Poly_set[g_Simple_Poly_used][0];
        
                g_Simple_Poly_used++;
        
                retVal = true;
            }
        
            return retVal;
        }
        
        bool create_poly_compleX (struct poly *p)
        {
            bool retVal = false; // default: not more space for poly
        
            if (g_Complex_Poly_used < MAX_COMPLEX)
            {
                p->isLarge = true;
                p->vetexes = &g_Complex_Poly_set[g_Complex_Poly_used][0];
        
                g_Complex_Poly_used++;
        
                retVal = true;
            }
        
            return retVal;
        }
        
        void your_stuff_with_poly ( struct poly *p)
        {
            uint32_t poly_size = (p->isLarge == false) ? SIZE_A : SIZE_B;
        
            // your stuff with the correct size
        }
        

        这是一个为结构的静态“实例化”而设计的简单实现。您还可以使用创建/销毁函数来增强代码,该函数可以跟踪池中的哪个数组可以免费使用。

        【讨论】:

        • 嵌入式系统的作用是知道内存占用。这似乎很合适。我会同意这个并报告结论。谢谢。
        【解决方案6】:

        使用malloc() 或类似的动态分配方法很容易。只需使用灵活的数组成员:

        typedef struct {
            short isLarge;         //set 0 at initialization
             .
             .
             .
            short array[SIZE_A];
            short largeArray[];
        } doc_t;
        

        分配一个“小结构”:

        doc_t *small = malloc( sizeof( *small ) );
        small->isLarge = 0;
        

        分配一个“大结构”:

        doc_t *large = malloc( sizeof( *large ) + ( SIZE_B - SIZE_A ) * sizeof( large->largeArray[ 0 ] );
        large->isLarge = 1;
        

        请注意,您必须保留largeArray 元素last,这意味着array 元素必须是倒数第二个元素才能正常工作。

        根据您自己的分配方式,这可能适用也可能不适用。

        (这也有点小技巧,因为它依赖于能够通过在SIZE_A 上使用SIZE_A 或更大的索引来访问largeArray 中的数据array。那是访问其边界之外的对象... )

        【讨论】:

        • “但是,我不想使用动态分配(嵌入式目标)。”,好吧,我肯定会使用这个答案,但用户不想要动态分配
        • @kaldoran 我了解 OP 不想使用动态分配。但他没有具体说明他是如何 分配结构的。如果没有这些信息,在使用灵活的数组成员时几乎不可能准确地提供答案。
        • 确实,这会很棒,但内存占用将不再被掌握。我更喜欢不能产生结果的算法,而不是因为内存满而崩溃。
        • 1) 不能保证arraylargeArray 之间没有填充。 2) 通过从array 初始化的指针访问largeArray 会调用未定义的行为(越界访问)。 3) 为什么不使用单个 FAM? 4) FAM 不适用于静态或自动对象。
        【解决方案7】:

        您的第二个解决方案是正确的想法。我不清楚你为什么认为这很丑陋。也许这个漂亮的实现会改变你的想法。

        你可以通过将基结构作为继承结构的第一个成员来实现单继承。然后可以使用指向基类型的指针来引用继承对象。

        typedef struct {
            short doc_type;
            short *array_ptr;
            // more array pointers
        } doc_base_t;
        
        typedef struct {
            doc_base_t base;         // base.doc_type set 0 at initialization
            short array[SIZE_A];     // base.array_ptr initialized to point here
            //more arrays
        } doc_small_t;
        
        typedef struct {
            doc_base_t base;         // base.doc_type set 1 at initialization
            short array[SIZE_B];     // base.array_ptr initialized to point here
            //more arrays
        } doc_large_t;
        
        void function( doc_base_t* document ) {
            if ( document->doc_type == 1) {
                 // array size is large
            } else {
                 // array size is small
            }
        
            //common code referencing arrays through doc_base_t->array_ptr
        }
        

        doc_base_t 中的 array_ptr 成员对于继承机制不是必需的。但我专门为你的函数的“公共代码”部分添加了这一点。如果doc_base_t 不包含array_ptr,那么您可以根据base_type 值将通用document 转换为doc_small_tdoc_large_t 类型。但是,您可能需要为每个继承的类型使用不同的实现。通过将array_ptr 成员添加到doc_base_t,我怀疑您可以为所有继承类型编写一个通用实现。

        因此,您将静态声明doc_small_tdoc_large_t 的所有实例。在初始化每个对象时,您将同时初始化 base.doc_typebase.array_ptr 成员。然后在调用function 之前将这两种​​类型的对象转换为doc_base_t。 (或者传递base成员的地址,得到相同的指针值。)

        更新示例:

        static doc_small_t doc_small_instances[NUM_SMALL_INSTANCES];
        static doc_large_t doc_large_instances[NUM_LARGE_INSTANCES];
        
        // DocInit must be called once at startup to initialize all the instances.
        void DocInit()
        {
            int index;
        
            for (index = 0; index < NUM_SMALL_INSTANCES; index++)
            {
                doc_small_instances[index].base.doc_type = SMALL;
                doc_small_instances[index].base.array_ptr = doc_small_instances[index].array;
            }
        
            for (index = 0; index < NUM_LARGE_INSTANCES; index++)
            {
                doc_large_instances[index].base.doc_type = LARGE;
                doc_large_instances[index].base.array_ptr = doc_large_instances[index].array;
            }
        }
        
        // DocProcess processes one doc, large or small.
        void DocProcess(doc_base_t *document)
        {
            int index;
            short *array_member_ptr = document->array_ptr;
        
            int array_size = SMALL;
            if (document->doc_type == LARGE)
            {
                array_size = LARGE;
            }
        
            for (index = 0; index < array_size; index++)
            {
                // Application specific processing of *array_member_ptr goes here.
        
                array_member_ptr++;
            }
        }
        
        // ProcessAllDocs processes all large and small docs.
        void ProcessAllDocs(void)
        {
            int index;
        
            for (index = 0; index < NUM_SMALL_INSTANCES; index++)
            {
                DocProcess(&doc_small_instances[index].base);
            }
        
            for (index = 0; index < NUM_LARGE_INSTANCES; index++)
            {
                DocProcess(&doc_large_instances[index].base);
            }
        }    
        

        【讨论】:

        • 这些是完全不同的数据类型,这是完全错误的!指针不是数组,数组也不是指针!
        • @Olaf 我不确定你的意思。我的意图是将 base.array_ptr 设置为指向数组的第一个元素(例如:doc_instance_1.base.array_ptr = doc_instance_1.array;)。指针不能指向数组是你的位置吗?
        • 嗯,我可能误解了你的意图。但是,UB 代表:您将如何获得访问的实际类型,即设置指向数组的指针?我真的很好奇您是如何在不违反有效类型规则而调用 UB 的情况下做到这一点的。让这个 UB 分开:这与没有指针的类型转换有什么不同?
        • @Olaf 我假设设计师知道他们在设计时需要的大小文档实例的数量。他们将编写代码以静态分配适当数量的实例。并且将有一个在启动时运行的初始化例程来初始化每个实例的基本成员。我在答案中添加了更多示例代码以使这一点更清楚。
        • 你了解什么是有效类型规则吗?请阅读标准。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-08-15
        • 2020-12-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-08-14
        相关资源
        最近更新 更多