簡體   English   中英

二進制文件讀取/寫入錯誤,結構中包含char *指針

[英]binary file read/write error containing char* pointer within struct

我有一個奇怪的問題。 我不知道為什么會這樣。 我嘗試了各種方式。 可能是因為我仍然是C語言的新手。

請看下面的代碼。

它帶有2個參數。 --write--read

  • 在我的write()函數中,我寫入文件,然后調用read()函數。 這會將數據寫入文件,並按預期正確打印3行值。

  • 在我的read()函數中,我讀取了文件。 當我單獨傳遞--read參數時,程序會給出segmentation fault錯誤消息。 盡管在下面的代碼中,如果我將靜態字符串值分配給char *name此讀取功能將按預期工作。

以下是我創建的用於模擬問題的完整代碼。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef struct _student {
    int id;
    char *name;
} Student;

void write();
void read();

int main(int argc, char *argv[])
{
    if (argc > 1) {
        if (strcmp(argv[1], "--write") == 0) {
            write();
            read();
        }
        else if (strcmp(argv[1], "--read") == 0) {
            read();
        }
    }
    return 0;
}

void write()
{
    printf("Write\n");
    FILE *fp;

    // write student
    Student *std_writer = (Student *) malloc(sizeof(Student));
    std_writer->id = 10;
    //std_writer->name = "Alice"; // But if i remove the below 4 lines and uncommented this line, everything works as expected.
    char *a = "Alice";
    std_writer->name = malloc(20);
    memset(std_writer->name, '\0', 20);
    strncpy(std_writer->name, a, 5);

    fp = fopen("Student.file", "wb");
    fwrite(std_writer, sizeof(Student), 1, fp);
    fwrite(std_writer, sizeof(Student), 1, fp);
    fwrite(std_writer, sizeof(Student), 1, fp);
    fclose(fp);

    free(std_writer);
}

void read()
{
    printf("Read\n");
    FILE *fp;

    // read student
    Student *std_reader = (Student *) malloc(sizeof(Student));
    fp = fopen("Student.file", "rb");
    while(fread(std_reader, sizeof(Student), 1, fp) == 1) {
        printf("ID %i  \tName : %s\n", std_reader->id, std_reader->name);
    }
    fclose(fp);

    free(std_reader);
}

請幫助我理解並解決此問題。

編輯

確定根據我所理解的以下答案,我按如下所示對struct Student進行了點綴。

typedef struct _student {
    int id;
    char name[20];
} Student;

這可行。

任何意見 ?

請注意,您並沒有寫上學生的姓名。 您僅將指針寫入該字符串。 當然,這不是您想要的。 當您讀取文件時,您正在讀取的指針不再有效。

要么將整個字符串放入結構(不是char指針,而是char數組)中,否則您應該將字符串分別寫入文件中。

不要叫你的函數readwrite (這些名字是POSIX函數)。 並且不要期望能再次讀取由不同進程編寫的指針。 這是未定義的行為

因此,在您的write中(假設使用64位x86系統,例如Linux),您將寫12個字節(4即sizeof(int) + 8即sizeof(char*) ); 后8個字節是某些malloc指針的數值。

read您正在讀取這12個字節。 因此,您要將name字段設置為在完成write的過程中恰好有效的數字指針。 這通常不會起作用(例如,由於ASLR )。

通常,對指針執行I / O是非常糟糕的。 它只對同一過程有意義。

您要執行的操作稱為序列化 出於軟件工程方面的原因,我建議使用文本格式進行序列化(例如JSON ,也許使用Jansson庫)。 文本格式不那么脆弱,並且更容易調試。


假設您將以JSON格式編碼學生,例如

{ "id":123, "name":"John Doe" }

這是使用Jansson的可能的JSON編碼例程:

int encode_student (FILE*fil, const Student*stu) {
   json_t* js = json_pack ("{siss}", 
                           "id", stu->id, 
                           "name", stu->name);
   int fail = json_dumpf (js, fil, JSON_INDENT(1));
   if (!fail) putc('\n', fil);
   json_decref (js); // will free the JSON
   return fail;  
}

注意,您需要一個函數來釋放由malloc分配的Student區域,這里是:

void destroy_student(Student*st) {
   if (!st) return;
   free (st->name);
   free (st);
}

而且您可能還需要宏

#define DESTROY_CLEAR_STUDENT(st) do \
  { destroy_student(st); st = NULL; } while(0)

現在,這是使用Jansson的JSON解碼例程; 它在堆中提供了一個Student指針(稍后由調用者使用DESTROY_CLEAR_STUDENT銷毀)。

Student* decode_student(FILE* fil) { 
   json_error_t jerr;
   memset (&jerr, 0, sizeof(jerr));
   json_t *js = json_loadf(fil, JSON_DISABLE_EOF_CHECK, &err);
   if (!js) {
      fprintf(stderr, "failed to decode student: %s\n", err.text);
      return NULL;
   }
   char* namestr=NULL;
   int idnum=0;
   if (json_unpack(js, "{siss}",  
                       "id", &idnum,
                       "name", &namestr)) {
       fprintf(stderr, "failed to unpack student\n");
       return NULL;
   };
   Student* res = malloc (sizeof(Student));
   if (!res) { perror("malloc student"); return NULL; };
   char *name = strdup(namestr);
   if (!name) { perror("strdup name"); free (res); return NULL; };
   memset(res, 9, sizeof(Student));
   res->id = id;
   res->name = name;
   json_decref(js);
   return res;
}

您還可以決定以某種二進制格式進行序列化(我不建議您這樣做)。 然后,您應該定義序列化格式並堅持使用。 很可能您必須對學生ID,其名稱的長度,其名稱進行編碼。

您還可以(在C99中)確定學生的name是一個靈活的數組成員 ,即聲明

typedef struct _student {
   int id;
   char name[]; // flexible member array, conventionally \0 terminated
} Student;

您確實希望學生姓名的長度不同。 然后,您不能簡單地將不同長度的記錄放在簡單的FILE 您可以使用一些索引文件庫,例如GDBM (每個記錄可以在JSON中)。 您可能要使用Sqlite或像MariaDbMongoDB這樣的真實數據庫。

read() ,您永遠不會在Student結構中為name分配內存。 (在這方面,您的write()函數的行為要好得多。)

printf語句中引用它時,將調用未定義的行為

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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