为什么我在下面的代码中出现分段错误?

我有一个txt文件,我只想获得大于12个字符的行。 将这些行插入名为graph_node的图形变量中。

txt文件:

1,"execCode(workStation,root)","OR",0 2,"RULE 4 (Trojan horse installation)","AND",0 3,"accessFile(workStation,write,'/usr/local/share')","OR",0 4,"RULE 16 (NFS semantics)","AND",0 5,"accessFile(fileServer,write,'/export')","OR",0 6,"RULE 10 (execCode implies file access)","AND",0 7,"canAccessFile(fileServer,root,write,'/export')","LEAF",1 6,7,-1 

图节点类型:

 #ifndef Graph_Structure #define Graph_Structure struct Graph_Node{ char id[50]; char node_name[50]; struct Graph_Node* next; struct Graph_Node* edges; }; typedef struct Graph_Node graph_node; #endif 

这是将数据插入图形变量的方法:

 void insert_node(graph_node** node, graph_node* data){ printf("\nINSERTING\n"); graph_node* temp = (graph_node*)malloc(sizeof(graph_node)); for(int i = 0; i id); i++) temp->id[i] = data->id[i]; for(int i = 0; i node_name) - 1; i++) temp->node_name[i] = data->node_name[i]; temp -> next = *node; *node = temp; } 

