C语言图文件解析器无法正确将边值存入数组问题排查
图结构解析器数组存储错误的解决方法
问题描述
我用C语言编写了一个解析图结构文件的程序,文件格式示例如下:
c FILE: graph_test c c SOURCE: generator c p edge 10 12 e 1 2 e 2 3 e 6 2
- 以
c开头的行是注释,需忽略 p行指定顶点总数和边总数e行描述顶点间的边关系
程序使用strtok()分割字符串提取数据,但将数据存入二维数组int edges[num_edges][2]时出现错误。执行时tmp打印的节点值正确,但数组中出现大量无意义的垃圾数据,输出结果示例:
> Execution List of edges : 1 -> 2 2 -> 3 6 -> 2 8 -> 3 3473509 -> 52 8 -> 7 9 -> 4 5 -> 6 7 -> 9 10 -> 1 1 -> 7 5 -> 4
完整代码如下:
#include <stdlib.h> #include <stdio.h> #include <string.h> #define MAX_SIZE 1000 int main(int argc, char *argv[]) { if (argc != 2) { printf("\n[USAGE] : ./parser file_path\n"); exit(1); } char *file_name = argv[1]; FILE *file; // Open file in read mode file = fopen(file_name, "r"); if (file == NULL) { printf("\nUnable to open file '%s'\n", file_name); exit(2); } char line[MAX_SIZE]; int num_vertices = 0; int num_edges = 0; int edges[num_edges][2]; // Read each line from the file int i; while (fgets(line, MAX_SIZE, file) != NULL) { // If the line starts with "c", it is a comment, so we ignore it if (line[0] == 'c') { continue; } // If the line starts with "p", it contains the number of vertices and edges if (line[0] == 'p') { i = 0; char *tmp = strtok(line, " "); while (tmp != NULL) { if (i == 2) { num_vertices = atoi(tmp); } if (i == 3) { num_edges = atoi(tmp); } tmp = strtok(NULL, " "); i++; } i = 0; continue; } // If the line starts with "e", we retrieve the edges if (line[0] == 'e') { char *tmp = strtok(line, " "); int j = 0; while (tmp != NULL) { if (j == 1) { edges[i][0] = atoi(tmp); } if (j == 2) { edges[i][1] = atoi(tmp); } printf("tmp : %s\n", tmp); tmp = strtok(NULL, " "); j++; } i++; continue; } } printf("\nNumber of vertices: %d\n", num_vertices); printf("Number of edges: %d\n", num_edges); printf("List of edges:\n"); // Print all edges for (int i = 0; i < num_edges; i++) { printf("%d -> %d\n", edges[i][0], edges[i][1]); } // Close the file fclose(file); return 0; }
错误原因
核心问题是数组初始化时机错误:
- 定义
int edges[num_edges][2];时,num_edges的初始值为0,因此该数组的实际大小是[0][2],没有可用存储空间。 - 后续读取
p行修改num_edges的值,无法改变已创建数组的大小。 - 向0大小的数组写入数据会触发内存越界访问,覆盖其他变量空间或读取未初始化的内存区域,导致输出垃圾值。
解决方案
需要先读取p行获取num_edges的值,再创建对应大小的存储结构。由于C语言栈上的变长数组(VLA)无法动态调整大小,推荐使用动态内存分配(malloc/free)解决问题:
修正后的代码
#include <stdlib.h> #include <stdio.h> #include <string.h> #define MAX_SIZE 1000 int main(int argc, char *argv[]) { if (argc != 2) { printf("\n[USAGE] : ./parser file_path\n"); exit(1); } char *file_name = argv[1]; FILE *file; // Open file in read mode file = fopen(file_name, "r"); if (file == NULL) { printf("\nUnable to open file '%s'\n", file_name); exit(2); } char line[MAX_SIZE]; int num_vertices = 0; int num_edges = 0; int **edges = NULL; // 先声明指针,后续分配内存 // 第一步:读取文件找到p行,获取顶点数和边数 while (fgets(line, MAX_SIZE, file) != NULL) { if (line[0] == 'c') { continue; } if (line[0] == 'p') { int i = 0; char *tmp = strtok(line, " "); while (tmp != NULL) { if (i == 2) { num_vertices = atoi(tmp); } if (i == 3) { num_edges = atoi(tmp); } tmp = strtok(NULL, " "); i++; } break; // 找到p行后跳出循环,准备分配内存 } } // 分配内存存储边数据 if (num_edges > 0) { edges = malloc(num_edges * sizeof(int*)); for (int k = 0; k < num_edges; k++) { edges[k] = malloc(2 * sizeof(int)); } } else { printf("\nNo edges found in the file\n"); fclose(file); exit(3); } // 第二步:重置文件指针到开头,再次读取处理边数据 rewind(file); int i = 0; while (fgets(line, MAX_SIZE, file) != NULL) { if (line[0] == 'c') { continue; } if (line[0] == 'p') { continue; // 已处理过p行,直接跳过 } if (line[0] == 'e') { char *tmp = strtok(line, " "); int j = 0; while (tmp != NULL) { if (j == 1) { edges[i][0] = atoi(tmp); } if (j == 2) { edges[i][1] = atoi(tmp); } tmp = strtok(NULL, " "); j++; } i++; // 防止读取的边数超过p行指定的数量 if (i >= num_edges) { break; } } } printf("\nNumber of vertices: %d\n", num_vertices); printf("Number of edges: %d\n", num_edges); printf("List of edges:\n"); // 打印所有边 for (int k = 0; k < num_edges; k++) { printf("%d -> %d\n", edges[k][0], edges[k][1]); } // 释放动态分配的内存 for (int k = 0; k < num_edges; k++) { free(edges[k]); } free(edges); // 关闭文件 fclose(file); return 0; }
关键修改点
- 分两次读取文件:
- 第一次读取仅定位
p行,获取顶点数和边数 - 使用
rewind(file)重置文件指针,第二次读取处理所有e行
- 第一次读取仅定位
- 动态内存分配:
- 根据
num_edges的实际值分配对应大小的二维数组 - 程序结束前释放内存,避免内存泄漏
- 根据
- 边界检查:
- 处理
e行时,当读取的边数达到num_edges就停止,防止越界访问
- 处理
简化替代方案(一维数组模拟二维)
如果不想分两次读取文件,也可以用一维数组存储边对,减少内存分配的复杂度:
// 分配内存 int *edges = malloc(num_edges * 2 * sizeof(int)); // 存储边 edges[i*2] = atoi(tmp); // 第一个顶点 edges[i*2+1] = atoi(tmp); // 第二个顶点 // 访问边 printf("%d -> %d\n", edges[k*2], edges[k*2+1]);
内容的提问来源于stack exchange,提问作者Le Magicien Annonyme
相关产品推荐
相关产品推荐

