簡體   English   中英

動態結構數組中動態分配的字符串(段錯誤)

[英]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 塊的起始地址的指針,所以它可以被釋放,更需要。

解決問題的更好方法是將每一行讀入一個簡單的字符數組(大小足以容納每一行)。 然后,您可以分開agenamescore並確定name中的字符數,以便您可以正確分配parr[i].name ,然后您可以在分配后復制名稱。 如果你很小心,你可以簡單地在緩沖區中找到兩個',' ,為parr[i].name分配,然后使用sscanf()和適當的格式字符串來分隔、轉換和復制所有值到你的結構parr[i]在一次調用中。

由於您無法確定//total line in the file is calculated ,我們將假設一個足夠大的數字來容納您的示例文件以進行討論。 找到那個號碼是留給你的。

要將每一行讀入一個數組,只需聲明一個足夠大的緩沖區(字符數組)以容納每一行(取最長的預期行並乘以 2 或 4,或者如果在典型的 PC 上,只需使用10242048的緩沖區字節將容納除晦澀文件之外的所有文件,其行長。(規則:不要吝嗇緩沖區大小!! )你可以這樣做,例如

#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()格式字符串並分離agenamescore ,例如

                /* 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM