SIGSEGV w dziwnym miejscu...

0

Witam, mam problem z nieszczęsnym sigsegv... Tak, wiem, że ten błąd powodowany jest przez (prawdopodobnie) wyjście poza tablicę jakimś wskaźnikiem albo innym cudem, ale, czy ktoś spotkał się z wyjściem poza tablicę w miejscu, gdzie program się kończy?...
Kompilator: lazarus, język pascal.

Kod:
!! PLIK bazaDanychWskazniki !!

 program bazaDanychWskazniki;

uses unit_TOsoba, unit_bazadanych;

var
  mojaBaza : wskTOsoba;
  i : integer;

begin
  utworzBaze(mojaBaza, 2);

  wczytajBazeZPliku(mojaBaza, 'pliki/baza.tos');

  powiekszBaze(mojaBaza, 1);
  wypelnijTOsoba(mojaBaza[6]);

  for i:=1 to mojaBaza[0].wiek do
      wyswietlTOsoba(mojaBaza[i]);

  writeLn('abc');
  readLn();
end.
  

!! PLIK unit_TOsoba !!

 
unit unit_TOsoba;

interface

type
    TOsoba = record
           imie:String[16];
           nazwisko:String[16];
           wiek:integer;
    end;
    wskTOsoba = ^Tosoba;
procedure wypelnijTOsoba(var osoba : TOsoba);
procedure wyswietlTOsoba(var osoba : TOsoba);

implementation

procedure wypelnijTOsoba(var osoba : TOsoba);
begin
  writeLn('Wypelnij dane osobowe...');
  write('Imie: ');
  readLn(osoba.imie);
  write('Nazwisko: ');
  readLn(osoba.nazwisko);
  write('Wiek: ');
  readLn(osoba.wiek);
end;

procedure wyswietlTOsoba(var osoba : TOsoba);
begin
  writeLn(osoba.imie:16, '|', osoba.nazwisko:16, '|', osoba.wiek:3);
end;

end.  

!! PLIK unit_bazaDanych !!

 unit unit_bazadanych;

interface

uses unit_TOsoba;

procedure utworzBaze(var baza : wskTOsoba; const ileUtworzyc : integer);
procedure usunBaze(var baza : wskTOsoba);
procedure powiekszBaze(var baza : wskTOsoba; const oIlePowiekszyc : integer);
procedure wczytajBazeZPliku(var baza : wskTOsoba; const sciezka : string);
procedure zapiszBazeDoPliku(const baza : wskTOsoba; const sciezka : string);

implementation

procedure utworzBaze(var baza : wskTOsoba; const ileUtworzyc : integer);
begin
  getMem(baza, sizeOf(baza^) * ileUtworzyc+1);
  baza[0].wiek := ileUtworzyc;
end;

procedure usunBaze(var baza : wskTOsoba);
begin
  freeMem(baza, sizeOf(baza^) * baza[0].wiek+1);
end;

procedure wczytajBazeZPliku(var baza : wskTOsoba; const sciezka : string);
var
  i, rozmiarPliku: integer;
  plik : file of TOsoba;
  strumienTOsoba : wskTOsoba;
begin
  assign(plik, sciezka);
  reset(plik);
  new(strumienTOsoba);

  rozmiarPliku := 0;
  while not eof(plik) do
  begin
    read(plik, strumienTOsoba^);
    rozmiarPliku := rozmiarPliku+1;
	end;

  if (rozmiarPliku > baza[0].wiek ) then
     powiekszBaze(baza, (rozmiarPliku - baza[0].wiek) );

  reset(plik);

  for i:=1 to (baza[0].wiek) do
      read(plik, baza[i]);


  close(plik);
  dispose(strumienTOsoba);
end;

procedure zapiszBazeDoPliku(const baza : wskTOsoba; const sciezka : string);
var
  plik : file of TOsoba;
  i:integer;
begin
  assign(plik, sciezka);
  rewrite(plik);

  for i:=1 to (baza[0].wiek) do
    write(plik, baza[i]);

  close(plik);
