【问题标题】:Why does this sscanf modify some data i don't want to be modified?为什么这个 sscanf 会修改一些我不想修改的数据?
【发布时间】:2019-12-17 09:38:28
【问题描述】:

我正在读取和保存格式化文件中的字符串,但由于某种原因,我发现 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-&gt;ident内容已经改变,我想知道为什么以及如何解决这个问题,因为我真的不知道。 ..

【问题讨论】:

  • 这不是问题,但看起来你的 addent 函数不能添加多个。遍历列表直到cursNULL 的循环等效于nodo_id* curs=NULL;
  • 你能展示完整的会话,包括你的输入和所有输出吗?
  • 我的电脑现在死机了,我正在打电话我收到费用后会尽快处理,“完整会话”是指我的完整代码吗?
  • 我不知道你输入的字符串有多大,但是scanf的众多问题之一是不方便正确防止缓冲区溢出。但是您可能想使用sscanf(temp,"%29s %29s",temp5,temp2),它应该避免溢出temp5temp2。 (如果您更改数组的大小,即如果您更改 string,请记住更改 %s 指令中的数字。)

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


【解决方案1】:

在函数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;

是一种不好的做法。

【讨论】:

  • @Aconcagua 如果他会使用他需要的标准 C 函数。
  • 取决于她/他之后要对列表做什么,但这超出了当前的问题。对于列表节点中的纯赋值,为什么我需要指向非常量的指针?
  • new_nodo_id 附加到列表并更改原始*head 当且仅当它是NULL 的部分非常聪明,但也有点棘手。至少我不得不三思而后行。我认为它值得评论
  • 所以在我为nodo_id 执行malloc 并将字符串存储在其中之后,它实际上并没有复制到新的内存区域中,但ident 仍然指向temp2 的数据数组?
  • @wattbatt 你在写。数据成员 ident 的值是数组 temp3 的第一个元素的地址。所以所有节点都会有相同的地址。
【解决方案2】:

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

curs->ident=id_ent;

但这只不过是temp2 的地址,所以如果您通过调用sscanf() 将其他内容复制到temp2,您也会在testa_e-&gt;ident 中看到新值。

将上面的线形改为

curs->ident=strdup(id_ent);

创建一个副本。 在释放 curs 之前不要忘记致电 free(curs-&gt;ident)

【讨论】:

    猜你喜欢
    • 2019-06-25
    • 1970-01-01
    • 2019-07-26
    • 2014-10-07
    • 2019-09-02
    • 2022-01-01
    • 1970-01-01
    • 2011-04-27
    • 1970-01-01
    相关资源
    最近更新 更多