动态分配C结构体?

9

我想动态分配一个C结构体:

typedef struct {
    short *offset;
    char *values;
} swc;

'offset'和'values'都应该是数组,但它们的大小在运行时是未知的。

我该如何为我的结构体及其数组动态分配内存?


6
您在询问C还是C++?最佳解决方案将取决于您使用的编程语言。 - anon
2
以下是如何分配的好例子。将数组大小保存在结构体中可能是一个好主意,以使其更通用。然后可以编写通用访问函数(在C中)或成员访问方法(在C++中)。 - Richard Pennington
1
回应Neil:C和C++是非常不同的语言。选择其中一个,不要使用混合(如下面许多答案所示)的风格,即两种语言的混合。 - Martin York
13个回答

21
swc *a = (swc*)malloc(sizeof(swc));
a->offset = (short*)malloc(sizeof(short)*n);
a->values = (char*)malloc(sizeof(char)*n);

n是每个数组中的项目数,a是新分配的数据结构的地址。在释放a之前,不要忘记free()偏移量和值。


4
在 C 中,不需要将 malloc 的结果转换类型,但在 C++ 中需要。而 sizeof(char) 总是等于1。 - JoeG
3
@joe说:sizeof(char)始终为1,但在代码中有它还是很好的。这种做法类似于记录你的意图(而且它并不会在运行时产生额外的开销)。 - Martin York
2
同时使用sizeof(*a)和sizeof(a->values [0])意味着您不需要在代码中重复类型。请注意,sizeof是编译时的,因此是安全的。 - Mike Weller
1
首先,对于malloc()返回值的强制转换是一个有争议的做法,所以不要表现得你支持的一方是“正确”的。这个问题已经被讨论了很多次,而且这些评论不是复制辩论的地方。如果你需要C++兼容性,那么你别无选择,但如果不需要,我建议你不要这样做,因为如果a->offset的类型更改为long,你将不得不更改malloc()(和任何你可能调用的realloc())的强制转换,否则会导致严重的错误,这会比必要的维护更加麻烦。(接下来的内容见下一条评论) - Chris Lutz
6
出于同样的原因,你应该使用sizeof(*a)sizeof(*a->offset)而不是sizeof(swc)sizeof(short)。如果a的类型发生更改(或更可能的是,a->offset的类型从short变为long),使用变量而不是类型将使您仅更改声明变量的行,而不是更改所有调用malloc()realloc()的行。如果它们都能从变量(或结构体成员)中推断出正确的类型,那么如果我们必须将该类型更改为另一种类型,我们就会少些麻烦。每个人都受益。 - Chris Lutz
显示剩余4条评论

11

在C语言中:

swc *s = malloc(sizeof *s); // assuming you're creating a single instance of swc
if (s)
{
  s->offset = malloc(sizeof *(s->offset) * number_of_offset_elements);
  s->values = malloc(sizeof *(s->values) * number_of_value_elements);
}

在C++中:

try
{
  swc *s = new swc;
  s->offset = new short[number_of_offset_elements];
  s->values = new char[number_of_value_elements];
}
catch(...)
{
   ...
}

请注意,在C++中,与动态分配缓冲区相比,您可能更好地使用向量(vectors):

struct swc 
{
  std::vector<short> offset;
  std::vector<char> values;
};

swc *a = new swc;

问题:values 应该是一个单个字符的数组还是字符串的数组?这会有所不同。

编辑

我越想,就越不满意这个 C++ 的答案;在 C++ 中做这种事情的正确方式(假设你需要动态分配缓冲区而不是向量,但你可能并不需要)是将偏移和值的内存分配作为结构类型中的构造函数的一部分,并在结构实例被销毁时由析构函数来释放这些元素(通过delete或超出范围)。

struct swc
{
  swc(size_t numOffset = SOME_DEFAULT_VALUE, 
      size_t numValues = SOME_OTHER_DEFAULT_VALUE)
  {
    m_offset = new short[numOffset];
    m_values = new char[numValues];
  }

  ~swc()
  {
    delete[] m_offset;
    delete[] m_values;
  }

  short *m_offset;
  char  *m_values;
};