end;

procedure powiekszBaze(var baza : wskTOsoba; const oIlePowiekszyc : integer);
var
  nowaBaza : wskTOsoba;
  i : integer;
begin
  utworzBaze(nowaBaza, (baza[0].wiek + oIlePowiekszyc));
  for i:=1 to (baza[0].wiek) do
  begin
    nowaBaza[i].imie := baza[i].imie;
    nowaBaza[i].nazwisko := baza[i].nazwisko;
    nowaBaza[i].wiek := baza[i].wiek;
	end;
  usunBaze(baza);
  baza := nowaBaza;
end;

end.
        

Okej, jak mamy kod, to teraz najśmieszniejsze... Kompilator wywala SIGSEV po readLn(); w głównym miejscu wykonywania programu, czyli w pliku bazaDanychWskazniki (tym 1.). Kiedy uruchamiam program z .exe'ka, wywala runtime errory 216 w konsoli dokladnie w tym samym miejscu (linijka writeLn('abc') umieszczona w celu debugowania...). Tak, debugowałem program krok po kroku i sigseva + runtime'y wywala dopiero po tym readLn();...

Problem pojawił się kiedy dodałem linijkę powiekszBaze(mojaBaza, 1) i wypełniłem nowy rekord... bez tych dwóch linijek kod działa bez zarzutu.

Celowo z kodu usunąłem procedurę zwalniającą pamięć po bazie (usunBaze), której używałem na końcu programu, ale niestety dzieje się to samo (myślałem, że to ona wychodzi poza zakres).

Czy ktoś miał takie przykre doświadczenia i wie, jak z tego wybrnąć?

Pozdrawiam, Rosumad.

2

Procedura wczytajBazeZPliku jest dziwna przecież możesz sprawdzić rozmiar pliku po otwarciu (FileSize w plikach typowanych to ilość rekordów), później na tej podstawie ustalasz wielkość bazy i czytasz plik do końca not EOF a nie jakieś wydziwiani, przy powiększaniu bazy też lecisz pętla przenosząc rekordy zamiast normalnie Movealbo lepiej od razu wykorzystać ReAllocMem.

0

Po wykorzystaniu rad kAzka kod zmienionych procedur wygląda tak:

 
procedure powiekszBaze(var baza : wskTOsoba; const oIlePowiekszyc : integer);
begin
  baza := reAllocMem(baza, (baza[0].wiek + oIlePowiekszyc) );
  baza[0].wiek += oIlePowiekszyc;
end;

procedure wczytajBazeZPliku(var baza : wskTOsoba; var plik : plikTOsoba);
var
  i: integer;
begin
  if (fileSize(plik) > baza[0].wiek ) then
     powiekszBaze(baza, (fileSize(plik) - baza[0].wiek) );

  for i:=1 to fileSize(plik) do
      read(plik, baza[i]);
end; 

I - co najważniejsze - nie wywala sigsegvow i runtime errorow... Dziękuję bardzo, kAzku. A tak na przyszłość, ktoś wie CO powodowało błąd w miejscu, gdzie nie robiłem już nic z pamięcią?

@Edit
Testuje sobie ten kod i właśnie zauważyłem, ze procedura usunBaze także wywala sigseva... Co ciekawe, (znów) nie wywala błędu kiedy używam procedury usunBaze, ale dopiero po zakończeniu wykonywania programu... Dodatkowo testując zauważyłem, że kiedy użyje procedury usunBaze, a potem odwołam się do bazy, to odwołania normalnie się wykonują i dane się wypisują...

 
procedure usunBaze(var baza : wskTOsoba);
begin
  freeMem(baza, (sizeOf(baza^) * baza[0].wiek) );
end; 

program bazaDanychWskazniki;

uses unit_TOsoba, unit_bazadanych;
const Sciezka = 'pliki/baza.tos';

var
  mojaBaza : wskTOsoba;
  i : integer;
  plik : plikTOsoba;

