• Познакомьтесь с пентестом веб-приложений на практике в нашем новом бесплатном курсе

    «Анализ защищенности веб-приложений»

    🔥 Записаться бесплатно!

  • CTF с учебными материалами Codeby Games

    Обучение кибербезопасности в игровой форме. Более 200 заданий по Active Directory, OSINT, PWN, Веб, Стеганографии, Реверс-инжинирингу, Форензике и Криптографии. Школа CTF с бесплатными курсами по всем категориям.

Запись/чтение структруры в бинарный (двоичный) файл

  • Автор темы BattleMage
  • Дата начала
B

BattleMage

Как структуру, у которой 5 полей типа char [20] записать в бинарный файл. Точнее записал, но вот не считывается...

Структура объявлена так:
struct kniga
{
char nazvanie[20];
char avtor[20];
char zhanr[20];
char izdatelstvo[20];
char god_izdaniya[20];
};
struct kniga *ptr;

Вот так записал:
FILE *db;
db=fopen("DataBase.xxx","wb");
fwrite((char*)&(*ptr),sizeof(*ptr),1,db);
fclose(db);

Вот так считываю. То что считал хочу записать в ячейки StringGrid-a:
db=fopen("DataBase.xxx","rb");
fread((char*)&ptr,sizeof(ptr),1,db);
while (fgetc(db)!=EOF)
{
StringGrid1->Cells[0][StringGrid1->RowCount]=IntToStr(StringGrid1->RowCount);
StringGrid1->Cells[1][StringGrid1->RowCount]=ptr->nazvanie;
StringGrid1->Cells[2][StringGrid1->RowCount]=ptr->avtor;
StringGrid1->Cells[3][StringGrid1->RowCount]=ptr->zhanr;
StringGrid1->Cells[4][StringGrid1->RowCount]=ptr->izdatelstvo;
StringGrid1->Cells[5][StringGrid1->RowCount]=ptr->god_izdaniya;
StringGrid1->RowCount++;
}
fclose(db);

Что я не так делаю? Либо записываю, либо читаю, либо и то и то :)
 
P

Pasha

Намутил с указателями.
Код:
// выделили память 
ptr = new kniga();
// записали
fwrite((char*)ptr,sizeof(kniga),1,db); 
// прочитали
fread((char*)ptr,sizeof(kniga),1,db);
// удалили
delete ptr;
 
B

BattleMage

Pasha, написал как ты сказал. Скомпилировалось нормально, запустилось. Ввел некоторые данные - он записал их в бинарный файл. Потом ещё раз запустить решил. Думал, что покажет в ячейках StringGrid-a то что я прошлый раз вводил. Не тут то было... Вылетела ошибка: "EAccessViolation."
Что это значит?

Мне кажется я не так считываю...

db=fopen("DataBase.xxx","rb");
rewind(db);
fread((char*)ptr,sizeof(kniga),1,db);
while (fgetc(db)!=EOF)
{
ptr=(struct kniga*)malloc(sizeof(struct kniga));
StringGrid1->Cells[0][StringGrid1->RowCount]=IntToStr(StringGrid1->RowCount);
StringGrid1->Cells[1][StringGrid1->RowCount]=ptr->nazvanie;
StringGrid1->Cells[2][StringGrid1->RowCount]=ptr->avtor;
StringGrid1->Cells[3][StringGrid1->RowCount]=ptr->zhanr;
StringGrid1->Cells[4][StringGrid1->RowCount]=ptr->izdatelstvo;
StringGrid1->Cells[5][StringGrid1->RowCount]=ptr->god_izdaniya;
StringGrid1->RowCount++;
free(ptr);
}
fclose(db);
 
P

Pasha

Для: BattleMage
Не проверял (нет у меня установленных плюсов), но примерно так:
Код:
db=fopen("DataBase.xxx","rb");
ptr=new kniga();
while (!feof(db))
{
fread(ptr,sizeof(kniga),1,db);
StringGrid1->Cells[0][StringGrid1->RowCount]=IntToStr(StringGrid1->RowCount);
StringGrid1->Cells[1][StringGrid1->RowCount]=ptr->nazvanie;
StringGrid1->Cells[2][StringGrid1->RowCount]=ptr->avtor;
StringGrid1->Cells[3][StringGrid1->RowCount]=ptr->zhanr;
StringGrid1->Cells[4][StringGrid1->RowCount]=ptr->izdatelstvo;
StringGrid1->Cells[5][StringGrid1->RowCount]=ptr->god_izdaniya;
StringGrid1->RowCount++;
}
fclose(db);delete ptr;
или так
Код:
db=fopen("DataBase.xxx","rb");
kniga buff;
while (!feof(db))
{
fread(&buff, sizeof(kniga),1,db);
StringGrid1->Cells[0][StringGrid1->RowCount]=IntToStr(StringGrid1->RowCount);
StringGrid1->Cells[1][StringGrid1->RowCount]=ptr.nazvanie;
[snip]
}
fclose(db);
 