void foo(void)
{
  swc *a = new swc(10,20); // m_offset and m_values allocated as 
                           // part of the constructor
  swc b;                   // uses default sizes for m_offset and m_values
  ...
  a->m_offset[0] = 1;
  a->m_values[0] = 'a';
  b.m_offset[0] = 2;
  b.m_values[0] = 'b';
  ...
  delete a; // handles freeing m_offset and m_values
            // b's members are deallocated when it goes out of scope
}

+1。这是唯一一个回答他有关未知运行时值的问题的答案。 - Luca Matteis
我希望我能够为以下三个方面加分:1) 区分C和C ++,2) 检查malloc()的返回值,以及3) 使用sizeof var而不是sizeof(type)。虽然说,我可能会考虑使用std::string来替代std::vector<char> - Chris Lutz
1
如果您要使用C++构造函数和析构函数等结构,请确保正确使用。上述定义是危险的。您__必须__添加复制构造函数和赋值运算符,或者找到另一种管理偏移量和值数组的方法。此外,如果您要展示异常处理,则至少应该展示如何清理已分配的内存(因为使用两个原始指针并不容易)。 - Martin York

5
你需要分别进行操作。首先分配结构体,然后再分配数组内存。
在C中:
swc *pSwc = malloc(sizeof(swc));
pSwc->offset = malloc(sizeof(short)*offsetArrayLength);
pSwc->values = malloc(valuesArrayLength);

在C++中,你不应该做任何像那样的事情。


5
在C语言中:
typedef struct
{
    short *offset;
    char  *values;
} swc;

/// Pre-Condition:  None
/// Post-Condition: On failure will return NULL.
///                 On Success a valid pointer is returned where
///                 offset[0-n) and values[0-n) are legally de-refrancable.
///                 Ownership of this memory is returned to the caller who
///                 is responsible for destroying it via destroy_swc()
swc *create_swc(unsigned int size)
{
    swc *data    = (swc*)  malloc(sizeof(swc));
    if (data)
    {
        data->offset = (short*)malloc(sizeof(short)*n);
        data->values = (char*) malloc(sizeof(char) *n);
    }
    if ((data != NULL) && (size != 0) && ((data->offset == NULL) || (data->values == NULL)))
    {
        // Partially created object is dangerous and of no use.
        destroy_swc(data);
        data = NULL;
    }
    return data;
}
void destroy_swc(swc* data)
{
    free(data->offset);
    free(data->values);
    free(data);
}

在C++中

struct swc
{
    std::vector<short>   offset;
    std::vector<char>    values;
    swc(unsigned int size)
        :offset(size)
        ,values(size)
    {}
};

在C语言中,我的建议是不要对malloc()的返回值进行强制类型转换。如果offset的类型从short变为int(或longlong long),那么强制类型转换会导致需要修改一行代码。我宁愿使用data->offset = malloc(sizeof(*data->offset) * n),这样即使offset的类型发生变化也能正常工作。 - Chris Lutz
如果 size 为零,则 malloc 可能有效地返回 NULL,因此在决定是否有部分创建的对象之前,您应该先检查它,或者将 "size 为正" 添加为前提条件。 - Rob Kennedy
@Rob:你说得对。我本来以为会返回一个有效的指针,但是在阅读文档后发现实际上是由具体实现决定的(这也是更倾向于使用C++ new的另一个原因)。 - Martin York

4
你需要一个函数来完成这个任务。 类似这样的(我的C/C++有点生疏)
swc* makeStruct(int offsetCount, int valuesCount) {
  swc *ans = new swc();
  ans->offset = new short[offsetCount];
  ans->values = new char[valuesCount];
  return ans;
}

myNewStruct = makeStruct(4, 20);

语法可能有些问题,但通常这就是你需要的。如果你正在使用C++,那么你可能需要一个带有构造函数的类来接受这两个参数,而不是使用makeStruct,但做的事情非常相似。


