У меня странная проблема. Не могу догадаться, почему это происходит. Пробовал разными способами. Может быть, это потому, что я все еще новичок в языке 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;
Это работает.
Любые комментарии ?
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.
Обратите внимание, что вы не записываете имя студента в файл. Вы пишете только указатель на эту строку. Это, конечно, не то, что вам нужно. Когда вы читаете файл, вы читаете указатель, который больше не действителен.
Либо поместите всю строку в свою структуру (не указатель char, а массив символов), либо вы должны отдельно записать строки в файл.
В read()
вы никогда не выделяете память для name
в своей структуре Student
. (В этом отношении ваша функция write()
ведет себя намного лучше.)
Когда вы ссылаетесь на него в своем операторе printf
, вы вызываете неопределенное поведение .
--write
.. после записи, я вызываю ту же функцию read()
. то время это работает. переменные внутри функций read()
полностью независимы. ..
std_reader->name = (char *) malloc(20);
this, в функцию чтения над while
?
Student
, вам также нужно будет выделить память для name
. Что касается вашего второго комментария: да, выделяйте столько памяти, сколько вы выделяете, когда пишете. И не забудьте нулевой терминатор.
Похожие вопросы
Новые вопросы
c
C - это язык программирования общего назначения, используемый для системного программирования (ОС и встраиваемых), библиотек, игр и кроссплатформенности. Этот тег следует использовать с общими вопросами, касающимися языка C, как это определено в стандарте ISO 9899 (последняя версия 9899: 2018, если не указано иное, а также для запросов, специфичных для версии, с c89, c99, c11 и т. Д.). C отличается от C ++ и не должен сочетаться с тэгом C ++ без разумной причины.
name
должен оставаться указателем или быть гибким элементом массива.