用C数组存储结构体后指针读取出现地址偏移的原因与修复
你遇到的问题本质是两种数组类型的混淆,加上一处内存分配的错误,导致内存访问的偏移计算完全错误。我们一步步拆解:
1. 内存分配的核心错误
你创建的是一个指针数组(Path **array),每个元素是指向Path结构体的指针。但在给每个元素分配内存时,你写错了:
array[i] = malloc(sizeof(Path *)); // 错误:分配的是指针的大小,不是结构体的大小
这里应该分配Path结构体本身的大小,而不是指针的大小:
array[i] = malloc(sizeof(Path)); // 正确:分配能容纳整个Path结构体的内存
你的错误导致每个array[i]指向的内存块只有指针的大小(64位系统下是8字节,即0x10),但实际Path结构体的大小是0x20(从你观察到的地址间隔可以推断),这已经造成了内存越界,后续对path->next_hop和path->weight的修改可能会破坏堆内存。
2. PrintTable函数的类型不匹配
你的PrintTable函数声明是:
void PrintTable(Path *paths, int n)
它期望接收一个连续的Path结构体数组的首地址,所以代码里用paths + i来计算第i个结构体的地址——这是基于连续结构体数组的偏移逻辑(偏移量是i * sizeof(Path))。
但你传给它的是array[0],也就是指针数组里第一个Path*的地址。此时paths实际上指向的是Path*类型的元素(而不是Path结构体),所以paths + i的偏移量是i * sizeof(Path*)(64位下是i*8,即0x10的间隔),这和每个Path结构体实际的地址间隔0x20完全不匹配。你手动改成paths + (2*i)刚好凑对了偏移,但这是治标不治本的歪招,本质是类型不兼容导致的。
有两种合理的修复方式,根据你的需求选择:
方案一:改用连续的结构体数组(推荐)
如果你的场景不需要动态释放单个结构体,直接分配一块连续的Path结构体数组更高效,也符合PrintTable的设计:
// 直接分配n个连续的Path结构体 Path *array = malloc(items * sizeof(Path)); // 初始化每个结构体(不需要再循环malloc) for (int i=0; i<items; i++) { array[i].next_hop = -1; array[i].weight = INT_MAX; // 注意:INT_MAX是int类型,如果weight是float,建议用FLT_MAX } // 调用PrintTable时直接传数组首地址 PrintTable(array, items);
这种方式下,array本身就是连续的结构体数组,PrintTable里的paths + i会正确计算每个结构体的地址,完全不需要调整偏移。
方案二:修改PrintTable适配指针数组
如果你必须使用指针数组(比如需要单独释放某个结构体),那么要修改PrintTable的参数类型为Path**,然后遍历每个指针:
// 修改函数声明为接收指针数组 void PrintTable(Path **paths, int n) { if(paths == NULL || n <= 0) return; printf("%s %8s %8s\n", "Net ID", "Weight", "Next Hop"); for(int i = 0; i < n; i++) { Path *p = paths[i]; // 直接取指针数组的第i个元素 // 注意:要检查p是否为NULL,避免空指针访问 if (p == NULL) { printf("%d %8s %8s\n", vertices[i], "NULL", "NULL"); continue; } printf("%d %8.2f %8d\n", vertices[i], p->weight, p->next_hop); } } // 调用时传指针数组的首地址 PrintTable(array, items);
同时别忘了修复之前的内存分配错误,把sizeof(Path*)改成sizeof(Path)。
- 如果
weight是float类型,用INT_MAX赋值可能会有类型转换问题,建议用<float.h>里的FLT_MAX。 - 内存分配后记得检查
malloc的返回值是否为NULL,避免空指针访问。 - 程序结束时要记得释放内存:如果是连续数组,直接
free(array);如果是指针数组,要先循环free(array[i]),再free(array)。
内容的提问来源于stack exchange,提问作者Nat569On