O

Over

Код:
db=fopen("DataBase.xxx","rb");
rewind(db);
fread((char*)ptr,sizeof(kniga),1,db);
while (fgetc(db)!=EOF)
{ptr=(struct kniga*)malloc(sizeof(struct kniga));
StringGrid1->Cells[0][StringGrid1->RowCount]=IntToStr(StringGrid1->RowCount);
StringGrid1->Cells[1][StringGrid1->RowCount]=ptr->nazvanie;
StringGrid1->Cells[2][StringGrid1->RowCount]=ptr->avtor;
StringGrid1->Cells[3][StringGrid1->RowCount]=ptr->zhanr;
StringGrid1->Cells[4][StringGrid1->RowCount]=ptr->izdatelstvo;
StringGrid1->Cells[5][StringGrid1->RowCount]=ptr->god_izdaniya;
StringGrid1->RowCount++;
free(ptr);
}
fclose(db);

Вот ты в этом коде сначала делаешь fread, а потом в цикле уже делаешь malloc. Делать надо наоборот. Сначала выделить память, а потом в неё уже производить считывание.
И ещё. Выделяешь память странно как-то. Нужно так: ptr = (kniga *) malloc(sizeof(kniga))
 
B

BattleMage

1) БОЛЬШОЕ спасибо за советы. Сейчас попробую сделать...

2) Over, а что есть разница между строчками:

ptr = (kniga *) malloc(sizeof(kniga))
и
ptr=(struct kniga*)malloc(sizeof(struct kniga));

приколист :)
 
B

BattleMage

Так. Почти все как надо. Только вот один недочет:
добавил очередную запись. закрыл программу. запускаю, а запись которая была добавлена в последний раз два раза встречается в StringGrid - e (на последней и предпоследней строчке). Вот только не понятно: он просто не так как-то в StringGrid выводит это или в текстовом файле такая "каша"...

И ещё. Если файл пуст. То StringGrid состоит из строки, где в поле номер записано "1", а в поле название "$u"

сейчас у меня такой код:

FILE *db;
//установка выравнивания структуры на 1
#pragma pack(push) // сохранение текущего выравнивания
#pragma pack(1)
struct kniga
{
char nazvanie[20];
char avtor[20];
char zhanr[20];
char izdatelstvo[20];
char god_izdaniya[20];
};
struct kniga *ptr;
#pragma pack(pop) // восстановление предыдушего состояния
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
StringGrid1->Cells[0][0]="№";
StringGrid1->Cells[1][0]="Название";
StringGrid1->Cells[2][0]="Автор";
StringGrid1->Cells[3][0]="Жанр";
StringGrid1->Cells[4][0]="Издательство";
StringGrid1->Cells[5][0]="Год издания";
db=fopen("DataBase.xxx","rb");
rewind(db);
ptr=(struct kniga*)malloc(sizeof(struct kniga));
while (!feof(db))
{
fread(ptr,sizeof(kniga),1,db);
StringGrid1->Cells[0][StringGrid1->RowCount]=IntToStr(StringGrid1->RowCount);
StringGrid1->Cells[1][StringGrid1->RowCount]=ptr->nazvanie;
StringGrid1->Cells[2][StringGrid1->RowCount]=ptr->avtor;
StringGrid1->Cells[3][StringGrid1->RowCount]=ptr->zhanr;
StringGrid1->Cells[4][StringGrid1->RowCount]=ptr->izdatelstvo;
StringGrid1->Cells[5][StringGrid1->RowCount]=ptr->god_izdaniya;
StringGrid1->RowCount++;
}
fclose(db);
free(ptr);
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
if ((Edit1->Text=="")||(Edit2->Text=="")||(Edit3->Text=="")||(Edit4->Text=="")||(Edit5->Text==""))
ShowMessage("Нужно заполнить все поля!");
else
{
ptr=(struct kniga*)malloc(sizeof(struct kniga));
strcpy(ptr->nazvanie,Edit1->Text.c_str());
strcpy(ptr->avtor,Edit2->Text.c_str());
strcpy(ptr->zhanr,Edit3->Text.c_str());
strcpy(ptr->izdatelstvo,Edit4->Text.c_str());
strcpy(ptr->god_izdaniya,Edit5->Text.c_str());
StringGrid1->Cells[0][StringGrid1->RowCount]=IntToStr(StringGrid1->RowCount);
StringGrid1->Cells[1][StringGrid1->RowCount]=ptr->nazvanie;
StringGrid1->Cells[2][StringGrid1->RowCount]=ptr->avtor;
StringGrid1->Cells[3][StringGrid1->RowCount]=ptr->zhanr;
StringGrid1->Cells[4][StringGrid1->RowCount]=ptr->izdatelstvo;
StringGrid1->Cells[5][StringGrid1->RowCount]=ptr->god_izdaniya;
StringGrid1->RowCount++;
db=fopen("DataBase.xxx","a+b");
fwrite(ptr,sizeof(kniga),1,db);
fclose(db);
Edit1->Text="";
Edit2->Text="";
Edit3->Text="";
Edit4->Text="";
Edit5->Text="";
free(ptr);
}
}
 
