C中结构成员(指针与数组)的内存分配之间的差异

Difference between memory allocations of struct member (pointer vs. array) in C

这两种分配内存的方式有什么有效的区别吗?

1.

typedef struct {
  uint8_t *buffer;
} Container;

Container* init() {
  Container* container = calloc(sizeof(Container), 1);
  container->buffer = calloc(4, 1);
  return container;
}

2.

typedef struct {
  uint8_t buffer[4];
} Container;

Container* init() {
  Container* container = calloc(sizeof(Container), 1);
  return container;
}

据我了解,整个 Container 结构将被分配到堆中,而 buffer 将指向相同的结构。这是正确的吗?

有区别。

我将尝试说明示例。

正如其他人指出的那样:

  1. 第一个示例更难管理,但您可以随时更改缓冲区的大小。
  2. 第二个例子更容易管理(你不必关心单独释放缓冲区),但你只能有固定大小的缓冲区。

正如评论中所指出的:如果缓冲区是结构中的最后一个元素(如提供的示例中所示),则可以为缓冲区分配任何长度。

例如

int extra_bytes_needed = ...;
Container* container = calloc(sizeof(Container) + extra_bytes_needed, 1);

图片左侧 - 您的第一个案例。

在图片的右侧-您的第二个案例。

Vladislav 很好地说明了区别;但是这是什么意思?不同的内存组织有几个影响:

    第二个例子中的
  1. struct container可用as-is;它不需要任何初始化。例如。
typedef struct {
  uint8_t buffer[4];
} Container;

Container c; 
strcpy(c.buffer, "Yes");

很好,但它可能会在第一个版本中崩溃,因为指针 c.buffer 将未初始化并包含无效地址。

  1. 具有 built-in 缓冲区的结构的性能可能更好,因为每个 init() 节省了一次分配。内存局部性也可能是一个问题:通过动态分配,缓冲内存可能远离结构内存,因此它不在缓存中。

还有一点。您在这里模仿 C++,init() 扮演带有构造函数的工厂的角色。

不幸的是,只要 struct Container 的定义可见,任何用户都可以创建一个未初始化的 Container 并使用它,这会带来灾难性的后果。 (在 C++ 中,我们会将构造函数声明为私有的,但在 C 中不能这样做。)

阻止用户创建 struct Container 的唯一方法是隐藏其实现。这类似于 C++ Pimpl idiom: The user has no header which actually defines Container but only a header defining operations on it which take and return 指向 Container(就像你的 init())。就用户而言,Container 保持 不完整类型

这是一个例子。此版本的容器具有以下功能:

  • 它不提供对数据的直接访问,而是分发数据的副本。这是否是可接受的开销取决于用例。我只是想说明 我们需要对 Container 的零知识。 它是完全隐藏的。缺少 re-engineering 类型,除非通过其官方界面,否则根本无法操作内容。 (这可能是一个缺点。)

  • 实际缓冲区(以及大小)现在是动态的。用户数据大小的唯一限制是由系统强加的。

  • 容器在用户获取容器中数据的副本时为用户分配内存,类似于POSIXscanf"assignment-allocation character"'m'.

  • 容器为分配的内存量和用户数据实际占用的内存量维护不同的大小。这避免了不必要的 re-allocations.

用户看到的容器是这个 header 和 collection 的函数签名:

#ifndef CONTAINER_INTERFACE_H
#define CONTAINER_INTERFACE_H

/* An abstract container. It can hold arbitrary amounts of data
   by means of danamic allocation. An out-of-memory condition will make
   it exit with an exit code of 1.
*/
#include <stddef.h>  // size_t

/** Forward declaration, actual definition unknown */
struct Container;
typedef struct Container Container; // convenience

/** Create and initialize a Container of size 0.
*/
Container *ac_init();

/** Delete a Container and free its buffer */
void ac_dispose(Container *container);

/** Obtain the data in the given container. Note that we don't
    expose the internal pointer to the user.
    @param userBuf is a pointer a pointer  
    which will be set to an allocated memory area of sufficient 
    size. The user must free() it. If the container does not hold data,
    *userBuf is not changed.
    @return the number of bytes actually copied, which is also the
    size of the allocated buffer.
*/
size_t ac_get(Container *container, unsigned char **userBuf);

/** Fill the container buffer with user data.
    @return the number of bytes actually copied
*/
void ac_put(Container *container, const unsigned char *userData, size_t userDataSz);

/* ... (Many) more functions for more complicated structs */

#endif //ndef CONTAINER_INTERFACE_H

一个简单的使用例子:

#include <stdio.h>
#include <stdlib.h> // exit, malloc etc.
#include <string.h>
#include "container-interface.h"

/// Obtain a copy of the container data and print it.
void printContainerData(Container *c)
{
    unsigned char *dataFromContainer; // will be set by ac_get
    size_t contDataSz = ac_get(c, &dataFromContainer);
    if(contDataSz == 0)
    {
        printf("[empty]\n");
    }
    else
    {
        dataFromContainer[contDataSz-1] = 0; // terminate string just in case.
        printf("String from container:               ->%s<-\n", (const char *)dataFromContainer);
        free(dataFromContainer);
    }
}

int main()
{
    char *userInput; // will be set by scanf
    Container *c = ac_init();


    while(1) // exit by EOF (Ctrl-Z or Ctrl-D)
    {
        printf("Please enter a line (empty for exit) ->");
        // EOF etc. will make scanf return something other than 1.
        // Use the fancy "m" POSIX extension in the format string
        // which allocates memory for us, obviating maximum line length
        // considerations.
        if(scanf("%m[^\n]", &userInput) != 1) { break; }
        getchar(); // read away remaining newline
        ac_put(c, (unsigned char *)userInput, strlen(userInput)+1);
        printContainerData(c);
        free(userInput);
    }
    ac_dispose(c); // kinda unnecessary in a hosted environment, but good habit.
}

最后,(隐藏,通常在仅链接的库中)Container 及其 "member" 函数的实现如下所示:

#include <stdlib.h> // exit, malloc etc.
#include <string.h>    // memcpy
#include "container-interface.h" // to make sure the function signatures match

/** The actual definition of Container. The user never sees this. */
struct Container
{
    unsigned char *buf;
    size_t dataSz;
    size_t allocSz;
};

/** Create and initialize a struct Container */
struct Container *ac_init()
{
    struct Container *newCont = malloc(sizeof(struct Container));
    if(!newCont) { exit(1); } // out of mem
    newCont->dataSz = 0;
    newCont->allocSz = 0;
    newCont->buf = NULL;
    return newCont;
}

void ac_dispose(struct Container *container)
{
    free(container->buf);
    free(container);
}

size_t ac_get(struct Container *container, unsigned char **userBuf)
{
    if(container->dataSz > 0)
    {
        *userBuf = malloc(container->dataSz);
        if(!*userBuf) { exit(1); } // out of mem
        memcpy(*userBuf, container->buf, container->dataSz);
    }
    return container->dataSz;
}

void ac_put(struct Container *container, const unsigned char *userData, size_t userDataSz)
{
    if(userDataSz != 0)
    {
        if(container->allocSz < userDataSz)
        {
            free(container->buf);
            container->buf = malloc(userDataSz);
            if(!container->buf) { exit(1); } // out of mem
            container->allocSz = userDataSz;
        }
        memcpy(container->buf, userData, userDataSz);
    }
    container->dataSz = userDataSz;
}

/* ... (Many) more functions for more complicated structs */