malloc'd 结构内按值传递的指针

malloc'd pointer inside struct that is passed by value

提问人:Jeffrey Bonde 提问时间:10/3/2022 更新时间:10/3/2022 访问量:175

问:

我正在用 C 语言组合一个项目,其中我必须传递一个可变长度的字节序列,但由于堆可能有限,我正在尝试限制 malloc 调用。

假设我有一个结构体,它包含可变长度字节序列 ,以及一个函数 ,它创建一个 的实例。在 中,是 malloc'd,并按值返回。 然后将由 value 传递的其他函数使用: 。代码如下。my_structptrmy_funcmy_structmy_funcmy_struct.ptrmy_structmy_structanother_func

  1. 这是否“安全”,可以防止在原始副本或任何副本上提供的内存泄漏,当按值传递时,我调用或释放 malloc'd 指针?具体来说,有没有办法在返回时可以被重写或悬而未决?my_structmy_struct_destroyanother_funcinst.ptr
  2. 由于 stackoverflow 不喜欢基于意见的问题,是否有任何好的参考资料来讨论这种行为?我不确定要搜索什么。
typedef struct {
    char * ptr;
} my_struct;

// allocates n bytes to pointer in structure and initializes.
my_struct my_func(size_t n) {
    my_struct out = {(char *) malloc(n)};
    /* initialization  of out.ptr */

    return out;
}

void another_func(my_struct inst) {
    /* 
    do something using the passed-by-value inst 
    are there problems with inst.ptr here or after this function returns?
    */
}

void my_struct_destroy(my_struct * ms_ptr) {
    free(ms_ptr->ptr);
    ms_ptr->ptr = NULL;
}

int main() {
    my_struct inst = my_func(20);
    
    another_func(inst);
    
    my_struct_destroy(&inst);
}
C 指针 结构

评论

0赞 Avi Berger 10/3/2022
只要你的“my_struct_destroy或空闲”毕竟是该缓冲区的使用,你就不会编写用其他(地址)值覆盖指针本身的代码,并且你不会多次尝试“my_struct_destroy或空闲”,那么它应该没问题。问题在于,如果您搞砸了任何条件,可能没有注意到,可能在代码维护期间的后期。
0赞 tadman 10/3/2022
按值而不是按指针返回确实违背了许多 C 约定。my_struct
0赞 Lundin 10/3/2022
为什么你的堆“潜在受限”?具有有限堆的系统通常是微控制器系统,您根本不应该使用堆。

答:

3赞 Gene 10/3/2022 #1
  1. 我可以安全地传递并返回一个包含按值指针的结构,就像你所做的那样。它包含 的副本。调用函数中没有任何更改。当然,如果释放,然后调用者尝试使用它或再次释放它,则会出现一个大问题。ptranother_funcptr

  2. alloc+free 的位置是最佳做法。在可能的情况下,使分配对象的函数也负责释放它。如果不可行,malloc 和 free of the same object 应该位于同一个源文件中。如果这是不可能的(想想带有删除的复杂图形数据结构),则应清楚地标识管理给定类型对象的文件集合并记录约定。有一种常见的技术对于分阶段工作的程序(如编译器)很有用,其中一个阶段分配的大部分内存应该在下一个阶段开始之前释放。在这里,内存只由经理以大块的形式进行。从这些对象中,管理器分配任何大小的对象。但它只知道一种自由的方式:一次全部,大概在一个阶段的尽头。这是一个想法:obstacks。当分配更复杂时,较大的系统会实现某种垃圾回收器。除了这些想法之外,管理 C 存储的方法与颜色一样多。对不起,我没有任何指向参考文献的指针(双关语:)mallocgcc

如果只有一个可变长度字段,并且不需要动态更新其大小,请考虑将结构中的最后一个字段设置为数组来保存它。这在 C 标准中是可以的:

typedef struct {
    ... other fields
    char a[1]; // variable length
} my_struct;

my_struct my_func(size_t n) {
  my_struct *p = malloc(sizeof *p + (n - 1) * sizeof p->a[0]);
  ... initialize fields of p
  return p;
}

这样就避免了单独释放可变长度字段的需要。不幸的是,它只适用于一个。

如果您对 gcc 扩展感到满意,则可以分配大小为零的数组。在 C 99 中,您可以使用 获得相同的效果。这样可以避免在大小计算中。a[]- 1

评论

2赞 Avi Berger 10/3/2022
请注意,从 C99 开始,它被标准化为灵活数组成员的形式。它不再是扩展,现在是非标准的。看这里而且,如果你愿意的话char a[];char a[1];
0赞 Fe2O3 10/3/2022
来自旧计时器的 UV,用于展示在新标准作者诞生之前编写的大量生产代码中使用的技术。大多数公司不愿意为“修改”代码以符合新标准而支付数天/数周的费用。事实上,许多公司只允许在业务需要更改生产代码时进行最小的修改(在胁迫下)。
0赞 chux - Reinstate Monica 10/3/2022
@Gene,极端情况:使用 VLA ,代码可以很好地处理。有了这个答案,我们就有麻烦了。char a[];my_func(0)(n - 1) * sizeof p->a[0]
1赞 Lundin 10/3/2022
@Fe2O3 “大多数公司不愿意为几天/几周的'修改'代码以符合新标准”付费“不,但他们可能必须为错误修复付费。这始终是一个错误 - 摆脱这里看到的旧“结构黑客”不仅仅是一些装饰性的东西,它导致了真实且通常微妙的错误。直到 2000 年代(C99 发布多年后),我在编写处理位图和位图标头的代码时,一直在处理由它引起的错误。对于同一目标,将看似有效的代码从一个编译器移植到另一个编译器会导致文件生成损坏。
0赞 Fe2O3 10/3/2022
@Lundin 很公平。“移植代码”...是的,当下部结构发生变化时,会出现裂缝。我曾经在一家机构工作,将操作系统和DBS维持在生命支持上,因为令人恐惧的“重写”太可怕了,无法考虑。还有多少 Win-XP 盒子??(有多少个MS-DOS盒子???颤抖)