begin
  utworzBaze(mojaBaza, 2);
  assign(plik, Sciezka);
  reset(plik);

  wczytajBazeZPliku(mojaBaza, plik);
  close(plik);

  powiekszBaze(mojaBaza, 1);
  wypelnijTOsoba(mojaBaza[mojaBaza[0].wiek]);

  usunBaze(mojaBaza);

  for i:=1 to mojaBaza[0].wiek do
      wyswietlTOsoba(mojaBaza[i]);

  readLn();
end.

Powyższy kod kompiluje się, wyświetla poprawnie zawartość pliku oraz rekord który sam wpiszę (pomimo zastosowania usunBaze zaraz po wypelnieniu tych wszystkich rzeczy), a na sam koniec, po kliknięciu entera (z racji readLn()), wywala sigseva... Mam dwie hipotezy czemu tak się dzieje:
a) nie mam pojęcia jak używać freeMem, albo ogólnie tych funkcji zarządzania pamięcia...
b) czegoś nie rozumiem.
Pozdrawiam ;)

@edit2
Chyba wiem, poprawcie mnie jeśli nie, dlaczego mogę odwołać się do bazy, pomimo jej usunięcia. Prawdopodobnie dlatego, że dane na stercie nadal zostają (nie czyszcze ich), a kiedy mowie kompilatorowi writeLn(baza[5].nazwisko); to on bierze adres, na który wskazuje baza, leci 5 * ilość bajtów w rekordzie do przodu + ilość bajtów w imieniu i wyświetla mi tą zmienną, która znajduje się w następnej komórce pamięci, tak?
Okej, ale czemu to wywala tego sigseva...

0

@Rosumad - cały ten kod to jeden wielki WTF; Nazewnictwo elementów beznadziejne, kompletnie niezgodne z przyjętą i powszechną konwencją nazewnictwa; Formatowanie zupełnie randomowe, nie trzymające się kupy; Zabawy ze wskaźnikami, choć nie są one w ogóle potrzebne; Baza jest de facto wskaźnikiem na rekord, a nie na jakiś sensowny kontener; Kod jest tak pokręcony i nieczytelny, że kompletnie nie wiadomo co się w nim dzieje i jedyne co można z nim zrobić to skasować;

Wywal ten kod i zacznij pisać od nowa, tym razem powoli i z głową.

0

Masz na pewno jeden błąd w powiekszBaze() - nie używasz wielkości rekordu.

0
procedure usunBaze(var baza : wskTOsoba);
begin
  freeMem(baza, (sizeOf(baza^) * baza[0].wiek) );
end;

Co to w ogóle jest? Co ma wiek osoby do rozmiaru bazy? I dziwisz się, że lecą wyjątki naruszenia dostępu do pamięci.

0

https://www.tutorialspoint.com/pascal/pascal_memory.htm

ReAllocMem nie jest zbudowana tak, że: reAllocMem(baza, 20) realokuje mi bazę na 20 rekordów? Bo jak zrobię reAllocMem(baza, sizeOf(baza^) * 20), to już nawet wyświetlanie i pobieranie bazy z pliku się wykrzacza, a jak jest bez sizeOf to wszystko działa poza tym sigsevem na koniec...

Co do formatowania, to tylko furiousowi przeszkadza? Czy wszystkim? Nie wiem, formatuje tak na wyczucie, dla mnie (tak wiem... dla mnie...) kod jest czytelny. Jeśli jest faktycznie bardzo źle, to postaram się popracować nad tym, ale niech się wypowie większa ilość osób na ten temat, proszę.

A co do logiki kodu itd., wybacz, że tak wymyśliłem. Faktycznie, można by to zrobić bez udziwniania wskaźnikami, ale niestety, mój wykładowca sądzi inaczej.

1

Bo jak zrobię reAllocMem(baza, sizeOf(baza^) * 20), to już nawet wyświetlanie i pobieranie bazy z pliku się wykrzacza, a jak jest bez sizeOf to wszystko działa poza tym sigsevem na koniec...

