У меня странная проблема. Не могу догадаться, почему это происходит. Пробовал разными способами. Может быть, это потому, что я все еще новичок в языке 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);
}

Пожалуйста, помогите мне разобраться и решить эту проблему.

ИЗМЕНИТЬ

Хорошо. Согласно приведенным ниже ответам, как я понял, я обновил свою структуру Student следующим образом.

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

Это работает.

Любые комментарии ?

1
BlueBird 16 Мар 2014 в 11:46
В какой операционной системе?
 – 
Basile Starynkevitch
16 Мар 2014 в 11:53
Очень короткий ответ: вы сохраняете указатель, но не то, на что он указывает.
 – 
user253751
16 Мар 2014 в 12:19
1
"Есть комментарии?" Что делать, если сохраняемое имя длиннее 19 символов?
 – 
alk
16 Мар 2014 в 12:24
Вы не должны были обновлять свою структуру. name должен оставаться указателем или быть гибким элементом массива.
 – 
Basile Starynkevitch
16 Мар 2014 в 12:46
1
@BlueBird: сделайте свое реальное приложение бесплатным программным обеспечением, например. github.com
 – 
Basile Starynkevitch
16 Мар 2014 в 14:30

3 ответа

Лучший ответ

Не вызывайте свои функции read и write (эти имена предназначены для функций Posix). И не ожидайте, что снова сможете прочитать указатель, написанный другим процессом . Это неопределенное поведение.

Итак, в вашем write вы (при условии, что 64-битная система x86, например, Linux) записываете 12 байтов (4 т. Е. sizeof(int) + 8 т. Е. sizeof(char*)); последние 8 байтов - это числовое значение некоторого указателя malloc.

В вашем read вы читаете эти 12 байтов. Итак, вы устанавливаете поле name на числовой указатель, который оказался действительным в процессе, выполнившем write. В целом это не сработает (например, из-за ASLR).

В общем, ввод-вывод для указателей - это очень плохо. Это имеет смысл только для одного и того же процесса.

То, что вы хотите сделать, называется сериализацией. По причинам, связанным с разработкой программного обеспечения, я рекомендую использовать текстовый формат для сериализации (например, JSON, возможно, используя Jansson библиотека). Текстовые форматы менее хрупкие и их легче отлаживать.


Предполагая, что вы закодируете студента в формате JSON, например

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

Вот возможная процедура кодирования JSON с использованием Jansson:

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 - ed 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)

Теперь вот процедура декодирования JSON с использованием Jansson; он дает указатель 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;
}

Вы также можете решить, что вы сериализуете в некотором двоичном формате (я не рекомендую это делать). Затем вы должны определить свой формат сериализации и придерживаться его. Очень вероятно, что вам придется кодировать идентификатор студента, длину его имени, его имя ....

Вы также можете (в C99) решить, что name учащегося является элемент гибкого массива, то есть объявлять

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

Вы действительно хотите, чтобы имена учащихся были разной длины. Тогда вы не сможете просто поместить записи различной длины в простой FILE. Вы можете использовать некоторую библиотеку индексированных файлов, например GDBM (каждая запись может быть в JSON). И вы, вероятно, захотите использовать Sqlite или настоящую базу данных, например MariaDb или MongoDB.

3
Basile Starynkevitch 16 Мар 2014 в 13:05
Ваш ответ имеет смысл. Как мне изменить свою программу, чтобы правильно записать значение. Мне нужно изменить мою структуру?
 – 
Muneer
16 Мар 2014 в 12:01
Я меняю указатель char в struct на массив char. теперь он работает нормально. Как вы думаете, это правильное решение?
 – 
Muneer
16 Мар 2014 в 12:12
1
Нет, вам не нужно это менять (но вы можете). Если это массив, сделайте его гибким членом массива. Но вы всегда должны правильно сериализовать.
 – 
Basile Starynkevitch
16 Мар 2014 в 12:16
Нет единого правильного ответа, как и на большинство вопросов типа «как мне что-то сделать?». В ответе Эмануэле Паолини говорится о двух возможностях.
 – 
user253751
16 Мар 2014 в 12:18
В моем случае я не могу сериализовать, потому что мне нужно выполнить поиск в этом файле. Приведенный выше код не является моим настоящим кодом. Я создал это, чтобы смоделировать мою проблему в простом количестве строк. для приложения, которое я создаю, поиск обязателен.
 – 
Muneer
16 Мар 2014 в 12:19

Обратите внимание, что вы не записываете имя студента в файл. Вы пишете только указатель на эту строку. Это, конечно, не то, что вам нужно. Когда вы читаете файл, вы читаете указатель, который больше не действителен.

Либо поместите всю строку в свою структуру (не указатель char, а массив символов), либо вы должны отдельно записать строки в файл.

4
Emanuele Paolini 16 Мар 2014 в 11:57

В read() вы никогда не выделяете память для name в своей структуре Student. (В этом отношении ваша функция write() ведет себя намного лучше.)

Когда вы ссылаетесь на него в своем операторе printf, вы вызываете неопределенное поведение .

1
Bathsheba 16 Мар 2014 в 11:53
Если вы заметили, когда я передаю аргумент --write .. после записи, я вызываю ту же функцию read(). то время это работает. переменные внутри функций read() полностью независимы. ..
 – 
Muneer
16 Мар 2014 в 11:57
Хорошо .. я должен поместить malloc, например std_reader->name = (char *) malloc(20); this, в функцию чтения над while?
 – 
Muneer
16 Мар 2014 в 11:59
Я не понимаю ваш первый комментарий: если вы выделяете новый Student, вам также нужно будет выделить память для name. Что касается вашего второго комментария: да, выделяйте столько памяти, сколько вы выделяете, когда пишете. И не забудьте нулевой терминатор.
 – 
Bathsheba
16 Мар 2014 в 12:01