C:fscanf作为while参数的问题

时间:2015-02-08 00:18:33

标签: c fopen

该程序部分应该读取未知数量的名称和ID号,并将它们编译成链表。名字排在第一位,最后是id(乔治华盛顿,188867)。我已经将问题缩小到了我的fopen声明,我无法弄清楚是什么导致它崩溃我的程序。

head也与我的struct同时声明为null。

struct studentInfo{
    char name[50];
    int id;
    struct studentInfo *next;
}*head = NULL;

void createList(){
    FILE *ofp;
    struct studentInfo *new_node = (struct studentInfo *)malloc(sizeof(struct studentInfo));
    struct studentInfo *temp = (struct studentInfo *)malloc(sizeof(struct studentInfo));
    char firstName[25], lastName[25];

    new_node = head;
    temp = head;

    ofp = fopen("C:\\Users\\Brent Rademaker\\Desktop\\COP3502C\\Assignment 1\\AssignmentOneInput.txt", "r"); 


    while(fscanf(ofp, "%s %s %d", &firstName, &lastName, &new_node->id) != EOF){
        strncat((new_node->name), firstName, 10);
        strncat((new_node->name), lastName, 10);
        new_node->next = NULL;

        if(head == NULL){
            head = new_node;
        }

        temp->next = new_node;
        temp = temp->next;
    }
    fclose(ofp);
}

main()
{
    createList();

    return 0;
}

编辑:在代码中添加了struct和main,试图弄清楚如何使用errno和sterror来提供更多信息

edit2:感谢您的建议,问题不再是我的fopen了,而是我的while循环变量。我不确定在哪里发现可以解决问题的麻烦。

1 个答案:

答案 0 :(得分:1)

假设您说文件已成功打开是正确的,则会发生崩溃,因为您在new_node = head;时设置head == NULL;,然后尝试读取&new_node->id中的数字,取消引用null指针。实际上,即使文件无法打开也可能发生崩溃,但这只是一个问题,即哪个空指针将首先被解除引用。

此代码中存在许多问题。你是:

  • 泄漏记忆,
  • 不检查错误返回
  • 不将输入字符串限制为变量的大小
  • char (*)[25]值传递给期望char *
  • 的函数
  • 取消引用空指针
  • 不在名称组件之间添加空格
  • 在非空终止字符串的内存块的末尾连接,
  • 没有为每个学生阅读分配新的空间,
  • 容易受到输入数据错误的影响,从而导致错误处理ID号。

在重写代码时会导致大量注释。

struct studentInfo
{
    char name[50];
    int id;
    struct studentInfo *next;
} *head = NULL;

static const char filename[] =
    "C:\\Users\\Brent Rademaker\\Desktop\\COP3502C\\Assignment 1\\AssignmentOneInput.txt";

// Pass file name as argument so that multiple files can be processed
void createList(const char *file)
{
    // I'd use ofp for an output file; fp when there's one file, or
    // ifp for an input file
    FILE *ofp;
    // struct studentInfo *new_node = (struct studentInfo *)malloc(sizeof(struct studentInfo));
    // struct studentInfo *temp = (struct studentInfo *)malloc(sizeof(struct studentInfo));
    struct studentInfo *tail = head;  // Major change
    char firstName[25];
    char lastName[25];
    int id;  // Added

    // Originally:
    // new_node = head;  // Overwrites newly allocated memory with NULL (leak 1)
    // temp = head;      // Overwrites newly allocated memory with NULL (leak 2)

    ofp = fopen(file, "r"); 
    if (ofp == NULL)
    {
        fprintf(stderr, "failed to open file %s for reading\n", filename);
        exit(1);
    }

    // Traverse to end of list (necessary on second call)
    while (tail != NULL && tail->next != NULL)
        tail = tail->next;

    // Limit strings to size of variables (size - 1 specified)
    // Test that you get 3 values; if you only get two, the ID is invalid.
    while (fscanf(ofp, "%24s %24s %d", firstName, lastName, &id) == 3)
    {
        // Originally:
        // strncat((new_node->name), firstName, 10);
        // strncat((new_node->name), lastName, 10);
        // These are appending to the end of a string that is not guaranteed
        // to be null terminated.  The names were not limited to 10 bytes.
        // There is no space between the first and last names in the concatenated string.

        // Allocate new node when student information read correctly.
        // Cast left in place since compiler may be a C++ compiler compiling C
        struct studentInfo *new_node = (struct studentInfo *)malloc(sizeof(*new_node));
        if (new_node == NULL)
            break;
        // This sequence is safe because firstname contains up to 24 bytes plus null,
        // lastName contains up to 24 bytes plus null, and new_node->name can
        // hold 24 + 1 + 24 + 1 = 50 bytes.
        strcpy(new_node->name, firstName);
        strcat(new_node->name, " ");
        strcat(new_node->name, lastName);
        // If need be, use strcpy_s() and strcat_s()
        // strcpy_s(new_node->name, sizeof(new_node->name), firstName);
        // strcat_s(new_node->name, sizeof(new_node->name), " ");
        // strcat_s(new_node->name, sizeof(new_node->name), lastName);
        new_node->id = id;
        new_node->next = NULL;

        // Add new node to end of list
        if (head == NULL)
            head = new_node;
        else
            tail->next = new_node;
        tail = new_node;
        // Alternatively, and more simply, add new node to head of list
        // Don't need the tail variable any more, or any special case code
        // new_node->next = head;
        // head = new_node;
    }
    fclose(ofp);
}

