>我正在尝试将从文本文件读取的字符串添加到链表中。 由于我不知道文件或字符串有多长,我想动态执行此操作。 但是在某条线上,我遇到了分割错误。 我已经尝试了一切,但我认为我忽略了一些关键的东西。 有人可以告诉我我做错了什么吗?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct node {
char *name;
int age;
struct node* next;
}node;
node *add(node *head, char* n_m){
node *new_node;
new_node = (node*)malloc(sizeof(node));
if(new_node == NULL)
printf("Fehler bei Speicher reservierung...");
new_node->name = (char*)malloc(100*sizeof(char));
if(new_node->name == NULL)
printf("Fehler bei Speicher reservierung...");
strcpy(new_node->name, n_m);
if(head == NULL){
head = new_node;
head->next = NULL;
return head;
}
node *current;
current = head;
while(current->next != NULL){
current = current->next;
}
current->next = new_node;
new_node->next = NULL;
return head;
}
void print(node *head){
node *current;
current = head;
while(current != NULL){
printf("%sn", current->name);
current = current->next;
}
}
int main(){
node *head = NULL;
char character;
FILE *fp;
fp = fopen("test.txt", "r");
while ((character = fgetc(fp)) != EOF) {
char *n_m;
n_m = (char*)malloc(100 * sizeof(char));
if(n_m == NULL)
printf("Fehler bei Speicher reservierung...");
int i = 0;
while (character != ' ') {
n_m[i++] = character;
character = fgetc(fp);
}
n_m[++i] = ' '; // NULL-terminate
head = add(head, n_m);
free(n_m);
}
print(head);
return 0;
}
你最大的问题是你对字符的读取没有捕获EOF
,并且在遇到EOF
后会继续读取,导致i
超出你的数组边界,调用未定义的行为,导致你的SegFault。有问题的代码是:
while (character != ' ') {
n_m[i++] = character;
character = fgetc(fp);
}
由于 POSIX 文件不以' '
(空格(结尾,而是以'n'
结尾,因此您对文件中最后一个单词的读取不会在EOF
处停止。此外,您还会遇到多个空格一起重复将包含空字符串的节点写入列表的问题。
您也无法处理除space
以外的任何其他空格,这意味着您在存储的单词中包含't'
、'n'
、垂直制表符等。与其使用' '
检查space
,不如使用ctype.h
中包含的isspace()
宏。
n_m[++i] = ' ';
应该是n_m[i] = ' ';
.您用n_m[i++] = character;
递增i
。您不希望在nul 终止字符串之前使用预递增运算符再次递增i
。这会导致字符串中的最后一个字符不确定(在尝试读取字符串时再次调用未定义的行为(
解决这些问题(并使用c
而不是character
,ndx
而不是i
,buf
而不是n_m
(,您对列表的读取和add()
类似于:
while ((c = fgetc(fp)) != EOF) { /* read each char in file */
if (isspace(c) || ndx == MAXC - 1) { /* is space or buf full? */
if (in) { /* were we within word? */
buf[ndx] = 0; /* nul-terminate */
head = add (head, buf); /* add node to list */
}
if (ndx < MAXC - 1) /* buffer not full */
in = 0; /* set in flag zero */
ndx = 0; /* reset index zero */
}
else { /* otherwise */
buf[ndx++] = c; /* add char to buf */
in = 1; /* set in flag 1 */
}
}
(注意:使用变量in
作为in/out
标志来跟踪您是否在单词内读取字符,或者在单词之间读取空格可以解决序列中多个空格字符的问题,例如"hello world"
(
可选但有用的是,在分配还包含需要分配的成员的节点时,编写一个完全分配和初始化所有节点成员的createnode()
函数,而不是将该代码放在add()
中。它保持清洁,并确保你分配的每个节点在add()
使用之前都完全初始化。例如:
/** create new node, allocate and initialize all member values,
* return pointer to node on success, NULL otherwise.
*/
node *createnode (char *s)
{
node *new_node;
new_node = malloc (sizeof *new_node); /* allocate/validate node */
if (new_node == NULL) {
perror ("malloc-Fehler bei Speicher reservierung...");
return NULL;
}
new_node->name = malloc (strlen (s) + 1); /* allocate/validate name */
if (new_node->name == NULL) {
perror ("malloc-Fehler bei Speicher reservierung...");
return NULL;;
}
strcpy (new_node->name, s); /* initialize all node values */
new_node->age = 0;
new_node->next = NULL;
return new_node; /* return newly allocated/initialized node */
}
然后,您的add()
函数简化为:
/** add node containing allocated string 'n_m' to list, return pointer
* to 1st node in list on success, exit with failure otherwise.
*/
node *add (node *head, char *n_m)
{
node *new_node = createnode (n_m); /* allocate/initialize new node */
node *current = head; /* pointer to current head */
if (new_node == NULL) /* validate allocation */
exit (EXIT_FAILURE);
if (!head) /* handle 1st node */
return new_node;
while (current->next != NULL) /* iterate to end of list */
current = current->next;
current->next = new_node; /* set next node to new_node */
return head; /* return pointer to head */
}
将其放在一起并添加一个del_list()
函数以释放列表的所有已分配内存,您可以执行以下操作:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAXC 1024
typedef struct node {
char *name;
int age;
struct node *next;
} node;
/** create new node, allocate and initialize all member values,
* return pointer to node on success, NULL otherwise.
*/
node *createnode (char *s)
{
node *new_node;
new_node = malloc (sizeof *new_node); /* allocate/validate node */
if (new_node == NULL) {
perror ("malloc-Fehler bei Speicher reservierung...");
return NULL;
}
new_node->name = malloc (strlen (s) + 1); /* allocate/validate name */
if (new_node->name == NULL) {
perror ("malloc-Fehler bei Speicher reservierung...");
return NULL;;
}
strcpy (new_node->name, s); /* initialize all node values */
new_node->age = 0;
new_node->next = NULL;
return new_node; /* return newly allocated/initialized node */
}
/** add node containing allocated string 'n_m' to list, return pointer
* to 1st node in list on success, exit with failure otherwise.
*/
node *add (node *head, char *n_m)
{
node *new_node = createnode (n_m); /* allocate/initialize new node */
node *current = head; /* pointer to current head */
if (new_node == NULL) /* validate allocation */
exit (EXIT_FAILURE);
if (!head) /* handle 1st node */
return new_node;
while (current->next != NULL) /* iterate to end of list */
current = current->next;
current->next = new_node; /* set next node to new_node */
return head; /* return pointer to head */
}
void print (node * head)
{
node *current;
current = head;
while (current != NULL) {
printf ("%sn", current->name);
current = current->next;
}
}
/** delete all nodes in list */
void del_list (node *head)
{
node *pn = head; /* pointer to iterate */
while (pn) { /* iterate over each node */
node *victim = pn; /* set victim to current */
pn = pn->next; /* advance pointer to next */
free (victim->name); /* free current string */
free (victim); /* free current node */
}
}
int main (int argc, char **argv) {
char buf[MAXC];
int c = 0, in = 0, ndx = 0;
node *head = NULL;
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
while ((c = fgetc(fp)) != EOF) { /* read each char in file */
if (isspace(c) || ndx == MAXC - 1) { /* is space or buf full? */
if (in) { /* were we within word? */
buf[ndx] = 0; /* nul-terminate */
head = add (head, buf); /* add node to list */
}
if (ndx < MAXC - 1) /* buffer not full */
in = 0; /* set in flag zero */
ndx = 0; /* reset index zero */
}
else { /* otherwise */
buf[ndx++] = c; /* add char to buf */
in = 1; /* set in flag 1 */
}
}
if (fp != stdin) /* close file if not stdin */
fclose (fp);
print (head); /* print list */
del_list (head); /* free all allocated memory */
}
示例输入文件
$ cat dat/captnjack.txt
This is a tale
Of Captain Jack Sparrow
A Pirate So Brave
On the Seven Seas.
示例使用/输出
$ ./bin/ll_name_age dat/captnjack.txt
This
is
a
tale
Of
Captain
Jack
Sparrow
A
Pirate
So
Brave
On
the
Seven
Seas.
内存使用/错误检查
在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您有 2个责任:(1(始终保留指向内存块起始地址的指针,以便 (2( 当不再需要内存块时可以释放它。
必须使用内存错误检查程序来确保不会尝试访问内存或超出/超出分配块边界的写入,不会尝试读取或基于未初始化值的条件跳转,最后确认释放了已分配的所有内存。
对于Linux来说,valgrind
是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。
$ valgrind ./bin/ll_name_age dat/captnjack.txt
==18265== Memcheck, a memory error detector
==18265== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==18265== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==18265== Command: ./bin/ll_name_age dat/captnjack.txt
==18265==
This
is
a
tale
Of
Captain
Jack
Sparrow
A
Pirate
So
Brave
On
the
Seven
Seas.
==18265==
==18265== HEAP SUMMARY:
==18265== in use at exit: 0 bytes in 0 blocks
==18265== total heap usage: 35 allocs, 35 frees, 6,132 bytes allocated
==18265==
==18265== All heap blocks were freed -- no leaks are possible
==18265==
==18265== For counts of detected and suppressed errors, rerun with: -v
==18265== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
始终确认已释放已分配的所有内存,并且没有内存错误。
仔细查看,如果您有其他问题,请告诉我。
您需要防止读取超出文件末尾和超出已分配的内存。 也可能您的文件在经过缓冲区末尾i
通过后缀和前缀在 100 个字符处有一行"长行"。
while ((character = fgetc(fp)) != EOF) {
char *n_m;
n_m = (char*)malloc(100 * sizeof(char));
if(n_m == NULL)
printf("Fehler bei Speicher reservierung...");
int i = 0;
while ((character != ' ')
&& (character != EOF)
&& (i < 100)) {
n_m[i++] = character;
character = fgetc(fp);
}
// don't postfix add about then prefix add below
if (i >= 100) {
… there is a problem …
}
n_m[i] = ' '; // NULL-terminate
head = add(head, n_m);
free(n_m);
}
你可能会考虑更像这样的东西
#define BUFF_SIZE 100
char buff[BUFF_SIZE];
buff[0] = ' ';
int i = 0;
while (((character = fgetc(fp)) != EOF)
&& (i < BUFF_SIZE)) {
buff[i++] = character;
character = fgetc(fp);
if (character = ' ') {
buff[i] = ' '; // NULL-terminate
head = add(head, buff);
i = 0;
buff[0] = ' ';
}
}
if (i >= BUFF_SIZE) { … there is a problem … }
这做了一些有用的事情。 一个是缓冲区是静态分配的,它的大小由单个 #define 控制。 其次,它减少了所涉及的循环数量,从而提高了可读性。