Jeśli chcesz skorzystać z ReallocMem to musisz mieć jeden ciągły blok pamięci do przeniesienia;

Co do formatowania, to tylko furiousowi przeszkadza? Czy wszystkim?

A widziałeś kiedyś wytyczne co do formatowania kodu? Hmm?

Wytyczne te pozwalają pisać kod w sposób czytelny i zrozumiały dla wszystkich, a nie tylko dla autora kodu; Nie widziałeś, pewnie nie miałeś o tym pojęcia, więc zobacz np. tutaj - Object Pascal Style Guide;

Pisałem już kilka postów wyżej, że ten kod to WTF i nie po to, aby zrobić Ci na złość; Twój program leży, pomijając błędne działanie i wyjątki dotyczące naruszenia pamięci, Twój kod napisany jest niedbale, ma złe formatowanie, do tego już sam zamysł przechowywania danych jest bardzo zły, przekombinowany, zagmatwany; Kod ten należy usunąć i napisać od nowa, uprzednio zapoznając się z artykułem, do którego link podałem w poprzednim paragrafie;

A co do logiki kodu itd., wybacz, że tak wymyśliłem. Faktycznie, można by to zrobić bez udziwniania wskaźnikami, ale niestety, mój wykładowca sądzi inaczej.

Nie rozumiesz - samo użycie wskaźników nie jest złe, bo to zadanie widocznie ma sprawdzić Twoją wiedzę z tego zakresu; Problemem jest to, że logika Twojego programu to kupa jakichś cudów i kombinacji, które znane były programistom NES 25 lat temu, kiedy walczono o każdy bajt pamięci, stosując różne triki z alokacją pamięci w miejscach przeznaczonych do zupełnie innych rzeczy; Łamiesz regułę KISS, a to bradzo źle;

Dziś nie ma praktycznie żadnych ograniczeń co do pamięci, więc upychanie rozmiaru bazy tam, gdzie normalnie znajduje się długość krótkiego łańcucha to bardzo głupi pomysł; A dowodem tego jest chaos panujący w tym kodzie - bez dogłębnej i czasochłonnej analizy nie można jasno określić co się w nim dzieje; I to nie jest mój problem czy innych próbujących pomóc, a Twój, bo to Ty tracisz czas na użeranie się z wyjątkami, nie my;

Jeśli potrzebujesz użyć wskaźników to wręcz idealnym kontenerem na dane jest standardowa lista jednokierunkowa lub dwukierunkowa; Skorzystanie z takiego kontenera pozwoli na wykorzystanie wskaźników w sensowny sposób, dzięki czemu Twój program będzie miał ręce i nogi.

0

Zrobiłem tak, jak napisał furious programming, czyli usunąłem cały kod i napisałem od nowa ;) Teraz przy użyciu listy jednokierunkowej. Aktualnie program nie wyrzuca żadnych błędów, ale wklejam kod, może potomnym się przyda. furious, sprawdź proszę czy jest lepiej w kwestii formatowania a przede wszystkim te nazwy zmiennych nieszczęsne...

unit TDaneOsoboweRekord

 
{
Typy:
  TDaneOsobowe przechowuje dane na temat 1 osoby
  PtrTDaneOsobowe to wskaznik na TDaneOsobowe
  PlikTDaneOsobowe to plik z rekordami typu TDaneOsobowe

Funkcje:
  WyswietlRekord: wyswietla rekord na konsole cmd w formacie
    |          Imie |        Nazwisko | Wiek |
  WypelnijRekord: kaze uzytkownikowi wypelnic 1 rekord
  WczytajRekordZPliku: wczytuje 1 rekord z pliku
  ZapiszRekordDoPliku: zapisuje 1 rekord do pliku }

unit TDaneOsoboweRekord;

