C++ Segmentation fault podczas odczytu pliku binarnego

0

Witam.

Piszę w następującej sprawie, mianowicie mam aplikacje (poniższy przykład jest dość trywialny, wiem aczkolwiek służy on tylko i wyłącznie do zobrazowania problemu ) w której to korzystam z zapisu i odczytu plików binarnych.

Kod obrazujący problem:

#include <cstdlib>
#include <fstream>
#include <iostream>
#include <string>

using namespace std;

int main(int argc, char** argv)
{
	int input;
	std::cout << "--------------" << std::endl;
	std::cout << " 1. save data " << std::endl;
	std::cout << " 2. open data " << std::endl;
	std::cout << "--------------" << std::endl;
	std::cout << "_>";
	std::cin >> input;

	std::string fileName = "./myFile";

	if (input == 1)
	{
		std::string array[3];
		array[0] = "Tomek";
		array[1] = "Romek";
		array[2] = "Atomek";

		std::fstream outputFile(fileName.c_str(), std::ios::out | std::ios::binary);

		for (int i = 0; i < 3; i++)
		{
			/* Opcja tekstowa */
			// outputFile << array[i]; 
			// outputFile << std::endl;

			/* Opcja binarna */
			outputFile.write((char*)&array[i], sizeof(std::string));

		}
		outputFile.close();

		for (int i = 0; i < 3; i++)
		{
			std::cout << array[i] << std::endl;

		}
	}

	else
	{
		std::fstream inputFile(fileName.c_str(), std::ios::in | std::ios::binary);

		std::string buffer[3];

		for (int i = 0; i < 3; i++)
		{
			/* Opcja tekstowa */
			//inputFile >> buffer[i]; 

			/* Opcja binarna */
			inputFile.read((char*)&buffer[i], sizeof(std::string));
		}

		inputFile.close();

		/* Log */
		for (int i = 0; i < 3; i++)
		{
			std::cout << buffer[i] << std::endl;
		}
	}

	system("pause");

	return 0;
}

Problem polega na tym iż kod kompiluje się aczkolwiek nie działa odczyt pliku binarnego na Linuksie, podczas odczytu wyrzuca błąd: Segmentation fault. Testowałem powyższy przykład w Visual Studio 2015 na Windowsie i wszystko działało zgodnie z oczekiwaniami, zarówno zapis jak i odczyt.

Na dodatek dodam że na Linuksie korzystam z kompilatora GNU C++ w wersji 4.9 oraz że zapis i odczyt z użyciem operatorów << i >> dla plików tekstowych, działa poprawnie.

Przyznam szczerze że nie mam bladego pojęcia skąd ten błąd, czy to jakiś błąd kompilatora czy inne ustrojstwo?

Będę niezmiernie wdzięczny za pomoc w rozwiązaniu problemu.
Pozdrawiam.

4
 utputFile.write((char*)&array[i], sizeof(std::string));

jak myślisz co ci tu zapisze i jakiej wielkości?

nputFile.read((char*)&buffer[i], sizeof(std::string)); 

a jak myślisz, w jakie miejsce tu będziesz miał wczytane i ile danych?

Jak odpowiesz sobie na te pytania, to będziesz wiedział dlaczego to nie działa i nie ma prawa działać poprawnie (chyba, że przypadkiem)

1

Bo zapisujesz na pałe obiekt std::string a tak nie wolno. Ten obiekt nie trzyma w sobie twojego stringa jakoś statycznie tylko ma wskaźnik do pamięci zaalokowanej na tego stringa. Powinineś serializować samą "zawartość" czyli to co zwróci string_jakiśtam.c_str()

1

Zadaj sobie pytanie jak string trzyma swoje bufory a stanie się jasne, dlaczego nie działa.

Po kolei:
To jest błędne, w ten sposób nie zapiszesz zawartości string do pliku. W ten sposób zapiujesz binarną zawartość klasy reprezentującą string. Klasa ta może mieć wskaźnik na inne miejsce w pamięci, gdzie trzyma swój buffor, czyli faktyczną zawartość o którą Ci chodzi. W efekcie zapiszesz do pliku wskaźnik na miejsce w pamięci, gdzie znajduje się zawartość stringa.

outputFile.write((char*)&array[i], sizeof(std::string))

Tutaj analogicznie. Odczytujesz binrną zawartość klasy string, którą zapisaś. Niestety buffor na który wskazuje ten obiekt nie istnieje w pamięci. Podany adres nie był zmapowany do procesu więc poleciało seg fault.

inputFile.read((char*)&buffer[i], sizeof(std::string));

Jeżeli na Windowsie działało to tylko dlatego, że nie przeprowadził randomizacji przydzielanych adresów dla procesu.

0

Witam ponownie.

