那么,将struct reclamation method参数定义为const结构的指针,这样我们就可以避免在任何时候强制转换为非const,并且在reclamation method实现中执行一次这种脏的强制转换吗?
不,不使用更常见
const
具有动态分配的结构,或者具有包含指向动态分配内存的指针的结构。
你唯一的标记
康斯特
您不打算修改的内容;释放它或它的成员引用的数据是一种修改。看看怎么做
free()
声明:
void free(void *)
不是
void free(const void *)
.
这是OP代码中的核心问题,并且使用
struct test_struct_t *test_struct_ptr = create(10);
没有
康斯特
限定符是正确的解决方案。
不过,这里有一个有趣的潜在问题,我想仔细考虑一下,因为这个问题的措辞是这样的,那些寻找答案的人将通过网络搜索遇到这个问题。
如何正确回收结构?
让我们来看一个实际情况:动态分配的字符串缓冲区。有两种基本方法:
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_mutex_t
互斥和
pthread_cond_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_INITIALIZER;
.
一个函数,它可以增大或缩小为数据分配的内存量,只需要一个指向第一个结构的指针;但要么是指向指向第二个结构的指针的指针,要么返回可能修改过的指针,以便调用方能够看到更改。
例如,如果我们想从某个源设置缓冲区内容,可能
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);
请注意
const sbuffer2 *src
不是打字。因为函数不会修改
src
指针(我们可以做到
const sbuffer2 *const src
!)它不需要指向数据指针的指针,只需要指向数据的指针。
真正有趣的部分是回收/免费功能。
释放这些动态分配的内存的功能在一个重要部分上确实有所不同:第一个版本可以轻微地毒害字段,以帮助检测释放错误后的使用情况:
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); free(v);
模式(与
free(&v);
!),它们通常期望函数为
void sbuffer2_free2(sbuffer2 *sb)
{
free(sb);
}
相反,这个不能毒害指针。除非用户做了相当于
sbuffer2_free2(sb); sb = NULL;
,存在重复使用
sb
之后。
C库通常不会立即将内存返回到操作系统,而是将其添加到自己的内部空闲列表中,供后续的操作系统使用。
malloc()
,
calloc()
或
realloc()
打电话。这意味着在大多数情况下,指针仍然可以在
自由()
没有运行时错误,但它指向的数据将完全不同。这就是为什么这些bug在复制和调试时如此令人讨厌的原因。
中毒只是将结构成员设置为无效的值,因此在运行时很容易检测到空闲后的使用,因为很容易看到这些值。将用于访问动态分配内存的指针设置为
NULL
意味着如果指针被取消引用,程序将崩溃
segmentation fault
.使用调试器进行调试要容易得多;至少您可以轻松地找到崩溃发生的确切位置和方式。
这在自包含代码中并不那么重要,但对于库代码或其他程序员使用的代码,它可以对组合代码的一般质量产生影响。这要看情况而定;我总是根据具体情况来判断它,尽管我确实倾向于使用指针成员和中毒版本作为例子。
我对指针成员和灵活的数组成员进行了更多的上进和下退。
in this answer
.对于那些想知道如何回收/释放结构,以及如何选择在各种情况下要使用的类型(指针成员或灵活的数组成员)的人来说,这可能很有趣。