数组元素即使在分配后也具有 0 值

Array element has 0 value even after assigning it

所以我正在用 c 实现堆栈。现在初始堆栈大小为 5,每次达到限制时我都会将其加倍。我将其中的值从 1 推入 20,然后将其一一弹出并打印出来。

这是输出: 20, 19, ..., 7, 0, 5, 4, 3, 2, 1
注意 0 而不是 6

这里是stack.c:

#define INITIAL_CAPACITY 5

struct Stack
{
    int size; // initial size = 5
    int *data; // dynamically allocated array
    int pointer; // position of the top element
};

struct Stack *create_stack()
{
    int *array = calloc(INITIAL_CAPACITY, sizeof(int));
    struct Stack *stack = malloc(sizeof(struct Stack));

    if (array == NULL || stack == NULL)
    {
        printf("Memory allocation failed in create_stack");
        exit(EXIT_FAILURE);
    }
    stack->size = INITIAL_CAPACITY;
    stack->data = array;
    stack->pointer = -1;

    return stack;
}

static void resize(struct Stack *stack)
{
    const int new_size = stack->size * 2;

    stack->data = realloc(stack->data, (sizeof *stack->data) * new_size);

    if (stack->data == NULL)
    {
        printf("Memory allocation failed in the resize function.\n");
        exit(EXIT_FAILURE);
    }

    stack->size = new_size;
}

void push(struct Stack *stack, int element)
{
    int *stack_data = stack->data; // --> I think the bug is here
    stack->pointer = stack->pointer + 1;

    if (stack->pointer >= stack->size)
    {
        printf("Stack is full. Expanding the stack size.");
        resize(stack);
    }

    assert(stack->pointer < stack->size);

    stack_data[stack->pointer] = element;
}

但是当我将推送功能更改为此时,一切正常并且输出正确:

void push(struct Stack *stack, int element)
{
    stack->pointer = stack->pointer + 1;

    if (stack->pointer >= stack->size)
    {
        printf("Stack is full. Expanding the stack size.");
        resize(stack);
    }

    assert(stack->pointer < stack->size);

    (stack->data)[stack->pointer] = element; // <--- changed line
}

现在输出正确吗? 发生了什么事?

这是问题所在:

void push(struct Stack *stack, int element)
{
    int *stack_data = stack->data;
    stack->pointer = stack->pointer + 1;

    if (stack->pointer >= stack->size)
    {
        printf("Stack is full. Expanding the stack size.");
        resize(stack);
        // stack_data still points to old memory allocation
    }

    assert(stack->pointer < stack->size);

    stack_data[stack->pointer] = element;
}

由于您在 resize() 中修改了 stack->data,您保存的指针 stack_data 可能指向 resize() 之后的无效内存位置。您从未将新的 stack->data 分配给 stack_data 而您直接使用后者,这会导致内存访问冲突。

您的修复版本一直在使用 stack->data,因此上述问题不再存在。

指针 stack->data 可能会被

更新
stack->data = realloc(stack->data, (sizeof *stack->data) * new_size);

resize 函数中。但是,它并没有反映到变量 stack_data 中,它仍然指向旧的和可能无效的地方。

要使该函数起作用,您必须在调用 resize 之后分配给 stack_data

void push(struct Stack *stack, int element)
{
    stack->pointer = stack->pointer + 1;

    if (stack->pointer >= stack->size)
    {
        printf("Stack is full. Expanding the stack size.");
        resize(stack);
    }

    int *stack_data = stack->data; // move this after the call of resize()

    assert(stack->pointer < stack->size);

    stack_data[stack->pointer] = element;
}

当你调用函数 realloc 就像在这个语句中一样

stack->data = realloc(stack->data, (sizeof *stack->data) * new_size);

返回的指针不必与存储在用作函数参数的指针中的地址相同。

因此在函数push的第一次实现中,指针stack_data在其初始化后

int *stack_data = stack->data; // --> I think the bug is here
调用函数resize

后,

不一定等于指针stack->data的值

resize(stack);

因此这个语句

stack_data[stack->pointer] = element;

通常可以调用未定义的行为,因为可以使用指向已释放内存的指针 stack_data

在函数 push 的第二个实现中,使用了存储在数据成员 stack->data

中的重新分配内存的相同地址
(stack->data)[stack->pointer] = element; 

所以第二个函数实现没有第一个函数实现中存在的错误。

注意push和resize函数不能发出任何消息。决定是否输出消息的是函数的调用者。所以该函数应该有一种方法来报告它们是否成功。

函数可以这样定义

static int resize(struct Stack *stack)
{
    const int new_size = stack->size * 2;

    int *tmp = realloc(stack->data, (sizeof *stack->data) * new_size);

    int success = tmp != NULL;
    
    if ( success )
    {
        stack->data = tmp;
        stack->size = new_size;
    }
    
    return success;
}

int push(struct Stack *stack, int element)
{
    stack->pointer = stack->pointer + 1;

    int success = stack->pointer < stack->size;
    
    if ( !success )
    {
        success = resize( stack );
    }

    if ( success ) stack->data[stack->pointer] = element;
    
    return success;
}

问题出在您本地使用 stack_data。请注意 realloc 完全可以 更改 整个内存区域。来自手册页:

If the area pointed to was moved, a free(ptr) is done.

当您调用 resize(因此调用 realloc)时,stack->data 的内存区域可能已更改位置。如果是这样,当您从 push 中的 resize 返回时,stack_data 不再指向有效的内存区域,实际上,访问 freed 内存会调用 undefined行为。在那里使用本地真的没有意义,只需将 push 更改为

void push(struct Stack *stack, int element)
{
    stack->pointer = stack->pointer + 1;

    if (stack->pointer >= stack->size)
    {
        printf("Stack is full. Expanding the stack size.\n");
        resize(stack);
        // stack->data might point somewhere entirely different now.
    }

    assert(stack->pointer < stack->size);
    // access stack->data directly, no need for a local
    stack->data[stack->pointer] = element;
}

我创建了一个 mre that potentially illustrates this problem (took a few liberties with parts omitted in the OP). realloc doesn't have to change the memory region, so you will only see your error if that happens: https://godbolt.org/z/4nEoM65Yc