【问题标题】:Struct field of undefined type in CC中未定义类型的结构字段
【发布时间】:2020-11-14 05:57:24
【问题描述】:

是否可以定义一个结构成员来保存未定义类型/大小的数据?

typedef struct my_data {
size_t size;
??? data;
} my_data

int main (void) {
my_data *toto;
void *test;

test = malloc(100);
toto = (my_data*)test;

基本上我希望 size 字段保存前 8 个字节,第二个字段保存其余字节?是否可以在 C 中以这种方式定义结构?

【问题讨论】:

  • 你不能有未定义的类型,但你可以有一个未定义大小的数组,比如char data[]; 但是你不能将它与自动变量一起使用,就像你在@中所做的那样987654324@。您必须使用指向堆分配数据的指针,如my_data *toto = malloc (...);
  • 你可以有一个不透明的指针struct,看:stackoverflow.com/questions/7553750/…它看起来像你需要的。
  • @n.'pronouns'm。谢谢你,灵活长度的数组正是我所需要的。
  • @Ekid 这个问题是指未定义的类型,这表明您想要的不仅仅是一个灵活的数组,因为它具有指定的元素类型。

标签: c generics struct void


【解决方案1】:

要处理的主要问题是对齐。 malloc 返回的指向内存块的指针对于任何对象类型都是正确对齐的,但是对于某些对象类型,您类型的 size 成员末尾的指针可能未正确对齐。您可以通过使用 size 成员和其他一些 max_align_t 类型的对象之间的联合来解决这个问题:

typedef union {
    size_t size;
    max_align_t reserved__;
} my_data_header;

然后分配一个对象,加上my_data_header 的大小,并使用刚刚超过my_data_header 末尾的指针指向您的对象:

my_data_header *head = malloc(sizeof(*head) + object_size);
if (head) {
    head->size = object_size;
    object_ptr = (void *)&head[1];
} else {
    object_ptr = NULL;
}

然而要释放内存,你需要释放头部,而不是对象指针。或者,可以从对象指针中检索指向头部的指针:

if (object_ptr) {
    head = &((my_data_head *)(void *)object_ptr)[-1];
} else {
    head = NULL;
}

可以定义一组接口函数以允许分配和释放任何大小的对象并检索当前大小:

obj_alloc.c

#include "obj_alloc.h"
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

typedef union {
    size_t size;
    max_align_t reserved__;
} obj_hdr;

static obj_hdr *obj_to_hdr(void *obj)
{
    if (!obj) {
        return NULL;
    }
    return (obj_hdr *)obj - 1;
}

size_t obj_size(const void *obj)
{
    if (!obj) {
        return 0;
    }
    return obj_to_hdr((void *)obj)->size;
}

void *obj_reallocarray(void *obj, size_t nmemb, size_t size)
{
    obj_hdr *hdr = obj_to_hdr(obj);

    if (!(nmemb && size)) {
        /* New size is zero. */
        free(hdr);
        return NULL;
    }
    if ((SIZE_MAX - sizeof(*hdr)) / nmemb < size) {
        /* Too big */
        errno = ENOMEM;
        return NULL;
    }
    hdr = realloc(hdr, sizeof(*hdr) + nmemb * size);
    if (!hdr) {
        /* Allocation failure. */
        errno = ENOMEM;
        return NULL;
    }
    /* Record size and return pointer to object. */
    hdr->size = nmemb * size;
    return hdr + 1;
}

void *obj_allocarray(size_t nmemb, size_t size)
{
    return obj_reallocarray(NULL, nmemb, size);
}

void *obj_calloc(size_t nmemb, size_t size)
{
    void *obj = obj_reallocarray(NULL, nmemb, size);

    if (obj) {
        memset(obj, 0, nmemb * size);
    }
    return obj;
}

void *obj_malloc(size_t size)
{
    return obj_reallocarray(NULL, 1, size);
}

void *obj_realloc(void *obj, size_t size)
{
    return obj_reallocarray(obj, 1, size);
}

void obj_free(void *obj)
{
    free(obj_to_hdr(obj));
}

obj_alloc.h

#ifndef OBJ_ALLOC_H__INCLUDED
#define OBJ_ALLOC_H__INCLUDED

#include <stddef.h>

/**
 * \brief Get current size of object.
 *
 * Get current size of object allocated by obj_malloc(),
 * obj_realloc(), obj_calloc(), obj_allocarray(), or
 * obj_reallocarray().
 *
 * \param[in] obj
 *   Pointer to object, or null pointer.
 *
 * \return current size of object.
 */ 
size_t obj_size(const void *obj);

/**
 * \brief Free object.
 *
 * Free object allocated by obj_malloc(), obj_realloc(),
 * obj_calloc(), obj_allocarray(), or obj_reallocarray().
 *
 * \param[in] obj
 *   Pointer to object, or null pointer.
 */
void obj_free(void *obj);

/**
 * \brief Allocate an object from dynamic memory.
 *
 * Allocate an object of specified size from dynamic memory and
 * record its size.
 *
 * \param[in] size
 *   Size of object to be allocated.
 *
 * \return On success, returns a pointer to the allocated object,
 * or a null pointer if \p size is 0. On failure, returns a null
 * pointer and sets \c errno to \c ENOMEM.
 */
void *obj_malloc(size_t size);

/**
 * \brief Reallocate an object from dynamic memory.
 *
 * Reallocate an existing object to a new, specified size from
 * dynamic memory and record its new size.
 *
 * obj_realloc(NULL, size) behaves like obj_malloc(size).
 *
 * obj_realloc(obj, 0) behaves like obj_free(obj) and returns a
 * null pointer.
 *
 * If a pointer to an object is given, it must have been returned
 * by a previous call to obj_malloc(), obj_realloc(),
 * obj_calloc(), obj_allocarray(), or obj_reallocarray().
 *
 * \param[in] obj
 *   Pointer to existing object, or a null pointer.
 * \param[in] size
 *   New size of object to be reallocated.
 *
 * \return On success, returns a pointer to the reallocated
 * object, or a null pointer if \p size is 0. On failure, returns
 * a null pointer and sets \c errno to \c ENOMEM, but the original
 * object remains unchanged.
 */
void *obj_realloc(void *obj, size_t size);

/**
 * \brief Allocate a zeroed array of objects from dynamic memory.
 *
 * Allocate memory for an array of objects from from dynamic
 * memory and with a specified number of elements and a specified
 * element size, recording the total size, and setting the memory
 * contents to zero.
 *
 * obj_calloc(nmemb, size) is equivalent to
 * obj_malloc(nmemb * size) followed by
 * memset(ptr, 0, nmemb * size) if the return value is non-null.
 * The call is valid even if the multiplication of \p nmemb by
 * \p size would result in arithmetic overflow, but the function
 * will fail to allocate memory in that case.
 *
 * \param[in] nmemb
 *   Number of elements to allocate.
 * \param[in] size
 *   Size of each element.
 *
 * \return On success, returns a pointer to the allocated memory,
 * or a null pointer if \p size is 0 or \p nmemb is 0. On failure,
 * returns a null pointer and sets \c errno to \c ENOMEM.
 */
void *obj_calloc(size_t nmenb, size_t size);

/**
 * \brief Allocate an array of objects from dynamic memory.
 *
 * Allocate memory for an array of objects from from dynamic
 * memory and with a specified number of elements and a specified
 * element size, recording the total size.
 *
 * obj_allocarray(nmemb, size) is equivalent to
 * obj_malloc(nmemb * size).  The call is valid even if the
 * multiplication of \p nmemb by \p size would result in
 * arithmetic overflow, but the function will fail to allocate
 * memory in that case.
 *
 * \param[in] nmemb
 *   Number of elements to allocate.
 * \param[in] size
 *   Size of each element.
 *
 * \return On success, returns a pointer to the allocated memory,
 * or a null pointer if \p size is 0 or \p nmemb is 0. On failure,
 * returns a null pointer and sets \c errno to \c ENOMEM.
 */
void *obj_allocarray(size_t nmemb, size_t size);

/**
 * \brief Reallocate an array of objects from dynamic memory.
 *
 * Reallocate an existing object to an array of objects from
 * dynamic memory with a specified number of elements and a
 * specified element size, recording the new total size.
 *
 * obj_reallocarray(obj, nmemb, size) is equivalent to
 * obj_realloc(obj, nmemb * size).  The call is valid even if the
 * multiplication of \p nmemb by \p size would result in
 * arithmetic overflow, but the function will fail to reallocate
 * memory in that case.
 *
 * obj_realloc(obj, 0, size) and obj_realloc(obj, nmemb, 0)
 * behave like obj_free(obj) and return a null pointer.
 *
 * If a pointer to an object is given, it must have been returned
 * by a previous call to obj_malloc(), obj_realloc(),
 * obj_calloc(), obj_allocarray(), or obj_reallocarray().
 *
 * \param[in] obj
 *   Pointer to existing object, or a null pointer.
 * \param[in] nmemb
 *   Number of elements to allocate.
 * \param[in] size
 *   Size of each element.
 *
 * \return On success, returns a pointer to the reallocated
 * memory, or a null pointer if \p nmemb is 0 or \p size is 0.
 * On failure, returns a null pointer and sets \c errno to \c
 * ENOMEM, but the original object remains unchanged.
 */
void *obj_reallocarray(void *obj, size_t n_memb, size_t size);

#endif

【讨论】:

    【解决方案2】:

    是的!您可以使用Zero length array。它基本上允许您在结构的末尾有一个可变长度的数组。它仅在作为结构的最后一个元素放置时才有效。

    typedef struct my_data {
        size_t size;
        int data[0];
    } my_data;
    
    int main (void) {
        my_data *toto;
        void *test;
    
        test = malloc(sizeof(my_data) + sizeof(int) * 100);
        toto = (my_data*)test;
        toto->size = 100;
        toto->data[43] = 123;
        printf("%d\n", toto->data[43]);
        free(toto); //don't forget to free dynamically allocated memory
    }
    

    所以这是最适合您的示例的方法。但是如果你只是想存储一个可变长度的数组,只需使用一个指向动态分配数组的指针:

    int main (void) {
        int *data;
        size_t size = 100;
        data = malloc(sizeof(int) * size);
        data[42] = 123;
        printf("%d\n", data[42]);
        free(data); //don't forget to free dynamically allocated memory
    }
    

    【讨论】:

    • 零长度数组是 GCC 扩展。您可以使用作为 ISO C 一部分的灵活数组成员。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-16
    • 1970-01-01
    • 1970-01-01
    • 2019-03-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多