【问题标题】:Structures and memory management, C结构和内存管理,C
【发布时间】:2021-11-01 14:51:37
【问题描述】:

我正在尝试了解有关结构和内存管理的一些内容。我有三个版本的代码,其结果是相同的。它们仅在结构的定义/分配上有所不同。在版本一中,该结构分配有以下代码:resultValue_t *resultVal = malloc(sizeof(resultValue_t));。在第二版中,结构是在函数内部创建的:resultValue_t resultVal;。在第三版中,resultValue_t resultVal; 是全局创建的。 我想问一下这三个定义实际上有何不同?哪一个给出了哪些可能性?哪个更受赞赏? 我知道如果通过指针访问结构,则应该使用-> 运算符等。但是在这里我很好奇这些定义之间的区别和可能性。

//version_1

typedef struct
{
    int valOne;
    int valTwo;
}resultValue_t;

void twoSum(int *array, int sum)
{
    int baseVal, tempSum;
    int arrLen = sizeof(array);
  
    resultValue_t *resultVal = malloc(sizeof(resultValue_t));
    
    //...
    if (tempSum == sum)
    {
        resultVal->valOne = array[index];
        resultVal->valTwo = array[nIndex];
        printf("%d + %d\n", resultVal->valOne, resultVal->valTwo);
    }
    //...

    free(resultVal);
}

//version_2

typedef struct
{
    int valOne;
    int valTwo;
}resultValue_t;

void twoSum2(int *array, int sum)
{
    int baseVal, tempSum;
    
    resultValue_t resultVal;
    
    //...
    if (tempSum == sum)
    {
        resultVal.valOne = array[index];
        resultVal.valTwo = array[nIndex];
        printf("%d + %d\n", resultVal.valOne, resultVal.valTwo);
    }
    //...
}

//version_3

typedef struct
{
    int valOne;
    int valTwo;
}resultValue_t;
resultValue_t resultVal;

【问题讨论】:

  • 作为记录,最好像 typedef struct resultValue_t {...} resultValue_t; 这样对你的结构进行 typedef,这样你就可以添加另一个 resultValue_t 结构作为它的变量(例如创建链表)
  • 变体 1 通常推荐。如果您只需要函数内的结构,请将其保持在本地存储持续时间(所以变体 2)除非它的大小如此之大以至于您需要担心堆栈溢出。变体 3 不是线程安全的。但是,如果您仍然是单线程的,它可以(在大型结构的情况下)是变体 1 的有效替代方案,以节省动态内存分配(从而提高性能)。
  • OT:这个代码int arrLen = sizeof(array); 是....嗯...嗯,你从来没有我们arrLen 所以很难说出你的期望,但是...从名字arrLen似乎您期望代码没有做的事情。只是警告:-)
  • @4386427 是的,我知道 :) 这只是此函数中代码的一部分
  • @MrmDdda 你知道代码没有给你数组的长度!?它为您提供指针的大小(通常为 4 或 8)。

标签: c struct memory-management


【解决方案1】:

区别在于“存储时长”,即对象的生命周期。

在版本 1 中使用“分配的存储持续时间”。这意味着对象resultValmalloc 返回之后存在,直到某些代码通过调用free 显式“杀死它”。在大多数系统上,内存分配在称为堆的区域中。分配通常很慢(即除非有充分的理由,否则不要使用它 - 见下文)。

在版本 2 中使用“自动存储期限”。这意味着对象resultVal 一直存在,直到相关块的执行结束。在这种情况下,直到函数返回。换句话说 - 在这种情况下,对象resultVal 在执行进入函数时自动创建,并在执行离开函数时自动销毁。 在大多数系统上,内存分配在称为堆栈的区域中。分配通常非常快。

在版本 3 中使用“静态存储持续时间”。这意味着对象resultVal 从执行开始到执行结束都存在。因此,可以从代码中的任何位置随时访问该对象。作为程序启动的一部分,内存通常分配在一些特殊区域,而且速度也非常快。

显然,所有 3 种类型都有用例。

通常应避免使用版本 3,但在某些特殊情况下静态对象是有意义的。使用时,通常最好使用关键字 static 将它们的使用限制在“文件范围”内。

在大多数情况下,您应该选择版本 2,因为它既快速又安全。但是,如果对象的大小(在本例中为 resultVal)非常大,则应避免使用版本 2,因为存在堆栈溢出的风险。在这种情况下使用版本 1。

如果您希望对象在函数返回后“活着”,版本 1 也很有用(实际上是必需的)。但是,问题中的示例并非如此,因为在离开函数之前会调用 free

结论:

由于resultValue_t; 是一个相当小的对象类型,并且对象resultVal 在函数调用后不需要“活动”,您应该选择版本2。

【讨论】:

  • 谢谢@4386427。对于版本 3,它会是构建接口时使用的一个很好的例子吗?在那里,我们基本上需要从正在构建的代码中的许多地方访问该结构。版本 1 和 2 已经严重依赖于我们正在编码的内容。
  • @MrmDdda 是的,这是可以接受版本 3 的一个示例。如果您实现了一些要从“其他文件”调用的函数,并且这些函数需要共享一些您想对函数用户隐藏的数据,您可以创建一个文件范围静态变量,以便代码中的所有函数可以使用该对象,但外部没有人可以访问它。但只是重复一遍......在使用版本 3 之前请三思。
  • Re“在执行离开范围之前一直存在”:根据 C 标准,对象存在直到关联块的执行结束,而不是直到执行离开 块。调用子例程会导致执行离开块,但块的执行只是暂停,而不是结束,因此对象继续存在并且可以在此期间使用(例如,通过传递给子例程的指针)。
  • Re “当执行离开函数时自动销毁”:在这种情况下是这样,除了上面的“离开”与“结束”问题,但更一般地说,定义出现在嵌套块,生命周期在关联块执行结束时结束,可能比函数执行结束时更早。
  • @EricPostpischil 感谢您提供有关措辞的建议。我试图相应地编辑答案。顺便说一句:请随时更新答案以更准确。
【解决方案2】:
  • 版本 1 从堆中分配内存。
  • 版本 2 从堆栈分配内存。
  • 版本 3 使用 bss 部分的内存(在我使用的每个编译器实现中,但它可能来自其他地方)。它也是唯一初始化对象的版本(在本例中为全零)。

【讨论】:

    猜你喜欢
    • 2012-04-22
    • 1970-01-01
    • 2012-03-24
    • 2012-04-22
    • 2020-10-10
    • 1970-01-01
    • 1970-01-01
    • 2012-07-20
    • 2013-05-09
    相关资源
    最近更新 更多