Wszystkim zainteresowanym tematem dziękuje za odpowiedzi. W ramach rozwiązania wątku przesyłam sposób w jak sobie poradziłem z problemem, tak aby ktoś na przyszłość miał opracowany „wzór do inspiracji”.

Kod po opracowaniu:

 
#include <cstdlib>
#include <fstream>
#include <iostream>

#include <string>
#include <string.h>
 
using namespace std;
 
class myStringObj
{
private:
    char m_Name[100] = "";
public: 
    myStringObj& operator= (std::string obj)
    {
        memcpy(this->m_Name, obj.c_str(), obj.size()); 
        return *this;
    }
    
    void show()
    {
        std::cout << this->m_Name << std::endl;
    } 
};

int main(int argc, char** argv)
{
    int input;
    std::cout << "--------------" << std::endl;
    std::cout << " 1. save data " << std::endl;
    std::cout << " 2. open data " << std::endl;
    std::cout << "--------------" << std::endl;
    std::cout << "_>";
    std::cin >> input;
 
    std::string fileName = "./myFile";
 
    if (input == 1)
    {
        myStringObj array[4]; 
        array[0] = "Tomek";
        array[1] = "Romek";
        array[2] = "Atomek";
        array[3] = "Atomek2";
        
        std::fstream outputFile(fileName.c_str(), std::ios::out | std::ios::binary);
 
        for (int i = 0; i < 4; i++)
        {
            /* Opcja tekstowa */
            // outputFile << array[i]; 
            // outputFile << std::endl;
 
            /* Opcja binarna */
            outputFile.write((char*)&array[i], sizeof(myStringObj));
 
        }
        outputFile.close();
 
        for (int i = 0; i < 4; i++)
        {
            array[i].show(); // << std::endl;
 
        }
    }
 
    else
    {
        std::fstream inputFile(fileName.c_str(), std::ios::in | std::ios::binary);

        myStringObj buffer[4];
 
        for (int i = 0; i < 4; i++)
        {
            /* Opcja tekstowa */
            //inputFile >> buffer[i]; 
 
            /* Opcja binarna */
            inputFile.read((char*)&buffer[i], sizeof(myStringObj));
        }
 
        inputFile.close();
 
        /* Log */
        for (int i = 0; i < 4; i++)
        {
            buffer[i].show();
        }
    }
 
    system("pause");
 
    return 0;
}

Temat do zamknięcia.

0
char m_Name[100] = "";
//
memcpy(this->m_Name, obj.c_str(), obj.size());

== buffer overflow jeśli wejściowy string będzie dłuzszy niz 100 znaków. Poza tym nie dodajesz na koniec \0 ani nie zerujesz bufora, więc skąd wiesz gdzie się taki string kończy?
Jeśli juz to uzyłbym strncpy i ograniczył liczbę kopiowanych bajtów do rozmiaru bufora.
Wzorem to bym tego kodu nie nazwał ;)

0

Możez zapisywać (serializować) do plików na kilka prostych sposobów.

  1. Zapisać do plików zawartość string::c_str() (razem z nullem). Plik bedzie posiadał stringi poodzielane nulami, łatwo je z powrotem sparsować.
 outputFile.write(array[i].c_str(), array[i].length()+1); 

Jeżeli usuniesz +1 to też zapisze poprawnie, ale bez nulla na koncu, co znaczy, że nie będziesz wiedział kiedy jeden string sie konczy a drugi zaczyna.

  1. Można też zastosować format
    ilosc_elementow\0 ilosc_bajtow\0bajty\0 ilosc_bajtow\0bajty\0 ...
    np.
    3\06Tomek\06Romek\07Atomek\0

  2. Można pokusić sie także o tablice indeksujaca, tzn na poczatku pliku miec np. taką strukture:

struct header {
    int32_t count;
    int64_t offsets[max_strings]; // count < max_strings
}

gdzie offset[i] jest offsetem w pliku stringa numer i. Po wczytaniu headera mozna skakać po pliku seek'iem.

0
  1. Usuń smrodek przy inkrementacji http://4programmers.net/Forum/1101404
ofstream &operator<<(ofstream &fout,const string &s)
  {
   size_t len=s.size();
   s.write((char*)&len,sizeof(size_t));
   s.write(&s[0],len);
   return fout;
  }

ifstream &operator>>(ifstream &fin,string &s)
  {
   size_t len;
   s.read((char*)&len,sizeof(size_t));
   s.resize(len);
   s.read(&s[0],len);
   return fin;
  }

ofstream fout(fileName.c_str(),ios::out|ios::binary);
for(int i=0;i<4;++i) fout<<array[i];
fout.close();

ifstream fin(fileName.c_str(),ios::in|ios::binary);
for(int i=0;i<4;++i) fin>>array[i];
fin.close();

1 użytkowników online, w tym zalogowanych: 0, gości: 1