我最近开始学习C语言,并且我正在try 实现哈希图数据 struct . 我目前正在将我的桶实现为动态子菜单数组支持的列表. 当测试我的bucket_remove函数时,我在测试代码中犯了一个我很难理解的错误.

这是我的new_entry功能:

struct Entry *new_entry(char *id)
{
    struct Entry *e = (struct Entry *)malloc(sizeof(struct Entry));
    e->id = id;
    return e;
}

这是我的test_remove功能:

static char *test_remove()
{
    struct Bucket *b = new_bucket();

    for (int i = 0; i < 5; i++)
    {
        char id[8];
        snprintf(id, 8, "entry-%d", i);
        struct Entry *e = new_entry(id);
        bucket_add(b, e);
    }

    printf("Entry 0 id: %s.\n", b->_entries[0]->id);
    printf("Entry 1 id: %s.\n", b->_entries[1]->id);

    // ... rest of code omitted for clarity
}

在每次迭代的循环内,都会创建具有正确id的新条目,但之前条目的id随后会更新为最近的id. 因此,控制台打印:

Entry 0 id: entry-4.
Entry 1 id: entry-4.

我相信这与我将本地id缓冲区传递给我的new_entity函数的方式有关.每个条目必须接收指向同一内存的指针.但我不太明白为什么会这样.

这就是我理解循环中代码的方式:

  1. char id[8]; -堆栈中分配了8个字符的内存,地址分配给id.
  2. snprintf(id, 8, "entry-%d", i); -指向id缓冲区的指针被传递给snprintf,后者用创建的字符串的字符填充它.
  3. struct Entry *e = new_entry(id); -指向id缓冲区的指针被传递给new_entry,其中在堆上分配新的Entry struct ,并将id(指针)分配给其id成员.
  4. bucket_add(b, e); -指向桶的指针和指向条目的指针被传递给bucket_add,并将条目(指针)添加到桶的条目数组中.

当发生新的迭代时,看起来就像分配了新的内存部分,并且其新的内存地址被分配给本地范围中的新变量.那么如何覆盖以前的内存呢?

如果有人能澄清我的理解,我将不胜感激.

推荐答案

感谢对我问题的 comments ,我现在明白了这个问题.

我遇到的错误是"stack use after scope"错误.当分配内存的作用域结束后使用指向堆栈中内存的指针时,会发生此错误.

在下面的代码中,我在for循环中创建了一个缓冲区char id[8],赋予它块内的本地作用域.这意味着id不仅在每次迭代结束时超出范围(大多数程序员从其他编程语言中识别出这一点),而且至关重要的是,底层内存本身也被标记为可用.

for (int i = 0; i < 5; i++)
{
    char id[8];
    snprintf(id, 8, "entry-%d", i);
    struct Entry *e = new_entry(id);
    bucket_add(b, e);
}

在我的for循环的每次迭代中,都会从堆栈请求一个新的8个字节,并将其分配给一个本地范围为id的新变量.我的编译器重复使用了堆栈上刚刚从上一次迭代中可用的8个字节,因此id在堆栈上被赋予与上一次迭代相同的内存地址.This is implementation specific though, it may not always work this way.

作为范围界定的正常规则,这本身是可以预见的.出现错误是因为我将指针指向堆栈内存id并将此pointer复制到此处的堆内存中:

struct Entry *new_entry(char *id)
{
    struct Entry *e = (struct Entry *)malloc(sizeof(struct Entry));
    e->id = id;
    return e;
}

编译器对于堆中指向堆栈上内存的指针没有任何问题.但当堆上的这个指针用于访问堆栈after it goes out of scope and becomes available for reuse中的存储器时,它就变成了错误.这是undefined behaviour,这意味着我们面临内存损坏或指向意外内存的风险.

因此,当我访问声明该数据的本地范围之外的数据时:

printf("Entry 0 id: %s.\n", b->_entries[0]->id);
printf("Entry 1 id: %s.\n", b->_entries[1]->id);

输出:

Entry 0 id: entry-4.
Entry 1 id: entry-4.

未定义的行为表现为我的桶中的每个条目都有一个指向相同内存位置的id指针,因此具有相同的值.指针使用得越晚,情况就会变得更糟,因为该内存很快就会再次被重写.

通过确保当指针需要超过其作用域时,它指向的基础内存 *also超过其作用域来解决这个问题.在我的情况下,这意味着我需要将id指向的字符串复制到堆内存中并保存新的堆地址.

我 Select 使用strdup函数来执行此操作:

#include <string.h>

struct Entry *new_entry(char *id)
{
    struct Entry *e = (struct Entry *)malloc(sizeof(struct Entry));
    e->id = strdup(id);
    return e;
}

输出显示错误已解决:

Entry 0 id: entry-0.
Entry 1 id: entry-1.

C++相关问答推荐

如何将匿名VLA分配给指针?

自定义malloc实现上奇怪的操作系统依赖行为

当包含头文件时,gcc会发出隐式函数声明警告

C strlen on char array

C编译器是否遵循restrict的正式定义?

减法运算结果的平方的最快方法?

在C中将通用字符名称转换为UTF-8

为什么net/if.h在ifaddrs.h之前?

为什么GCC C23中的关键字FALSE不是整数常量表达式?

_泛型控制表达式涉及数组碰撞警告的L值转换错误?

在CLANG中调试预处理器宏

Setenv在c编程中的用法?

如何使解释器存储变量

每次除以或乘以整数都会得到0.0000

如何按顺序将所有CSV文件数据读入 struct 数组?

发送和接收的消息中的Unix域套接字不匹配

`%%的sscanf无法按预期工作

atoi函数最大长-长误差的再创造

使用 _Atomic float 时,MSVC 编译的代码会命中调试断言

在带中断的循环缓冲区中使用 易失性