【问题标题】:Implementation of user defined array on stack and/or heap in C在C中的堆栈和/或堆上实现用户定义的数组
【发布时间】:2025-12-04 23:50:01
【问题描述】:

我在学习 C 编程时试图了解 stackheap 之间的区别。
为此,我尝试实现二进制搜索。我想从用户那里获取二进制搜索的输入数据集。为了做到这一点,我希望用户能够定义数据集的大小(在本例中为数组)。一旦获得数组的大小,我就初始化数组,然后要求用户用值填充它。

我对堆栈的(可能是错误的)理解是,如果在编译时已知数组的大小,则可以在堆栈上初始化该数组。因此,我尝试了以下方法来实现堆栈上的用户输入(并且它“有效”):

void getUserInput(int sizeArray, int inputArray[], int userIn[]){
    /* get input data set and the element to be searched for from user */

    printf("Enter data elements seperated by a comma, e.g. 1,2,3. Press Enter at the end. Do not enter more than %d numbers: \n", sizeArray);
    for (int i = 0; i < sizeArray; i++){
        scanf("%d, ", &inputArray[i]);
    }
    printf("\nEnter the number to be searched for: \n");
    scanf("%d", &userIn[1]);

    printf("\nFor iterative implementation, enter 0. For recursive implementation, enter 1 :\n");
    scanf("%d", &userIn[0]);
}

int main(int arg, char **argv){
   int sizeArray;
   int userIn[2];                                                  // userIn[1]: element to be searched for; userIn[0]: iterative or recursive implementations
   printf("Enter size of input array: \n");
   scanf("%d", &sizeArray);
        
   int inputArray[sizeArray];
        
   getUserInput(sizeArray, inputArray, userIn);

// more code ...

}

对于堆上的实现,我尝试使用动态内存分配(它也“有效”):

int main(int arg, char **argv) {
    int sizeArray;
    int userIn[2];                                                  // userIn[1]: element to be searched for; userIn[0]: iterative or recursive implementations
    printf("Enter size of input array: \n");
    scanf("%d", &sizeArray);

    int *inputArray;

    inputArray = (int*) malloc(sizeArray * sizeof(int));
    if(inputArray == NULL) {
        printf("\n\nError! Memory not allocated, enter size of array again:\n");
        scanf("%d", &sizeArray);
        inputArray = (int*) malloc(sizeArray * sizeof(int));
    }
    getUserInput(sizeArray, inputArray, userIn);

// more code...
    free(inputArray)                                               // free memory allocated by malloc on the heap
}

现在,我想将这两种方法合并到一个文件中,所以我创建了一个开关来在堆栈和堆实现之间切换,如下所示:

int main(int arg, char **argv) {
    /* Obtain input */
    int stackHeap;                                                      // 0 = implementation on stack; 1 = implementation on heap
    printf("Implementation on stack or heap? Enter 0 for stack, 1 for heap: \n");
    scanf("%d", &stackHeap);

    int sizeArray;
    int userIn[2];                                                  // userIn[1]: element to be searched for; userIn[0]: iterative or recursive implementations
    printf("Enter size of input array: \n");
    scanf("%d", &sizeArray);
    int *inputArray;

    if (stackHeap == 0) {
        inputArray =  &inputArray[sizeArray];    
        printf("input array = %p\n", inputArray);
    } else {
        inputArray = (int*) malloc(sizeArray * sizeof(int));
        printf("input array = %p\n", inputArray);
        if(inputArray == NULL) {
            printf("\n\nError! Memory not allocated, enter size of array again:\n");
            scanf("%d", &sizeArray);
            inputArray = (int*) malloc(sizeArray * sizeof(int));
        }
    }

    getUserInput(sizeArray, inputArray, userIn);
// more code...
}

目前堆栈方法不起作用。我尝试在 if 语句中初始化 inputArray,而不是 inputArray = &inputArray[sizeArray]。然而这是不允许的,因为它只在 if 语句的范围内有效。我想我对如何使用指针 *inputArray 来初始化堆栈上的数组感到困惑。

我一直在阅读 C 中的指针和数组,这就是为什么我认为实现它会很有趣。我非常感谢您提供的任何反馈(很高兴我也犯了任何基本错误 - 我对这个主题很陌生)。

非常感谢!

