![](/img/trans.png)
[英]Seg fault when reading a line into dynamically allocated char pointer array
[英]Dynamically allocated string in dynamic structure array(seg fault)
我想將整個文件(逐行)讀取到結構數組中的字符指針“名稱”中。(想要將名稱(可以是任意長度)保留在動態分配的字符串中然后我將讀取的字符串(名稱) 到結構中的塊(年齡名稱分數)。我得到段錯誤。(文件格式為:
age name score
25,Rameiro Rodriguez,3
30,Anatoliy Stephanos,0
19,Vahan: Bohuslav,4.2
struct try{
double age;
char *name;
double score;
};
void allocate_struct_array(struct try **parr,int total_line);
int main(){
int count=0,i=0;
char ch;
fileptr = fopen("book.txt", "r");
//total line in the file is calculated
struct try *parr;
allocate_struct_array(&parr,count_lines);
//i got segmentation fault at below.(parsing code is not writed yet just trying to read the file)
while((ch=fgetc(fileptr))!=EOF) {
count++;
if(ch=='\n'){
parr->name=malloc(sizeof(char*)*count+1);
parr[i].name[count+1]='\0';
parr+=1;
count=0;
}
}
fclose(fileptr);
}
void allocate_struct_array(struct try **parr,int total_line){
*parr = malloc(total_line * sizeof(struct try));
}
繼續我的評論,在allocate_struct_array(struct try **parr,int total_line)
中,您分配的是struct try
塊而不是指針塊(例如struct try*
)。 你的分配parr->name=malloc(sizeof(char*)*count+1);
嘗試分配count + 1
指針。 此外,在每次迭代中,您都會覆蓋parr->name
持有的地址,從而創建 memory 泄漏,因為指向先前分配的指針丟失並且無法釋放。
在您編寫的任何動態分配 memory 的代碼中,對於分配的 memory 的任何塊,您有兩個責任:(1)始終保留指向 ZCD69B4957F06CD818D7BF3D21980 塊的起始地址的指針,所以它可以被釋放,更需要。
解決問題的更好方法是將每一行讀入一個簡單的字符數組(大小足以容納每一行)。 然后,您可以分開age
、 name
和score
並確定name
中的字符數,以便您可以正確分配parr[i].name
,然后您可以在分配后復制名稱。 如果你很小心,你可以簡單地在緩沖區中找到兩個','
,為parr[i].name
分配,然后使用sscanf()
和適當的格式字符串來分隔、轉換和復制所有值到你的結構parr[i]
在一次調用中。
由於您無法確定//total line in the file is calculated
,我們將假設一個足夠大的數字來容納您的示例文件以進行討論。 找到那個號碼是留給你的。
要將每一行讀入一個數組,只需聲明一個足夠大的緩沖區(字符數組)以容納每一行(取最長的預期行並乘以 2 或 4,或者如果在典型的 PC 上,只需使用1024
或2048
的緩沖區字節將容納除晦澀文件之外的所有文件,其行長。(規則:不要吝嗇緩沖區大小!! )你可以這樣做,例如
#define COUNTLINES 10 /* if you need a constant, #define one (or more) */
#define MAXC 1024
#define NUMSZ 64
...
int main (int argc, char **argv) {
char buf[MAXC]; /* temporary array to hold each line */
...
在循環中讀取直到'\n'
或EOF
時,更容易連續循環並檢查循環內的EOF
。 這樣,最后一行將作為讀取循環的正常部分處理,您不需要特殊的最終代碼塊來處理最后一行,例如
while (nparr < count_lines) { /* protect your allocation bounds */
int ch = fgetc (fileptr); /* ch must be type int */
if (ch != '\n' && ch != EOF) { /* if not \n and not EOF */
...
}
else if (count) { /* only process buf if chars present */
...
}
if (ch == EOF) { /* if EOF, now break */
break;
}
}
(注意:對於您的示例,我們繼續使用您使用的fgetc()
進行閱讀,但在正常實踐中,您只需使用fgets()
用該行填充字符數組)
要查找數組中的第一個和最后一個','
,您可以簡單地#include <string.h>
並使用strchar()
查找第一個和 strrchr( strrchr()
查找最后一個。 使用指針和結束指針設置為第一個和最后一個','
名稱中的字符數變為ep - p - 1;
. 您可以通過以下方式找到','
並找到名稱的長度:
char *p = buf, *ep; /* pointer & end-pointer */
...
/* locate 1st ',' with p and last ',' with ep */
if ((p = strchr (buf, ',')) && (ep = strrchr (buf, ',')) &&
p != ep) { /* confirm pointers don't point to same ',' */
size_t len = ep - p - 1; /* get length of name */
一旦你找到了第一個','
和第二個','
並確定了name
中的字符數,你就分配了字符,而不是指針,例如,在name
中使用len
個字符和nparr
作為結構索引(而不是你的i
)你會做:
parr[nparr].name = malloc (len + 1); /* allocate */
if (!parr[nparr].name) { /* validate */
perror ("malloc-parr[nparr].name");
break;
}
(注意:您在分配錯誤時break
而不是exit
,因為分配和填充的所有先前結構仍將包含您可以使用的有效數據)
現在您可以在一次調用中制作一個sscanf()
格式字符串並分離age
、 name
和score
,例如
/* separate buf & convert into age, name, score -- validate */
if (sscanf (buf, "%d,%[^,],%lf", &parr[nparr].age,
parr[nparr].name, &parr[nparr].score) != 3) {
fputs ("error: invalid line format.\n", stderr);
...
}
將它完全放入一個簡短的程序中以讀取和分離您的示例文件,您可以執行以下操作:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define COUNTLINES 10 /* if you need a constant, #define one (or more) */
#define MAXC 1024
#define NUMSZ 64
typedef struct { /* typedef for convenient use as type */
int age; /* age is generally an integer, not double */
char *name;
double score;
} try;
/* always provde a meaningful return when function can
* succeed or fail. Return result of malloc.
*/
try *allocate_struct_array (try **parr, int total_line)
{
return *parr = malloc (total_line * sizeof **parr);
}
int main (int argc, char **argv) {
char buf[MAXC]; /* temporary array to hold each line */
int count = 0,
nparr = 0,
count_lines = COUNTLINES;
try *parr = NULL;
/* use filename provided as 1st argument (book.txt by default) */
FILE *fileptr = fopen (argc > 1 ? argv[1] : "book.txt", "r");
if (!fileptr) { /* always validate file open for reading */
perror ("fopen-fileptr");
return 1;
}
if (!fgets (buf, MAXC, fileptr)) { /* read/discard header line */
fputs ("file-empty\n", stderr);
return 1;
}
/* validate every allocation */
if (allocate_struct_array (&parr, count_lines) == NULL) {
perror ("malloc-parr");
return 1;
}
while (nparr < count_lines) { /* protect your allocation bounds */
int ch = fgetc (fileptr); /* ch must be type int */
if (ch != '\n' && ch != EOF) { /* if not \n and not EOF */
buf[count++] = ch; /* add char to buf */
if (count + 1 == MAXC) { /* validate buf not full */
fputs ("error: line too long.\n", stderr);
count = 0;
continue;
}
}
else if (count) { /* only process buf if chars present */
char *p = buf, *ep; /* pointer & end-pointer */
buf[count] = 0; /* nul-terminate buf */
/* locate 1st ',' with p and last ',' with ep */
if ((p = strchr (buf, ',')) && (ep = strrchr (buf, ',')) &&
p != ep) { /* confirm pointers don't point to same ',' */
size_t len = ep - p - 1; /* get length of name */
parr[nparr].name = malloc (len + 1); /* allocate */
if (!parr[nparr].name) { /* validate */
perror ("malloc-parr[nparr].name");
break;
}
/* separate buf & convert into age, name, score -- validate */
if (sscanf (buf, "%d,%[^,],%lf", &parr[nparr].age,
parr[nparr].name, &parr[nparr].score) != 3) {
fputs ("error: invalid line format.\n", stderr);
if (ch == EOF) /* if at EOF on failure */
break; /* break read loop */
else {
count = 0; /* otherwise reset count */
continue; /* start read of next line */
}
}
}
nparr += 1; /* increment array index */
count=0; /* reset count zero */
}
if (ch == EOF) { /* if EOF, now break */
break;
}
}
fclose(fileptr); /* close file */
for (int i = 0; i < nparr; i++) {
printf ("%3d %-20s %5.1lf\n",
parr[i].age, parr[i].name, parr[i].score);
free (parr[i].name); /* free strings when done */
}
free (parr); /* free struxts */
}
(注意:永遠不要在代碼中硬編碼文件名或使用幻數。如果您需要一個常量, #define...
一個。將要讀取的文件名作為程序的第一個參數傳遞或將文件名作為輸入。你應該'不必重新編譯你的代碼只是為了從不同的文件名中讀取)
示例使用/輸出
使用dat/parr_name.txt
中的示例數據,您將擁有:
$ ./bin/parr_name dat/parr_name.txt
25 Rameiro Rodriguez 3.0
30 Anatoliy Stephanos 0.0
19 Vahan: Bohuslav 4.2
Memory 使用/錯誤檢查
您必須使用 memory 錯誤檢查程序,以確保您不會嘗試訪問 memory 或寫入超出/超出分配塊的邊界,嘗試讀取或基於未初始化值的條件跳轉,最后確認釋放所有已分配的 memory。
對於 Linux valgrind
是正常的選擇。 每個平台都有類似的 memory 檢查器。 它們都易於使用,只需通過它運行您的程序即可。
$ valgrind ./bin/parr_name dat/parr_name.txt
==17385== Memcheck, a memory error detector
==17385== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==17385== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==17385== Command: ./bin/parr_name dat/parr_name.txt
==17385==
25 Rameiro Rodriguez 3.0
30 Anatoliy Stephanos 0.0
19 Vahan: Bohuslav 4.2
==17385==
==17385== HEAP SUMMARY:
==17385== in use at exit: 0 bytes in 0 blocks
==17385== total heap usage: 7 allocs, 7 frees, 5,965 bytes allocated
==17385==
==17385== All heap blocks were freed -- no leaks are possible
==17385==
==17385== For counts of detected and suppressed errors, rerun with: -v
==17385== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
始終確認您已釋放所有已分配的 memory 並且沒有 memory 錯誤。
使用fgets()
讀取每一行和一個臨時數組作為name
為了不給你留下錯誤的印象,這個問題可以通過使用fgets()
將每一行讀入一個字符數組並用sscanf()
分隔所需的值,將name
保存到一個足夠大小的臨時數組中來大大簡化。 現在所需要做的就是為parr[nparr].name
分配,然后將臨時name
復制到parr[nparr].name
。
通過這種方式,您可以大大降低逐個字符讀取的復雜性,並且通過使用臨時數組name
,您無需定位','
以獲得名稱的長度。
唯一需要的更改是為臨時名稱數組添加一個新常量,然后您可以將整個讀取循環替換為:
#define NAMSZ 256
...
/* protect memory bounds, read each line into buf */
while (nparr < count_lines && fgets (buf, MAXC, fileptr)) {
char name[NAMSZ]; /* temporary array for name */
size_t len; /* length of name */
/* separate buf into age, temp name, score & validate */
if (sscanf (buf, "%d,%[^,],%lf", &parr[nparr].age, name,
&parr[nparr].score) != 3) {
fputs ("error: invalid line format.\n", stderr);
continue;
}
len = strlen (name); /* get length of name */
parr[nparr].name = malloc (len + 1); /* allocate for name */
if (!parr[nparr].name) { /* validate allocation */
perror ("malloc-parr[nparr].name");
break;
}
memcpy (parr[nparr].name, name, len + 1);
nparr += 1;
}
fclose(fileptr); /* close file */
...
(相同的 output 和相同的 memory 檢查)
另請注意,如果您的編譯器提供strdup()
,您可以將分配和復制作為單個操作。 這會將name
的分配和復制減少到單個調用,例如
parr[nparr].name = strdup (name);
由於strdup()
分配 memory (並且可能失敗),因此您必須像使用malloc()
和memcpy()
一樣驗證分配。 但是,請理解, strdup()
不是標准的 C。 它是不屬於標准庫的 POSIX function。
您可以進行的另一項改進是添加邏輯以在 struct ( parr
) 塊已滿時調用realloc()
。 這樣,您可以從一些合理預期的結構開始,然后在用完時重新分配更多。 這將消除對您可以存儲的行數的人為限制——並且無需知道count_lines
。 (這個網站上有很多關於如何使用realloc()
的例子,實現留給你。
如果您還有其他問題,請仔細查看並告訴我。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.