为什么此sscanf会修改一些我不想修改的数据?

时间:2019-08-09 15:54:17

标签: c list scanf undefined-behavior singly-linked-list

我正在从格式化的文件中读取并保存字符串,由于某种原因,我发现sscanf()更改了testa_e->ident的内容。

我放了一些printf,发现问题发生在sscanf()之后。我还通过打印检查了temp2temp5testa_e的地址,但它们是不同的。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define string 30
#define line 100

typedef const char *identifier;

struct nodo_id {
    identifier ident;
    struct nodo_id *next;
};
typedef struct nodo_id nodo_id;

nodo_id *testa_e = NULL;

void addent(const char *id_ent) {
    if (testa_e == NULL) {
        testa_e = malloc(sizeof(nodo_id));
        testa_e->ident = id_ent;
        testa_e->next = NULL;
    } else {
        nodo_id *curs = testa_e;
        while (curs != NULL) {
            curs = curs->next;
        }
        curs = malloc(sizeof(nodo_id));
        curs->ident = id_ent;
        curs->next = NULL;  
    }
}

int main() {
    char temp[line];
    char temp2[string];
    char temp5[string];

    fgets(temp, line, stdin);
    while (strncmp(temp, "end", 3) != 0) {
        if (strncmp(temp, "addent", 6) == 0) {
            if (testa_e != NULL)
                printf("\nbefore sscanf: %s\n", testa_e->ident);
            sscanf(temp, "%s %s", temp5, temp2); 
            if (testa_e != NULL)
                printf("\nafter sscanf: %s\n", testa_e->ident);
            addent(temp2);
        }
        fgets(temp, line, stdin);
    }
}

这里的代码重现了完全相同的问题;启动后,在终端上addent firstword周围写addent secondwordsscanf,它应该向您显示testa_e->ident的内容已更改,我想知道为什么以及如何解决此问题,因为我真的不知道...

2 个答案:

答案 0 :(得分:4)

在函数addent中,此循环

        while(curs!=NULL){
            curs=curs->next;
        }

反复进行,直到curs等于NULL

然后您要更改指针

    curs=malloc(sizeof(nodo_id));
    curs->ident=id_ent;
    curs->next=NULL; 

列表本身未更改。您仅更改了局部变量curs

按以下方式更改循环

        while ( curs->next != NULL ){
            curs = curs->next;
        }

然后

    curs->next = malloc( sizeof( nodo_id ) );
    curs->next->ident = id_ent;
    curs->next->next = NULL; 

另一个问题是您正在使用指向本地数组的指针

char temp2[string];
//...
addent(temp2);

因此所有节点都将指向数组中存储的最后一个。您需要为将存储在列表中的每个字符串动态分配内存,并将地址分配给数据成员ident。在这种情况下,您必须从其声明中删除限定符const

请注意,使函数依赖于全局变量是一个坏主意,

函数addent的更好定义可以如下所示

struct nodo_id{
    char *ident;
    struct nodo_id* next;
};
typedef struct nodo_id nodo_id;

int addent( nodo_id **head, const char *id_ent )
{
    nodo_id *new_nodo_id = malloc( sizeof( nodo_id ) );
    int success = new_nodo_id != NULL;

    if ( success )
    {
        new_nodo_id->ident = malloc( strlen( id_ent ) + sizeof( ( char )'\0' ) );

        success = new_nodo_id->ident != NULL;

        if ( ! success )
        {
            free( new_nodo_id );
        }
        else
        {
            strcpy( new_nodo_id->ident, id_ent );
            new_nodo_id->next = NULL;

            while ( *head != NULL ) head = &( *head )->next;

            *head = new_nodo_id;
        }
    }

    return success;
}

该函数可以像

那样调用
addent( &testa_e, temo2 );

为什么在函数中使用了指向头部的指针?

首先,如果要更改原始磁头,则需要通过引用传递它。其次在循环中

while ( *head != NULL ) head = &( *head )->next;

同样,指针指向最后一个节点的数据成员next。因此,我们不会像在函数实现中那样更改局部变量curs,而是最后一个节点的数据成员next。因此,我们正在更改列表本身。

请注意将typedef定义为

typedef const char* identifier;

是一个坏习惯。

答案 1 :(得分:2)

主要问题是(除了他的答案中提到的@VladFromMoscow之外),在addent()中,您仅将指针 id_ent存储在结构中:

curs->ident=id_ent;

但这只是temp2的地址,因此,如果您通过调用temp2将其他内容复制到sscanf()中,则会在testa_e->ident中看到新值

将上面的线形更改为

curs->ident=strdup(id_ent);

创建副本。 而且,释放free(curs->ident)

之前,别忘了打电话给curs