这是使txt文件中的行大于12个字符的方法:

 void generate_nodes(graph_node** graph, char* file_name){ graph_node* data = (graph_node*)malloc(sizeof(graph_node)); FILE* f_Data_Pointer = fopen(file_name, "r"); FILE* f_Aux_Pointer = fopen(file_name, "r"); char c = 0; char line[256]; int counter = 0; int q = 0; //quotation marks bool jump_line = false; while(!feof(f_Data_Pointer)){ c = 0; memset(line, 0, sizeof(line)); while(c != '\n' && !feof(f_Aux_Pointer)){ // check line c = fgetc(f_Aux_Pointer); line[counter] = c; counter++; } if(strlen(line) > 12){ //lines with no edges /*line[counter-3] != '-' && line[counter-2] != '1'*/ int size = strlen(line); printf("\nline size: %d\n", size); counter = 0; c = 0; while(c != ','){ //id c = fgetc(f_Data_Pointer); if(c != ','){ data->id[counter] = c; counter++; } printf("%c", c); } counter = 0; c = 0; while(1){ //node_name c = fgetc(f_Data_Pointer); if(c != '"'){ data->node_name[counter] = c; counter++; } else{ q++; } if(q > 1 && c == ',') break; printf("%c", c); } counter = 0; c = 0; while(c != '\n'){ c = fgetc(f_Data_Pointer); printf("%c", c); } insert_node(graph, data); memset(data->id, 0, sizeof(data->id)); memset(data->node_name, 0, sizeof(data->node_name)); } else{ //lines with edges while(c != '\n' && !feof(f_Data_Pointer)){ c = fgetc(f_Data_Pointer); } } } fclose(f_Data_Pointer); fclose(f_Aux_Pointer); } 

我在“strlen”中的命令“for”中获取insert方法中的错误,它表示data-> id和data-> node_name未初始化但我不明白为什么。 我在数据上使用了malloc:

 graph_node* data = (graph_node*)malloc(sizeof(graph_node)); 

错误:

 Conditional jump or move depends on uninitialised value(s) ==3612== at 0x4C30B18: strcpy (vg_replace_strmem.c:510) ==3612== by 0x4008B2: insert_node (mulval.c:44) ==3612== by 0x400C03: generate_nodes (mulval.c:159) ==3612== by 0x400CE8: main (mulval.c:187) 

你的代码中最大的问题是你经常忽略'\0'终止字节并传递给像strlen这样的函数,它需要一个有效的字符串(一个以0结尾的字符串)。

例如在insert_node

 for(int i = 0; i < strlen(data->id); i++) temp->id[i] = data->id[i]; 

在这里你要复制所有字符,期望'\0'终止字节, temp->id将存储一系列字符,但它不是字符串。 strlen(data->id)将具有未定义的行为,因为如果在没有0终止字符串的情况下初始化它,则data->id很可能不会以0结尾。

如果你知道源字符串长度小于49个字符,那么你应该使用strcpy ,或者strncpy要完全确定:

 strncpy(temp->id, data->id, sizeof temp->id); temp->id[sizeof(temp->id) - 1] = 0; 

node_name相同。 另外,您不检查malloc返回NULL

foef行也是错误的,请参阅为什么while(!foef(...))总是错误的 。

你应该改写这个

 while(c != '\n' && !feof(f_Aux_Pointer)){ // check line c = fgetc(f_Aux_Pointer); line[counter] = c; counter++; } 

像这样:

 int c; // <-- not char c while((c = fgetc(f_Aux_Pointer)) != '\n' && c != EOF) line[counter++] = c; line[counter] = 0; // terminating the string! 

fgetc返回一个int ,而不是一个char ,它应该是int ,否则与EOF的比较会出错。 在这里你忽略了设置0终止字节,结果是一个字符序列,但不是字符串。 下一行if(strlen(line) ...溢出因为这个,因为strlen将继续寻找超出限制的0终止字节。对于我来说,为什么你甚至检查EOF是不清楚的,因为你会继续阅读外部while循环恢复。如果f_Aux_Pointer到了最后,该函数不应该返回吗?我不确定你在那里做什么。另外没有策略说明换行符不在前49个读取字符中我认为你应该在这里重新考虑你的策略。

这样做也会更容易:

 if(fgets(line, sizeof line, f_Aux_Pointer) == NULL) { // error handling return; // perhaps this? } line[strcspn(line, "\n")] = 0; // removing the newline if(strlen(line) > 12) ... 

这里

 while(c != ','){ //id c = fgetc(f_Data_Pointer); if(c != ','){ data->id[counter] = c; counter++; } printf("%c", c); } 

你有与上面相同的错误,你永远不会设置\ 0-终止字节。 在结束的while你应该有data->id[counter] = 0; 。 在下一个while循环中同样的事情。

generate_nodes您不需要为时间graph_node对象分配内存,因为insert_node无论如何都会创建一个副本。 你可以做:

 graph_node data; ... data.id[counter] = c; ... data.node_name[counter] = c; ... insert_node(graph, &data); data.id[0] = 0; data.node_name[0] = 0; 

而且你会担心一个malloc


编辑

因为您的ID始终是数字的,所以我将结构更改为:

 struct Graph_Node{ int id; char node_name[50]; struct Graph_Node* next; struct Graph_Node* edges; }; 

这将使生活更轻松。 如果您的条件为真,只有您需要的行超过12个字符,并且您感兴趣的行总是具有相同的格式(逗号之间没有空格,第二列始终是引号),就像您在答案中发布的那样然后你可以像这样解析它:

 int generate_nodes(graph_node **graph, const char *file_name) { if(file_name == NULL || graph == NULL) return 0; // return error // no need to allocate memory for it // if the insert_node is going to make a // copy anyway struct Graph_Node data = { .next = NULL, .edges = NULL }; FILE *fp = fopen(file_name, "r"); if(fp == NULL) { fprintf(stderr, "Error opening file %s: %s\n", file_name, strerror(errno)); return 0; } // no line will be longer than 1024 // based on your conditions char line[1024]; size_t linenmr = 0; while(fgets(line, sizeof line, fp)) { linenmr++; // getting rid of the newline line[strcspn(line, "\n")] = 0; if(strlen(line) <= 12) continue; // resume reading, skipt the line char *sep; long int id = strtol(line, &sep, 0); // assuming that there is not white spaces // before and after the commas if(*sep != ',') { fprintf(stderr, "Warning, line %lu is malformatted, ',' exepcted\n", linenmr); continue; } data.id = id; // format is: "....", if(sep[1] != '"') { fprintf(stderr, "Warning, line %lu is malformatted, \"\", exepcted\n", linenmr); continue; } // looking for ", char *endname = strstr(sep + 2, "\","); if(endname == NULL) { fprintf(stderr, "Warning, line %lu is malformatted, \"\", exepcted\n", linenmr); continue; } // ending string at ", // easier to do strcnpy *endname = 0; strncpy(data.node_name, sep + 2, sizeof data.node_name); data.node_name[sizeof(data.node_name) - 1] = 0; insert_node(graph, &data); } fclose(fp); return 1; } 

现在有趣的是这些:

  char *sep; long int id = strtol(line, &sep, 0); // assuming that there is not white spaces // before and after the commas if(*sep != ',') { fprintf(stderr, "Warning, line %lu is malformatted, ',' exepcted\n", linenmr); continue; } 

strtol是一个将字符串中的数字转换为实际整数的函数。 此函数优于atoi ,因为它允许您转换不同基数的数字,从二进制到hex。 第二个优点是它告诉你它停止阅读的位置。 这对于错误检测非常有用,我使用这个方法来检测该行是否具有正确的格式。 如果该行具有正确的格式,则必须在数字旁边显示逗号,我检查这一点,如果不是这样,我打印错误消息并跳过该行并继续读取该文件。

下一个if检查下一个字符是否是引号" ,因为根据你的文件,第二个参数用引号括起来。遗憾的是,参数的separtor是一个逗号,它也被用作字符串中的普通字符。这使得事情有点复杂(你不能在这里使用strtok )。再一次,我假设整个论点以",结尾。 请注意,这不会考虑转义引号。 像这样的一行:

 3,"somefunc(a,b,\"hello\",d)","OR",0 

会被错误地解析。 当",找到时,我将引号设置为'\0'以便更容易使用strncpy ,否则我将不得不计算字符串的长度,检查它的长度是否超过目标大小等等如果你需要继续解析, endname + 1应该指向下一个逗​​号,否则格式错误。

  strncpy(data.node_name, sep + 2, sizeof data.node_name); data.node_name[sizeof(data.node_name) - 1] = 0; 

在这里我复制字符串。 源是sep + 2 ,因为sep指向第一个逗号,所以你必须跳过它。 下一个字符是引号,所以你也必须跳过它,因此sep + 2 。 因为名称的长度是未知的,所以最好使用strncpy ,这样可以确保不会写入超出需要的字节数。

最后一步是将节点插入图表中。 请注意,我没有分配内存,因为我知道insert_node无论如何都要创建一个新的副本。 因为data只是临时使用,所以不需要分配内存,只需将指针传递给data (通过传递&data )到insert_node

我用虚拟graph和虚拟insert_node测试了这个函数,它只打印节点:

 void insert_node(graph_node** node, graph_node* data) { printf("ID: %d, node_name: %s\n", data->id, data->node_name); } 

这是输出:

 ID: 1, node_name: execCode(workStation,root) ID: 2, node_name: RULE 4 (Trojan horse installation) ID: 3, node_name: accessFile(workStation,write,'/usr/local/share') ID: 4, node_name: RULE 16 (NFS semantics) ID: 5, node_name: accessFile(fileServer,write,'/export') ID: 6, node_name: RULE 10 (execCode implies file access) ID: 7, node_name: canAccessFile(fileServer,root,write,'/export') 

现在,如果您使用此代码并且不断获得valgrind错误,那么这意味着您仍未在代码的其他部分中出现错误,而这些部分尚未向我们展示。