O

Over

Хорошо. Вот ты берёшь просто так и вызываешь функцию открытия файла. А прикинь, если файла вообще не будет существовать? Тебе надо вставить проверку на ошибки. Что-то типа такого:
Код:
if ((db=fopen("database.xxx","rb")) == NULL)
{ //Выдать сообщение об ошибке
return -1;  
}
Дальше. Когда выделил память, её нужно обнулить. Тогда в ней не будет сожержаться мусор. И потом ещё в цикле проверять значение, возвращаемое функцией fread() (не помню, вроде возвращает количество считанных байт). Если файл оказался пуст, то данные не считаются, и тогда в StringGrid не нужно ничего добавлять.

Over, а что есть разница между строчками:
ptr = (kniga *) malloc(sizeof(kniga)) и ptr=(struct kniga*)malloc(sizeof(struct kniga));
приколист
Разницы может и нет, но что-то я до сих пор не встречал, чтоб писали как ты: (struct kniga*)... Это просто банально лишний код.
 
E

European

Для: BattleMage
Вы уж простите, что я в Тулу со своим самоваром... Но зачем плодить кашу из сишного и плюс-плюсного кода? Неуженли нельзя использовать new и delete вместо malloc и free, неужели нет нормальных функций для работы с файлами?
 
P

Pasha

Для: European
Нормальные функции ... ты еще файлы отображаемые в память предложи использовать - ведь это красивее, чем тупо читать по одной структуре в буффер :) Пусть человек сначала с основами разберется, а стремление к порядку и красоте кода ему привьют на первой же серьезной работе.
 
E

European

<!--QuoteBegin-Pasha+19:07:2007, 11:53 -->
<span class="vbquote">(Pasha @ 19:07:2007, 11:53 )</span><!--QuoteEBegin-->ты еще файлы отображаемые в память предложи использовать - ведь это красивее, чем тупо читать по одной структуре в буффер
[snapback]72687" rel="nofollow" target="_blank[/snapback]​
[/quote]
А вот палку перегибать не надо!
<!--QuoteBegin-Pasha+19:07:2007, 11:53 -->
<span class="vbquote">(Pasha @ 19:07:2007, 11:53 )</span><!--QuoteEBegin-->Пусть человек сначала с основами разберется
[snapback]72687" rel="nofollow" target="_blank[/snapback]​
[/quote]
Ну так все в Turbo С и писать приложения под консоль.
Городить огород типа:
Код:
struct kniga *ptr;
ptr=(struct kniga*)malloc(sizeof(struct kniga));
вместо того чтобы написать
Код:
kniga* ptr = new kniga;
это изучение основ? Учится можно сразу, а не ждать нормальной работы, на которую из-за такого кода в тестовом задании могут и не взять
 

Kmet

Well-known member
25.05.2006
904
8
BIT
0
в случае тестового задания, имхо, скорее бы обратили бы внимание не на malloc, а на то, что использование динамического выделенения памяти в данном случае является абсолютно излишним.
 
P

Pasha