int main(void)
{
    createList(filename);

    return 0;
}

替代测试代码

我使用此代码测试main()函数,而不是现有的存根createList()

static void check_file(const char *file)
{
    struct studentInfo *node;

    printf("Before %s\n", file);
    createList(file);
    printf("After %s\n", file);
    node = head;
    while (node != NULL)
    {
        printf("%.5d %s\n", node->id, node->name);
        node = node->next;
    }
    printf("Done %s\n", file);
}

int main(void)
{
    check_file("data.1");
    check_file("data.2");
    return 0;
}

测试数据 - data.1

Firstname LastName 1234
Abby Holmes 2345
PersonWithVeryLongFirst AndWithVeryLongLastName 3456

测试数据 - data.2

Firstname LastName 12784
Abby Holmes 27845
PersonWithVeryLongFirst AndWithVeryLongLastName 78456

示例输出:

$ ./stud
Before data.1
After data.1
01234 Firstname LastName
02345 Abby Holmes
03456 PersonWithVeryLongFirst AndWithVeryLongLastName
Done data.1
Before data.2
After data.2
01234 Firstname LastName
02345 Abby Holmes
03456 PersonWithVeryLongFirst AndWithVeryLongLastName
12784 Firstname LastName
27845 Abby Holmes
78456 PersonWithVeryLongFirst AndWithVeryLongLastName
Done data.2
$

中间名和其他esoterica

  

有没有办法可以让它变量?例如,如果有人有中间名?

是的,有办法做到这一点,但当然,它们更难。考虑到英国皇室每个人都有大量名字的方式(在你处理标题等之前),你可能会得出结论,名字的数量有一个上限,例如8.你需要回到fgets()sscanf()处理。然后您可以使用:

typedef char Name[25];
Name n[9];          // At most 8 names plus an ID
char buffer[4096];
while (fgets(buffer, sizeof(buffer), ofp) != NULL)
{
    int n_scan = fscanf(ofp, "%s %s %s %s %s %s %s %s %s",
                        n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7], n[8]);
    if (n_scan < 3)
        …format error…
    else
    {
        …process elements n[0] to n[n_scan-2] as names…
        …process n[n_scan-1] as id…with sscanf() probably…
        …add the information to the list…
    }
}

有很多方法可以检测你是否使用过该行中的所有内容,但它们更加深奥。您还可以考虑简单地读取该行,然后向后扫描,忽略尾随空格,然后检查最后一个“单词”是否为ID,并简单地使用所有先前的材料作为“名称”。但是,有了这个,您需要考虑您的结构是否应该包含char *name;元素,以便将名称复制(strdup()?)。它使'free()`进程变得复杂(你需要在释放节点之前释放名称),但这通常是一个明智的惩罚。

您还可以查找How to use sscanf() in loops?来迭代该行中的文字。

您还可以考虑使用C99'灵活阵列成员'(FAM)作为名称;它必须是结构的最后一部分。由于必须动态分配包含FAM的结构,因此将它们与列表一起使用会带来一些问题。您可能或可能不需要记录名称的长度 - 这取决于您是否要更改现有节点中的名称。 (如果你不愿意,strlen(node->name)就足够了;如果你愿意的话,你需要知道新名称是否有足够的空间,当前名称可能比可用空间短。)总的来说,我认为FAM太复杂了,你本周无法可靠地管理;你现在需要比现在更舒服。