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

时间:2018-02-13 20:52:18

标签: c fault

我有一个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 < strlen(data->id); i++)
        temp->id[i] = data->id[i];

    for(int i = 0; i < strlen(data->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-&gt; id和data-&gt; 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)

1 个答案:

答案 0 :(得分:3)

您的代码中最大的问题是您经常忽略它 '\0' - 终止字节并传递给strlen等期望a的函数 有效字符串(以0结尾的字符串)。

例如insert_node

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

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

如果您知道源字符串小于,请使用strcpy 49个字符长,或strncpy完全确定:

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

node_name相同。您也不会检查malloc是否返回 NULL

foef行也是错误的,请参阅Why while(!foef(...)) is always wrong

你应该重写这个

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, '<id>,' exepcted\n", linenmr);
            continue;
        }

        data.id = id;

        // format is: "....",

        if(sep[1] != '"')
        {
            fprintf(stderr, "Warning, line %lu is malformatted, \"<string>\", exepcted\n", linenmr);
            continue;
        }

        // looking for ",
        char *endname = strstr(sep + 2, "\","); 

        if(endname == NULL)
        {
            fprintf(stderr, "Warning, line %lu is malformatted, \"<string>\", 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, '<id>,' exepcted\n", linenmr);
        continue;
    }

strtol是一个将字符串中的数字转换为实际的函数 整数。此功能优于atoi,因为它允许您转换 不同基数的数字,从二进制到十六进制。第二个好处是 它告诉你它停止阅读的地方。这非常适合错误检测, 我使用这个behviour来检测线条是否具有正确的格式。如果 line具有正确的格式,数字旁边必须出现逗号,,我检查 为此,如果不是这样,我打印一条错误消息并跳过该行 继续阅读文件。

下一个if检查下一个字符是否为引用",因为根据您的 file,第二个参数用引号括起来。可悲的是,他的副手 arguments是一个逗号,也用作字符串中的普通字符。 这使事情变得更复杂(你不能在这里使用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指向。{1} 第一个逗号,所以你必须跳过它。下一个字符是引用,所以你 也必须跳过它,因此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错误,那么这意味着 你仍未在代码的其他部分中出现错误 我们。