2
你还应该创建一个destroyStruct函数。对称性意味着在后续的操作中,不太可能有人忘记删除所有数据成员。 - doron
2
如果你要使用C++结构体,那么你应该全部按照C++的方式来做,并且使用构造函数/析构函数。这种混合的C/C++是不直观的。 - Martin York
@Dave: C++ 有自己更细粒度版本的垃圾回收,叫做智能指针。此外,spoilt 不是正确的术语,我会说是残疾 :-)(开玩笑) - Martin York
@Martin:是的,我之前用过智能指针。它们几乎让C++成为一种可以使用的合理语言(尽管我支持的代码库将它们与标准指针混合使用 - 纯恶魔)。不过从问题的水平来看,我怀疑它们是否适合在这里讨论。 - DaveC

3

在这里,有很多正确的答案,但需要补充一点:你可以使用malloc来分配一个过大的结构体,以容纳最后一个成员的可变大小数组。

struct foo {
   short* offset;
   char values[0]
};

以及之后

struct *foo foo1 = malloc(sizeof(struct foo)+30); // takes advantage of sizeof(char)==1

为了使 values 数组有30个对象的空间。你仍需要执行相应操作。
foo1->offsets = malloc(30*sizeof(short));

如果您希望它们使用相同大小的数组,可以这样做。我通常不会实际这样做(如果结构体需要扩展,这将是维护的噩梦),但这是一种工具。[在C中的代码。您需要转换malloc的类型(最好使用new和RAII习惯用法)]

是的!你承认了:这将会造成维护上的噩梦! - isekaijin

2
swc* a = malloc(sizeof(*a));
a->offset = calloc(n, sizeof(*(a->offset)));
a->values = calloc(n, sizeof(*(a->values)));

在 C 语言中,不应该使用 void* 类型。但在 C++ 中,你必须使用它!


1
使用malloc函数或calloc动态分配内存,并在Google上搜索示例。
The calloc function initializes allocated memory to zero.

可能仍然值得提到new/delete是C++的构造。特别是在C++世界中,结构体并不常见。 - Adam Luchjenbroers
是的,我知道这个问题,我正在从答案中删除new/delete部分。 - Ashish

1

大部分的答案都是正确的。我想补充一些你没有明确提出但可能也很重要的事情。

C / C++ 数组不会在内存中存储它们自己的大小。因此,除非你希望 offsetvalues 具有编译时定义的值(在这种情况下,最好使用固定大小的数组),否则你可能希望将两个数组的大小存储在 struct 中。

typedef struct tagswc {
    short  *offset;
    char   *values;
    // EDIT: Changed int to size_t, thanks Chris Lutz!
    size_t offset_count;
    size_t values_count; // You don't need this one if values is a C string.
} swc;

免责声明:我可能是错的。例如,如果所有swc实例的所有offset都具有相同的大小,则最好将offset_count存储为全局成员,而不是struct的成员。对于valuesvalues_count也是如此。此外,如果values是C字符串,则不需要存储其大小,但要注意类似于Schlemiel the painter的问题。


2
不要使用 int 来存储大小。你不想让任何东西的大小为 -12,对吧?使用专门用于存储大小的类型 size_t - Chris Lutz

1

既然还没有人提到它,有时候一次性分配这块内存是很好的选择,这样你只需要在一个东西上调用free():

swc* AllocSWC(int items)
{
    int size = sizeof(swc); // for the struct itself
    size += (items * sizeof(short)); // for the array of shorts
    size += (items * sizeof(char)); // for the array of chars
    swc* p = (swc*)malloc(size);
    memset(p, 0, size);
    p->offset = (short*)((char*)swc + sizeof(swc)); // array of shorts begins immediately after the struct
    p->values = (char*)((char*)swc + sizeof(swc) + items * sizeof(short)); // array of chars begins immediately after the array of shorts
    return p;
}

当然,这种方法阅读和维护起来会更加困难(特别是在首次分配数组后动态调整大小的情况下)。这只是我在许多地方看到的另一种替代方法。


1
  1. 使用 size_t 替代 int
  2. 这个 hack 值非常惊人(也许超过了 9000)。除非你已经发现分配是应用程序性能瓶颈,并且你已经发现这可以缓解瓶颈,否则不要使用它。
- Chris Lutz
那绝对不是一个好主意。你没有考虑内存对齐问题。如果有人因为维护原因重新排序结构体成员或添加另一个成员,这可能会在未来导致真正的问题。 - Martin York

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接