Для: Kmet
А еще скорее всего обратили бы внимание на использование текстового файла вместо нормальной базы данных, слишком жесткого ограничения на строки (название книги до 20 символов), код копирования строк без проверки длины (strcpy(ptr->avtor,Edit2->Text.c_str()):), имена контролов (Edit1,2,3,4)...
С другой стороны, всегда есть вероятность, что это не тестовое задание, а задача по "программированию" с условием "чтение и запись данных в бинарный (двоичный) файл с использованием динамического выделения памяти.".
 
O

Over

Для European:
Функция new, если посмотришь исходники, вызывает напрямую функцию malloc. delete в общем-то то же самое. Поэтому с программной точки зрения разницы нет. Если уже только гнаться за красивостью кода.
 
E

European

Для: Over
Иногда лучше жевать, чем говорить.
1 - new это не функция, а предопределенный оператор языка C++. Надеюсь разница понятна!
2 - new и delete могут использовать malloc и free, но нет никакой гарантии этого.
3 (и самое главное) - malloc и free ничего не знают о конструкторах и деструкторах классов соответсвенно. malloc выделяет неициализированную память. Интересно, как бы ты передал значение конструктору используя malloc. Далее, как ты выражаешься: "delete в общем-то то же самое"
4 - использование new и delete вперемешку с malloc и free приводит к непредсказуемым последствиям. Т.е. используя оба способа выделения памяти необходимо помнить о том, как была выделена память
5 - избегать использования malloc и free рекомендуют практически все нормальные авторы нормальных книг. Надеюсь фамилии Страуструпа, Майерса и Саттера тебе что-то говорят и ты прочитал хотя бы парочку их трудов. Если нет, то продолжение спора бесполезно
 

Kmet

Well-known member
25.05.2006
904
8
BIT
0
Для: Pasha
судить о целесообразности использования БД в данном мы не можем, так как толком задания не знаем. ограничение длины строки, можно тоже списать на ТЗ. проверка длины строки может производится на уровне компонента, что конечно же коряво, но все же. именна контролов можно списать на не соответстивие code convention, что конечно же плохо, но не так страшно. malloc\free в данном конкретном случае вполне допустимо. И дело не в том, что я не уважаю Страутсрапа и КО, просто если не обращать внимание на VCL, то код вполне соотвествует С-style.

С другой стороны, понимание критериев использования динамической памяти и дисциплина при работе с ней критична для любого С/C++ программиста. и не важно тут, это тестовое задание или просто задача.
 
B

BattleMage

Ну вы блин даёте... Вот только сейчас решил новые сообщения форума прочесть... Таким ублюдком начинаешь себя ощущать B)

Вот у меня ещё вопрос. Вроде написал как вы сказали. Ну и что-то от себя добавил. Почему при отсутствии файла DataBase.xxx (заполнение всех эдитов) вылетает сообщение об ошибке такое:
"Access violation at address 32657E9A in module 'CC3260MT.DLL'. Write of address 00000004.",
а если файл присутствует (пуст), то пишет:
"EAccessViolation."

Код теперь такой (меняется буквальные каждые 5 минут :) ) :

