【问题标题】:Array element has 0 value even after assigning it数组元素即使在分配后也有 0 值
【发布时间】:2021-09-05 08:02:05
【问题描述】:

所以我在 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
}

现在输出如何正确? 发生了什么?

【问题讨论】:

  • 什么是未公开的resize?它会改变stack-&gt;data吗?请发帖Minimal, Reproducible Example
  • int pointer; 是一个令人困惑的名字......因为它不是一个指针。我建议将变量命名为sizecapacity。请注意,如果一个callocmalloccreate_stack 中失败,您将泄漏另一个的内存。
  • @MikeCAT 我添加了 resize 和 create_stack 函数
  • resize 可以更改stack-&gt;data 的值。如果您使用保存在stack_data 中的旧值,那将是一个问题。
  • 永远不要这样做pointer = realloc(pointer, ...)!重新分配可能会失败,然后在旧内存仍然有效的情况下返回一个空指针——但是您会通过覆盖指针而丢失对的引用,如果您没有另一个指针,则表示内存泄漏。更好:tmp = realloc(pointer, ...); if(tmp) { pointer = tmp; } else { /* some appropriate error handling */ }.

标签: c pointers stack undefined-behavior realloc


【解决方案1】:

问题来了:

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-&gt;data,因此您保存的指针stack_data 可能指向resize() 之后的无效内存位置。您从未将新的stack-&gt;data 分配给stack_data,而是直接使用后者,这会导致内存访问冲突。

您的固定版本一直使用stack-&gt;data,因此上述问题不再存在。

【讨论】:

    【解决方案2】:

    指针stack-&gt;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;
    }
    

    【讨论】:

    • 你的意思是:resize调用后赋值给"stack_data"
    【解决方案3】:

    当你调用函数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-&gt;data的值

    resize(stack);
    

    因此声明

    stack_data[stack->pointer] = element;
    

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

    在函数push的第二个实现中,使用了与数据成员stack-&gt;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;
    }
    

    【讨论】:

      【解决方案4】:

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

      如果指向的区域被移动,则执行 free(ptr)。

      当您调用resize(因此调用realloc)时,stack-&gt;data 的内存区域可能已更改位置。如果是这样,当您从push 中的resize 返回时,stack_data 不再指向有效的内存区域,事实上,访问该freed 内存会调用未定义的行为。在那里使用本地确实没有意义,只需将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,它可能说明了这个问题(在 OP 中省略了一些部分)。 realloc 没有 来改变内存区域,所以你只会看到你的错误 if 发生:https://godbolt.org/z/4nEoM65Yc

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-12-09
        • 2019-04-03
        • 1970-01-01
        • 2013-06-05
        • 1970-01-01
        • 2020-09-03
        • 2019-01-07
        • 1970-01-01
        相关资源
        最近更新 更多