Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/jquery-ui/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C 如何正确回收结构?_C_Struct_Free_Const Pointer - Fatal编程技术网

C 如何正确回收结构?

C 如何正确回收结构?,c,struct,free,const-pointer,C,Struct,Free,Const Pointer,我试图理解提供结构的创建/回收功能的常用习惯用法(良好实践)。以下是我尝试过的: struct test_struct_t{ int a; }; struct test_struct_t *create(int a){ struct test_struct_t *test_struct_ptr = malloc(sizeof(*test_struct_ptr)); test_struct_ptr -> a = a; return test_struct_p

我试图理解提供结构的创建/回收功能的常用习惯用法(良好实践)。以下是我尝试过的:

struct test_struct_t{
    int a;
};

struct test_struct_t *create(int a){
    struct test_struct_t *test_struct_ptr = malloc(sizeof(*test_struct_ptr));
    test_struct_ptr -> a = a;
    return test_struct_ptr;
}

void release(struct test_struct_t *test_struct_ptr){
    free((void *) test_struct_ptr);
}

int main(int argc, char const *argv[])
{
    const struct test_struct_t *test_struct_ptr = create(10);
    release(test_struct_ptr); // <--- Warning here
}
这是清楚的。因此,我倾向于如下定义回收方法:

void release(const struct test_struct_t *test_struct_ptr){
    free((void *) test_struct_ptr);
}
警告消失了,但我不确定它是否容易出错

因此,通常的做法是将结构回收方法参数定义为指向常量结构的指针,这样我们就可以避免在任何时候强制转换为非常量,并在回收方法实现中执行一次脏强制转换

因此,通常的做法是将结构回收方法参数定义为指向常量结构的指针,这样我们就可以避免在任何时候强制转换为非常量,并在回收方法实现中执行一次脏强制转换

不可以。更常见的情况是不将
const
用于动态分配的结构,或者不使用包含指向动态分配内存的指针的结构

您只标记您不打算修改的内容;释放它或其成员引用的数据是一种修改。只要看看是如何声明的:
void-free(void*)
,而不是
void-free(const-void*)

这是OP代码中的核心问题,使用
struct test\u struct\u t*test\u struct\u ptr=create(10)const
限定符的code>是正确的解决方案


然而,这里有一个有趣的潜在问题,我想仔细思考一下,因为这个问题的措辞是这样的,那些寻找答案的人会通过网络搜索遇到这个问题

如何正确回收结构

让我们看一个实际案例:动态分配的字符串缓冲区。有两种基本方法:

typedef struct {
    size_t          size;  /* Number of chars allocated for data */
    size_t          used;  /* Number of chars in data */
    unsigned char  *data;
} sbuffer1;
#define  SBUFFER1_INITIALIZER  { 0, 0, NULL }

typedef struct {
    size_t          size;  /* Number of chars allocated for data */
    size_t          used;  /* Number of chars in data */
    unsigned char   data[];
} sbuffer2;
可以使用预处理器初始值设定项宏声明和初始化第一个版本:

    sbuffer1  my1 = SBUFFER1_INITIALIZER;
这在POSIX.1
pthread\u mutex\u t
mutex和
pthread\u cond\u t
条件变量中使用

但是,由于第二个具有灵活的数组成员,因此不能静态声明它;您只能声明指向它的指针。因此,您需要一个构造函数:

sbuffer2 *sbuffer2_init(const size_t  initial_size)
{
    sbuffer2  *sb;

    sb = malloc(sizeof (sbuffer2) + initial_size);
    if (!sb)
        return NULL; /* Out of memory */

    sb->size = initial_size;
    sb->used = 0;
    return sb;
}
因此,您可以使用:

    sbuffer2 *my2 = sbuffer2_init(0);
虽然我个人实现了相关的功能,所以你可以这样做

    sbuffer2 *my2 = NULL;
相当于
sbuffer1 my1=sbuffer1\u初始值设定项

一个可以增加或减少为数据分配的内存量的函数,只需要一个指向第一个结构的指针;但是,要么是指向第二个结构的指针,要么返回可能修改过的指针,以使更改对调用方可见

例如,如果我们想从某个源设置缓冲区内容,也许

int  sbuffer1_set(sbuffer1 *sb, const char *const source, const size_t length);

int  sbuffer2_set(sbuffer2 **sb, const char *const source, const size_t length);
仅访问数据但不修改数据的函数也有所不同:

int  sbuffer1_copy(sbuffer1 *dst, const sbuffer1 *src);

int  sbuffer2_copy(sbuffer2 **dst, const sbuffer2 *src);
请注意,
常量sbuffer2*src
不是打字错误。由于该函数不会修改
src
指针(我们可以将其设置为
const sbuffer2*const src
!),因此它不需要指向数据指针的指针,只需要指向数据的指针

真正有趣的部分是回收/释放函数

释放此类动态分配内存的函数在一个重要部分上有所不同:第一个版本可以对字段进行简单的毒害,以帮助在释放bug后检测使用情况:

void sbuffer1_free(sbuffer1 *sb)
{
    free(sb->data);
    sb->size = 0;
    sb->used = 0;
    sb->data = NULL;
}
第二个问题很棘手。如果我们遵循上述逻辑,我们将编写一个中毒回收/释放函数,如下所示

void sbuffer2_free1(sbuffer2 **sb)
{
    free(*sb);
    *sb = NULL;
}
但是因为程序员习惯了
void*v=malloc(10);免费(五)模式(与
免费(&v);
!)相反),他们通常希望函数

void sbuffer2_free2(sbuffer2 *sb)
{
    free(sb);
}
相反;这个不能毒害指针。除非用户执行了相当于
sbuffer2_free2(sb)的操作;sb=NULL
,则有可能在以后重用
sb
的内容

C库通常不会立即将内存返回操作系统,而是将其添加到自己的内部空闲列表中,供后续的
malloc()
calloc()
realloc()
调用使用。这意味着在大多数情况下,指针仍然可以在
free()
之后取消引用,而不会出现运行时错误,但它指向的数据将完全不同。这就是为什么这些bug如此难以复制和调试

中毒就是简单地将结构成员设置为无效值,以便在运行时很容易检测到释放后的使用,因为这些值很容易看到。将用于访问动态分配内存的指针设置为
NULL
,这意味着如果该指针被取消引用,程序将因错误而崩溃。使用调试器进行调试要容易得多;至少你可以很容易地找到车祸发生的确切地点和方式

这在自包含代码中并不重要,但对于库代码或其他程序员使用的代码来说,它可以改变组合代码的总体质量。视情况而定;我总是根据具体情况来判断,尽管我倾向于使用指针成员和中毒版本作为例子

我对指针成员与灵活数组成员之间的关系做了更多的探讨。对于那些想知道如何回收/释放结构,以及如何选择在各种情况下使用哪种类型(指针成员或灵活数组成员)的人来说,这可能很有趣

因此,通常的做法是将结构回收方法参数定义为指向常量结构的指针,这样我们就可以避免在任何时候强制转换为非常量,并在回收方法实现中执行一次脏强制转换

不可以。更常见的情况是不将
const
用于动态分配的结构,或者不使用包含指向动态分配内存的指针的结构

您只标记您不打算修改的内容;释放它或其成员引用的数据是一种修改。只要看看是如何声明的:
void-free(void*)
,而不是
void-free(const-void*)<
void sbuffer2_free2(sbuffer2 *sb)
{
    free(sb);
}