//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include<stdio.h>
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
FILE *db;
//установка выравнивания структуры на 1
#pragma pack(push) // сохранение текущего выравнивания
#pragma pack(1)
struct kniga
{
int nomer;
char nazvanie[40];
char avtor[40];
char zhanr[40];
char izdatelstvo[40];
char god_izdaniya[10];
};
kniga *ptr;
#pragma pack(pop) // восстановление предыдушего состояния
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
StringGrid1->Cells[0][0]="№";
StringGrid1->ColWidths[0]=30;
StringGrid1->Cells[1][0]="Название";
StringGrid1->Cells[2][0]="Автор";
StringGrid1->Cells[3][0]="Жанр";
StringGrid1->Cells[4][0]="Издательство";
StringGrid1->ColWidths[5]=73;
StringGrid1->Cells[5][0]="Год издания";
if ((db=fopen("DataBase.xxx","rb"))==NULL) ShowMessage("Файл базы данных DataBase.xxx отсутствует!");
else
{
rewind(db);
ptr=(kniga*)malloc(sizeof(kniga));
ptr=NULL;
while (!feof(db))
{
fread(ptr,sizeof(kniga),1,db);
ptr->nomer=StringGrid1->RowCount;
StringGrid1->Cells[0][StringGrid1->RowCount]=IntToStr(ptr->nomer);
StringGrid1->Cells[1][StringGrid1->RowCount]=ptr->nazvanie;
StringGrid1->Cells[2][StringGrid1->RowCount]=ptr->avtor;
StringGrid1->Cells[3][StringGrid1->RowCount]=ptr->zhanr;
StringGrid1->Cells[4][StringGrid1->RowCount]=ptr->izdatelstvo;
StringGrid1->Cells[5][StringGrid1->RowCount]=ptr->god_izdaniya;
StringGrid1->RowCount++;
}
fclose(db);
free(ptr);
}
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
if ((Edit1->Text=="")||(Edit2->Text=="")||(Edit3->Text=="")||(Edit4->Text=="")||(Edit5->Text==""))
ShowMessage("Нужно заполнить все поля!");
else
{
ptr=(kniga*)malloc(sizeof(kniga));
ptr=NULL;
strcpy(ptr->nazvanie,Edit1->Text.c_str());
strcpy(ptr->avtor,Edit2->Text.c_str());
strcpy(ptr->zhanr,Edit3->Text.c_str());
strcpy(ptr->izdatelstvo,Edit4->Text.c_str());
strcpy(ptr->god_izdaniya,Edit5->Text.c_str());
ptr->nomer=StringGrid1->RowCount;
StringGrid1->Cells[0][StringGrid1->RowCount]=IntToStr(ptr->nomer);
StringGrid1->Cells[1][StringGrid1->RowCount]=ptr->nazvanie;
StringGrid1->Cells[2][StringGrid1->RowCount]=ptr->avtor;
StringGrid1->Cells[3][StringGrid1->RowCount]=ptr->zhanr;
StringGrid1->Cells[4][StringGrid1->RowCount]=ptr->izdatelstvo;
StringGrid1->Cells[5][StringGrid1->RowCount]=ptr->god_izdaniya;
StringGrid1->RowCount++;
db=fopen("DataBase.xxx","a+b");
fwrite(ptr,sizeof(kniga),1,db);
fclose(db);
Edit1->Text="";
Edit2->Text="";
Edit3->Text="";
Edit4->Text="";
Edit5->Text="";
free(ptr);
}
}
 
E

European

Я полагаю причина в таком коде:
Код:
ptr=(kniga*)malloc(sizeof(kniga));
ptr=NULL;
strcpy(ptr->nazvanie,Edit1->Text.c_str());
Ты выделил память и получил указатель. Затем ты установил указатель в NULL и пытаешься к нему обращаться. Естественно будет ошибка. Кстати, если хочешь могу сделать парочку замечаний по "красоте кода"
 
B

BattleMage

Это кстати я уже исправил. Но все равно спасибо...
Вот ещё что. Он повторяет запись последнюю. Причем полностью (от первого до последнего поля). Я думаю можно исправить это заполнением оперделённых ячеек StringGrid-а. да? Но вот если файл существует, но в нем ничего нет (размер равен 0), то это уже не пройдет. Так как он начнёт затирать уже нужные данные. Как сделать проверку на размер файла? типа если 0, то одни, если нет - то другие...

Замечания давай. С удовольствием послушаю...
 
E

European

<!--QuoteBegin-BattleMage+20:07:2007, 10:28 -->
<span class="vbquote">(BattleMage @ 20:07:2007, 10:28 )</span><!--QuoteEBegin-->Вот ещё что. Он повторяет запись последнюю.
[snapback]72779" rel="nofollow" target="_blank[/snapback]​
[/quote]
Я так понял, что повторяет последнюю запись при создании формы? Хм-м, попробуй так:
Код:
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
...
if ((db=fopen("DataBase.xxx","rb"))==NULL) ShowMessage("Файл базы данных DataBase.xxx отсутствует!");
else
{
rewind(db);
ptr=(kniga*)malloc(sizeof(kniga));
while (!feof(db))
{
fread(ptr,sizeof(kniga),1,db);
if( !ferror( db ) )
{
ptr->nomer=StringGrid1->RowCount;
StringGrid1->Cells[0][StringGrid1->RowCount]=IntToStr(ptr->nomer);
...
StringGrid1->RowCount++;
}
}
fclose(db);
free(ptr);
}
}

P.s. А разве для добавления строки в TStringGrid достаточно сделать StringGrid1->RowCount++? И по умолчанию уже есть одна строка? Что-то я не помню уже...
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!