interface

  type
    TDaneOsobowe = record
      FImie: String[16];
      FNazwisko: String[16];
      FWiek: Integer;
    end;
    PtrTDaneOsobowe = ^TDaneOsobowe;
    PlikTDaneOsobowe = file of TDaneOsobowe;

  procedure WyswietlRekord(const Rekord: TDaneOsobowe);
  procedure PobierzRekord(var Rekord: TDaneOsobowe);
  procedure WczytajRekordZPliku(var Rekord: TDaneOsobowe;
    var Plik: PlikTDaneOsobowe);
  procedure ZapiszRekordDoPliku(const Rekord: TDaneOsobowe;
    var Plik: PlikTDaneOsobowe);

implementation

  { WyswietlRekord: wyswietla rekord na konsole cmd w formacie
    |          Imie |        Nazwisko | Wiek | }

  procedure WyswietlRekord(const Rekord: TDaneOsobowe);
  begin
    WriteLn('| ', Rekord.FImie:16, ' | ', Rekord.FNazwisko:16, ' | ',
      Rekord.FWiek:3, ' |');
  end;

  procedure PobierzRekord(var Rekord: TDaneOsobowe);
  begin
    WriteLn('Wypelnij rekord: ');
    Write('Imie: ');
    ReadLn(Rekord.FImie);
    Write('Nazwisko: ');
    ReadLn(Rekord.FNazwisko);
    Write('Wiek: ');
    ReadLn(Rekord.FWiek);
  end;

  { WczytajRekordZPliku:
    Procedura wymaga przekazania otwartego juz pliku }

  procedure WczytajRekordZPliku(var Rekord: TDaneOsobowe;
    var Plik: PlikTDaneOsobowe);
  begin
    read(Plik, Rekord);
  end;

  { ZapiszRekordDoPliku:
    Procedura wymaga przekazania otwartego juz pliku }

  procedure ZapiszRekordDoPliku(const Rekord: TDaneOsobowe;
    var Plik: PlikTDaneOsobowe);
  begin
    write(Plik, Rekord);
  end;

end.

unit TListaDanychOsobowych

 
{
Typy:
  TLista reprezentuje 1 element listy jednokierunkowej rekordow TDaneOsobowe
  PtrTLista to wskaznik na TLista

Funkcje:
  ListaDodaj: dodaje rekord na koniec istniejacej listy
  ListaUsun: usuwa rekord z konca listy
  ListaWczytajZPliku: wczytuje baze z pliku
  ListaZapiszDoPliku: zapisuje baze do pliku
  ListaWyswietl: wypisuje baze na cmd korzystajac z WyswietlRekord}

unit TListaDanychOsobowych;

interface
  uses TDaneOsoboweRekord;

  type
    PtrTLista = ^TLista;
    TLista = record
      FDaneOsobowe: TDaneOsobowe;
      FNastepny: PtrTLista;
    end;

  procedure ListaDodaj(Poprzedni: PtrTLista;
    const NowyRekord: TDaneOsobowe);
  procedure ListaUsun(Poprzedni: PtrTLista);
  procedure ListaWczytajZPliku(Poprzedni: PtrTLista;
    var Plik: PlikTDaneOsobowe);
  procedure ListaZapiszDoPliku(Poprzedni: PtrTLista;
    var Plik: PlikTDaneOsobowe);
  procedure ListaWyswietl(Poprzedni: PtrTLista);

