为什么这个sscanf修改了一些我不想修改的数据?

Why does this sscanf modify some data i don't want to be modified?

我正在读取和保存格式化文件中的字符串,出于某种原因,我发现 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 firstwordaddent secondword,就在 sscanf 附近,它应该会显示 testa_e->ident 内容已更改,我想知道为什么以及如何解决这个问题, 因为我真的不知道...

在函数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 )'[=15=]' ) );

        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;

是一种不好的做法。

主要问题是(除了@VladFromMoscow 在他的回答中提到的那个),在 addent() 中你只存储 pointer id_ent 在你的结构:

curs->ident=id_ent;

但这只是 temp2 的地址,所以如果您通过调用 sscanf() 将其他内容复制到 temp2,您将在 [=17] 中看到新值=] 也是。

将上面的行形式改为

curs->ident=strdup(id_ent);

创建副本。 并且不要忘记在释放 curs

之前调用 free(curs->ident)