【问题讨论】:

    标签: arrays c pointers stack heap-memory


    【解决方案1】:

    您的程序仅使用堆栈分配或仅使用堆分配都可以。堆栈分配的唯一潜在问题是,如果数组太大(即几 MB),它可能会溢出堆栈,而堆有 更多 更高的限制。

    组合程序的问题在于这没有意义:

    inputArray =  &inputArray[sizeArray]; 
    

    使用数组索引运算符实际上涉及取消引用指针,此时inputArray 没有指向任何地方。

    您需要选择其中一个,数组或指向动态分配数组开头的指针。

    【讨论】:

    • 如果选择堆类型,你需要想办法如何不定义自动存储数组。但它必须在两种情况下都定义
    • 感谢您的反馈。你是对的,我对指针 *inputArray 感到困惑。正如@johnbollinger 指出的那样,我必须将指针指向某个东西,所以我已经完成了他们在函数范围内创建临时数组的实现。然后我使用 inputArray 指针指向这个数组。
    【解决方案2】:
    typedef enum
    {
        STACK,
        HEAP,
    }type;
    
    size_t getSize(void)  // implement user input
    {
        return 100;
    }
    
    type getType(void)   // implement user input
    {
        return HEAP;
    }
    
    int main(void)
    {
        size_t stackSize = 0;
        size_t size;
        type tp;
        int *inputArray;
    
        size = getSize();
        tp = getType();
    
        if(tp == STACK) stackSize = size;
    
        int array[stackSize];
    
        if(tp == STACK)
            inputArray = array;
        else
            inputArray = malloc(size * sizeof(*inputArray));
    
        /* ... */
    }
    

    【讨论】:

    • 感谢您的反馈!抱歉,我发现很难理解您的代码。我对 typedef 和枚举不是很有信心,我对这些概念的理解几乎可以忽略不计。我会尝试进一步研究这些概念,然后再回到你的代码!
    【解决方案3】:

    TL;DR:除非您需要特殊语义,否则不要使用 VLA。它们很棘手,而且有点容易出错;使用堆通常更容易、更安全。

    您遇到的基本问题是定义 VLA 是一个声明,因此无法有条件地创建一个在条件块之后继续存在的 VLA。这几乎是设计使然——该块用于限制生命周期,因此可以自动释放它。

    因此,您的选择是无条件地创建一个(如果stackHeap 为真,则不要使用它)或将所有...更多代码...移动到条件块中。后者是最容易做到的,方法是把它全部放在一个函数中(例如processArray(int sizeArray, int inputArray[],... 很像你的getUserInput 函数)。那么你只需要在 if 和 else 情况下调用两次函数。

    【讨论】:

    • 根本不需要。看我的回答。最多你会浪费sizeof(int) id 实现不支持零大小的数组。
    • They're tricky and somewhat error prone; using the heap is generally easier and safer. 两者都容易出错且同样不安全。
    • 感谢您的反馈。 VLA 是变长数组?我很困惑——你提到不要使用 VLA,但后来又说使用堆更容易、更安全。我使用 malloc 创建的 inputArray 也在创建一个可变长度数组 - 那么为什么它会更安全/容易出错?另外,关于您在最后一段中的建议。到目前为止,这正是我实施这两个独立解决方案的方式。但是通过它,我有很多“复制”的代码,我想删除它们,因此我尝试合并这两种解决方案。
    【解决方案4】:

    我正在尝试了解堆栈和堆之间的区别,同时学习 C 编程。

    那是你的第一个错误。堆栈和堆不是 C 语言的概念,C 语言不依赖于内存区域之间的任何此类区别。话虽如此,许多 C 实现实际上确实依赖于这些,因此了解它们可能是值得的。然而,就学习 C 语言而言,最好专注于相关的原生 C 概念。

    在这个特定领域,这些将是对象的生命周期存储持续时间的相关概念。对象的生命周期是程序执行的一部分,在此期间该对象存在并保留其最后存储的值,这是其存储持续时间的函数,对于某些对象,还取决于它们的创建时间。存储持续时间由对象定义的位置(如果有)以及应用于它的类型限定符来定义。

    在代码块内没有static 限定符声明的对象具有“自动”存储持续时间,并且从进入块到块执行终止都存在。在基于堆栈的机器上,此类对象的存储通常在堆栈上分配。其他存储持续时间是“静态”、“分配”和“线程”,在基于堆栈的机器上,具有这些持续时间的对象的存储通常在堆栈外部实现,有些人将其描述为在堆上。

    我对堆栈的(可能是错误的)理解是,如果数组的大小在编译时已知,则可以在堆栈上对其进行初始化。

    这有点不精确。

    一方面,如果需要的大小在编译时已知,那么您当然可以为具有该大小的自动持续时间数组编写有效的声明。但是,这并不能保证程序在运行时实际上可以获得所需的空间。当声明具有非常大存储大小的对象时,这是一个特别的问题:当程序执行到达必须分配这样一个对象的点时,面向堆栈的 C 实现可能没有足够的可用堆栈空间。这就是本网站命名的“堆栈溢出”。

    另一方面,所有符合 C99 的实现以及大多数 C11 和 C17 实现都支持可变长度数组,其大小在运行时确定,并且可以是自动对象的类型。事实上,这就是您在第一个示例中为变量 inputArray 所采用的内容。该代码有效。一些实现也可能提供其他机制来分配可变长度数据作为自动存储。例如,请参阅alloca()

    对于堆上的实现,我尝试使用动态内存分配

    这将为您提供一个具有“已分配”存储持续时间的对象。在您可以描述为维护堆栈/堆区别的实现中,此类对象的存储确实通常在堆上。

    现在,我想将这两种方法合并到一个文件中,所以我创建了一个开关来在堆栈和堆实现之间切换

    这是可行的,但不是您尝试的方式。已经声明...

        int *inputArray;
    

    ...您需要将该指针分配为指向某物,然后才能访问其元素。在一种情况下,您通过使用malloc 分配内存来做到这一点,但在另一种情况下,这...

            inputArray =  &inputArray[sizeArray];    
    

    ... 根本不做你想做的事。 inputArray[sizeArray] 出现在表达式而不是声明中,不会导致分配任何内存。相反,它尝试访问inputArray 指向的对象中索引sizeArrayint。由于inputArray 尚未被分配一个值,这会产生未定义的行为。因此,确实,

    目前堆栈方法不起作用。

    你也是对的

    在 if 语句 [is] 不允许,因为它只有效 在 if 语句的范围内。

    所以当你说,

    我想我很困惑 如何使用指针 *inputArray 来初始化数组 堆栈。

    ,我倾向于同意。事实上,我认为你错过了一个关键点,那就是你不能在任何一种情况下使用指针来初始化一个数组,也不能像那样做。相反,您必须分配数组,并且或多或少分开分配指向它的指针。

    您显然知道如何使用 malloc 来做到这一点,尽管我想您并没有从这些方面考虑这一点。你这样做的方式是有效的,因为通过malloc 分配的对象具有“已分配”的存储持续时间,这意味着它的生命周期一直持续到它被释放。要对自动对象进行类似操作,您需要一个具有足够生命周期的对象,这意味着它必须直接在main() 的主体中声明,而不是在任何嵌套块中。你可以这样实现:

        int sizeArray;
        int sizeAutomatic = 1;
    
        printf("Enter size of input array: \n");
        scanf("%d", &sizeArray);
    
        if (stackHeap == 0) {
            sizeAutomatic = sizeArray;
        }
    
        int automaticArray[sizeAutomatic];
        int *inputArray;
    
        if (stackHeap == 0) {
            inputArray = automaticArray;
            // or, equivalently: inputArray = &automaticArray[0];
        } else {
            inputArray = malloc(sizeArray * sizeof(int));
            if(inputArray == NULL) {
                /// handle allocation error ...
            }
        }
    
        printf("input array = %p\n", (void *)inputArray);
    

    如果用户选择了自动分配选项,inputArray 将指向automaticArray 的第一个元素,从而有效地使两个别名相互对应。在另一种情况下,inputArray 将指向一个分配的对象。 automaticArray 对象在后一种情况下仍然存在,但它的长度为 1 并且未被使用。这一int 开销是您为允许用户决定使用哪种分配方法而付出的代价的一部分。

    【讨论】:

    • 哇。首先非常感谢你这么详细的解释。我想你已经通过这篇文章为我澄清了一些概念。我将尝试重申我对您的帖子的理解:-通过此实现,两种方法都实现了基本相同的想法->我们将指针指向已分配(已经)的数组。你是对的,这是我发现难以掌握的部分。 [1/2]
    • - 我明白你为什么定义 int sizeAutomatic = 1(对于堆的情况,automaticArray 被初始化为长度为 1 的数组)。我尝试通过仅声明 int sizeAutomatic; 来实现它这是否意味着在运行时,如果我选择堆方法,我正在声明随机数长度的自动数组(不必要)? [2/2]
    • 关于对象生命周期存储持续时间的另一个问题。如果我理解正确,这两个概念在某种意义上都描述了变量的范围?因为变量的范围告诉我变量在哪里有效(并且可访问)以及它的值是什么。还是我将两种不同的东西混合在一起?
    • @sar (1/2):是的。 (2/2):如果您通过删除sizeAutomatic 的初始化程序来修改我提供的代码,那么当执行到达automaticArray 的声明时,您会在动态分配情况下获得未定义的行为。您可以使用赋值代替初始化程序,但必须在对象的值被设置之前尝试使用该对象的值。
    • @sar (3/2):你把两件事混为一谈。范围是标识符的属性,而不是对象的属性。具有自动存储期限的对象的生命周期与其标识符的范围密切相关,但对于具有其他存储期限的对象,标识符范围和生命周期之间没有关联。具有分配持续时间的对象甚至没有标识符。此外,标识符范围和存储持续时间都受声明位置的影响,但最好将声明位置视为单独考虑。
    最近更新 更多