implementation

  procedure ListaDodaj(Poprzedni: PtrTLista;
    const NowyRekord: TDaneOsobowe);
  var
    Tmp: PtrTLista;
  begin
    new(Tmp);
    Tmp^.FDaneOsobowe := NowyRekord;
    Tmp^.FNastepny := nil;

    while Poprzedni^.FNastepny <> nil do // jesli nie jestem na koncu
    begin                                // listy, idz dalej
      Poprzedni := Poprzedni^.FNastepny;
    end; // po wykonaniu petli mam pewnosc, ze jestem na koncu
    Poprzedni^.FNastepny := Tmp;
  end;

  procedure ListaUsun(Poprzedni: PtrTLista);
  begin
    while Poprzedni^.FNastepny^.FNastepny <> nil do
    begin                                // ustaw na przedostatni element
      Poprzedni := Poprzedni^.FNastepny;
    end;
    Dispose(Poprzedni^.FNastepny);
    Poprzedni^.FNastepny := nil;
  end;

  procedure ListaWczytajZPliku(Poprzedni: PtrTLista;
    var Plik: PlikTDaneOsobowe);
  var
    Tmp: PtrTLista;
  begin
    while not EOf(Plik) do
    begin
      Read(Plik, Poprzedni^.FDaneOsobowe);

      if Poprzedni^.FNastepny = nil then // jesli nie ma nastepnego elementu
      begin                              // to dodaj jeden
        new(Tmp);
        Tmp^.FNastepny := nil;
        Poprzedni^.FNastepny := Tmp;
        Tmp := nil;
      end;

      Poprzedni := Poprzedni^.FNastepny;
    end;
  end;

  procedure ListaZapiszDoPliku(Poprzedni: PtrTLista;
    var Plik: PlikTDaneOsobowe);
  begin
    while Poprzedni^.FNastepny <> nil do // nie zapisze ostatniego elementu
    begin
      Write(Plik, Poprzedni^.FDaneOsobowe);
      Poprzedni := Poprzedni^.FNastepny;
    end;
    Write(Plik, Poprzedni^.FDaneOsobowe); // zapisz ostatni element
  end;

  procedure ListaWyswietl(Poprzedni: PtrTLista);
  begin
    while Poprzedni^.FNastepny <> nil do
    begin
      WyswietlRekord(Poprzedni^.FDaneOsobowe);
      Poprzedni := Poprzedni^.FNastepny;
    end;
  end;

end.

Główny plik programu (tylko operacje sprawdzające działanie)

 
program BazaDanychWskazniki;

uses TDaneOsoboweRekord, TListaDanychOsobowych;

var
  MojaBaza: PtrTLista;
  Temp: PtrTDaneOsobowe;
  MojPlik: PlikTDaneOsobowe;
begin
  New(MojaBaza);
  MojaBaza^.FNastepny := nil;

  Assign(MojPlik, 'pliki/baza.tos');
  Reset(MojPlik);
  ListaWczytajZPliku(MojaBaza, MojPlik);
  Close(MojPlik);

  ListaWyswietl(MojaBaza);
  ReadLn();
end.
1

W pierwszej kolejności wymieńmy kilka technik dotyczących pisania ładnego kodu (który sprawia wrażenie czytelnego):

  1. kod w całości pisany po angielsku (ew. z wyjątkiem komentarzy),
  2. jeden określony styl wcięć i pustych linii,
  3. nazewnictwo elementów kodu:
  4. PascalCase dla nazw modułów, zmiennych globalnych, pól rekordów (standardowych), procedur i funkcji,
  5. prefiks T dla typów,
  6. prefiks P dla typów wskaźnikowych,
  7. prefiks A dla argumentów procedur i funkcji.

Twój kod jest niezgodny z każdym wymienionym punktem :]

Poza tym źle implementujesz listę; O ile globalne procedury czy funkcje w Twoim przypadku mogą zostać to sama implementacja listy powinna wyglądać nieco inaczej; Twoja lista jest zbyt mocno przywiązana do rekordu z danymi osoby, a nie o to chodzi;

Trzymany wskaźnik na węzeł powinien wskazywać głowę listy i nazywać się Head, a nie jakiś Poprzedni; Procedura dodająca dane osoby do listy powinna pobierać wskaźnik na głowę listy i dane osoby; Procedura usuwająca węzeł z listy powinna przyjmować w parametrze wskaźnik na głowę listy oraz indeks węzła do usunięcia, ew. jakieś dane, na których podstawie ta procedura odszuka węzeł i go usunie (imię, nazwisko lub wiek); Procedury ładująca listę z pliku oraz zapisująca listę do pliku powinny pobierać w parametrach nazwę pliku oraz wskaźnik na głowę listy; Natomiast procedura wyświetlająca dane listy na ekranie powinna przyjmować tylko wskaźnik na głowę listy.

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