• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

question-closed [C] Czytanie dużego pliku linijka po linijce fgets()

Object Storage Arubacloud
0 głosów
386 wizyt
pytanie zadane 27 stycznia 2022 w C i C++ przez Marak123 Stary wyjadacz (11,190 p.)
zamknięte 30 stycznia 2022 przez Marak123

Witam smiley

Mam prosty kodzik napisany w "C" który odczytuje duży plik tekstowy linijka po linijce i wypisuje te linijki na ekran(dla przykładu), ale gdy czyta te linijki to przy 6701 linijce się zatrzymuje mimo że plik na pewno jest dłuższy bo sprawdzałem w programie klogg. Po wyświetleniu pliku w tym programie zauważyłem ze pętla zakończa działanie na linijce która jest długa ma ok 250 znaków tylko ze te znaki są trochę dziwne prawdopodobnie złe kodowanie ale nie ważne bo linijki poprzednie które też miały takie znaki normalnie czytała funkcja fgets() ustawiona na 1024 znaki a przy tej linijce zawiesza się funkcja fgets() pokazując za każdym razem null mimo ze następna linijka już jest normalna. 

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int main(void)
{
   FILE* fp = fopen("./../file.txt", "r");
   enum {
      BUF_SIZE = 10000000
   };
   char *buffer = (char*)malloc(BUF_SIZE);

   if (fp == NULL)
   {
      printf("Error opening file!\n");
      exit(1);
   }


    int counter = 0;

   clock_t start = clock();
   while (fgets(buffer, BUF_SIZE, fp) != NULL)
   {
      printf("Cos: %s \n", buffer);
      // printf("%s", buffer);
      counter++;
   }
   free(buffer);

   clock_t end = clock();
   double time_spent = (double)(end - start) / CLOCKS_PER_SEC;
   printf("Time: %f\n", time_spent);

   printf("Counter: %d\n", counter);
   printf("End of file\n");

   fclose(fp);
}

Wie ktoś dlaczego tak się może dziać i jak zabezpieczyć program aby czytało kolejne linijki a nie za się zawieszało i zwracało cały czas null? 

Myślałem ze jest to problem z długością wiersza ale użyłem malloc() jak widać w kodzie ale nadal to samo.

komentarz zamknięcia: Dzięki za pomoc. Problem rozwiązany. Kod w komentarzu
1
komentarz 27 stycznia 2022 przez Oscar Nałogowiec (29,320 p.)
Dziwną masz metodę definiowania BUF_SIZE. Nie wiem czy to nie za dużo na wartość enum. Jest opcja kompilacji powodujące użycie short jako wartości enumów.
komentarz 28 stycznia 2022 przez Marak123 Stary wyjadacz (11,190 p.)
Mógł bym dać sobie #define wiem ale nie ukrywając większość tego kodu jest z stackoverflow i trochę wziąłem z tematu czytania pliku linijka po linijce w następnym temacie znalałem jak ktoś pisał ze źle została zdefiniowana stała i dał taki kod z użyciem enum więc też tak zrobiłem ale mogłem też użyć #define
1
komentarz 28 stycznia 2022 przez Oscar Nałogowiec (29,320 p.)

Sprawdzałeś ten program na "normalnym" pliku tekstowym, takim zawierającym znaki w zakresie 0-127? Też przypuszczam, że problemem jest plik, a nie program. Czytasz jako plik tekstowy coś, co niekoniecznie jest takim plikiem. Napisałeś dziwne zdanie

linijki poprzednie które też miały takie znaki normalnie czytała funkcja fgets() ustawiona na 1024 znaki a przy tej linijce zawiesza się funkcja fgets() pokazując za każdym razem null mimo ze następna linijka już jest normalna

Przecież z kodu wynika, że NULL odczytujesz tylko raz i przerywasz program (słusznie), nie bardzo rozumiem tego "za każdym razem". Chodzi o kolejne uruchomienia programu? Może tam brakuje znaków końca linii, lub są niestandardowe. Pracujesz na linuxie (sądząc po ścieżce do pliku tak)? Pewnie ten plik jest za duży by go wrzucić na forum, zresztą jak ma dziwne znaki to najlepiej byłoby umieścić jego hexdump a przynajmniej tej "okolicy" gdzie występuje problem.

komentarz 28 stycznia 2022 przez Marak123 Stary wyjadacz (11,190 p.)

Ogólnie odnośnie tej linijki którą zacytowałeś to kod który dałem jest inny bo testowałem sobie z pętlą while która jest zawsze na true

while (true)
   {
      fgets(buffer, BUF_SIZE, fp)
      printf("Cos: %s \n", buffer);
   }

o to mi chodziło ze nawet jeżeli jest null to czyta dalej ale jak pisałem to po pierwszym null-u, czyli gdy doszło do linijki z jakimiś znakami pewnie inny encoding wyskoczył null i już tak zostało mimo że pętla cały czas działał i czytała kolejne wiesze.

I dlatego stąd pytanie jak zabezpieczyć skrypt przed tym aby się nie zawieszał gdy napotka dziwne znaki lub wystąpi jakiś błąd i czytał plik dalej?

 

komentarz 28 stycznia 2022 przez Marak123 Stary wyjadacz (11,190 p.)

Ogólnie mogę podesłać jeszcze linijkę na której zatrzymuje się odczytywanie.

Ta linijka na której zatrzymuje się odczytywanie:

~��>� ��$���Ky�oF���.���`���������`z\�i��։�햮W0������)_=\ָMSmnlڪ�BQ�Pڈ��)* K��EXU �E���+e��)R�`�:�^w���m�%!�@�1�����`9O�9Q0��        �|���[���N__��u�6=��V�M8}ήѦ�ٓ���⺭2�y�ٟ}"�_�O��dɇ�y���5s���'   �8 ;�젇�ָ
�]Pj��F9��T<���el�9n

A to jest część pliku, naokoło tej linijki, wiersze przed i po które. I te wiersze przed są czytane ale te wiersze pod już nie czyta bo pokazuje null

~+_
~+_-_$+<->
~+-
~+-">'}'+/
~+,
~+;
~+:
~+!
~+?
~+/
~+/=}&#<.%
~+/\*-_)@`
~+.
~+.!;-#/)[
~+'
~+')
~+"
~+"}{*~|"\
~+(
~+)
~+):{?*>~|
~+[
~+[_
~+]
~+]^
~+{
~+}
~+}@,",'{#
~+}#
~+@
~+@,
~+$
~+*
~+\
~+&
~+#
~+#"
~+%
~++
~++.
~�
~�~
~��
~��>� ��$���Ky�oF���.���`���������`z\�i��։�햮W0������)_=\ָMSmnlڪ�BQ�Pڈ��)* K��EXU �E���+e��)R�`�:�^w���m�%!�@�1�����`9O�9Q0��	�|���[���N__��u�6=��V�M8}ήѦ�ٓ���⺭2�y�ٟ}"�_�O��dɇ�y���5s���'	�8 ;�젇�ָ
�]Pj��F9��T<���el�9n
~	>;
~	-}
~	-#
~	;<
~	!/]
~	?
~	">'
~	)'
~	#`
~	#/
~
÷
÷÷÷
÷÷÷÷÷
÷←→§
÷�
÷�

A program zwraca coś takiego przez printf:

Cos: ~+_

Cos: ~+_-_$+<->

Cos: ~+-

Cos: ~+-">'}'+/

Cos: ~+,

Cos: ~+;

Cos: ~+:

Cos: ~+!

Cos: ~+?

Cos: ~+/

Cos: ~+/=}&#<.%

Cos: ~+/\*-_)@`

Cos: ~+.

Cos: ~+.!;-#/)[

Cos: ~+'

Cos: ~+')

Cos: ~+"

Cos: ~+"}{*~|"\

Cos: ~+(

Cos: ~+)

Cos: ~+):{?*>~|

Cos: ~+[

Cos: ~+[_

Cos: ~+]

Cos: ~+]^

Cos: ~+{

Cos: ~+}

Cos: ~+}@,",'{#

Cos: ~+}#

Cos: ~+@

Cos: ~+@,

Cos: ~+$

Cos: ~+*

Cos: ~+\

Cos: ~+&

Cos: ~+#

Cos: ~+#"

Cos: ~+%

Cos: ~++

Cos: ~++.

Cos: ~Ç

Cos: ~ę~

Cos: ~┬°

Cos: ~¨▓>ó

Początek się zgada ale ostatni wiersz który zwraca program to wiersz tej długiej linijki.

I właśnie przed takim czymś chcę ochronić program. Ktoś jakiś pomysł??

1
komentarz 28 stycznia 2022 przez Oscar Nałogowiec (29,320 p.)
Chyba po prostu musisz przestać zakładać, że plik jest tekstowy i zacząć traktować go jako binarny, czytać blok po bloku (fread) i sprawdzać, czy we wczytywanych danych jest jakiś fragment tekstu. Trochę tam za dużo tych "krzaczków", nie wygląda to na jakieś egzotyczne kodowanie, chyba, że to tekst pisany jakimś egzotycznym alfabetem, gdzie każdy znak jest spoza ASCII.
komentarz 28 stycznia 2022 przez Marak123 Stary wyjadacz (11,190 p.)
Ewidentnie coś jest z plikiem bo wczytując 1mln znaków za każdym razem urywa mi wczytywanie w tej linijce mimo ze wczytując 100tys urywa i wczytując 1mln znaków urywa więc chyba muszę wywalić ten plik.

Dzięki za pomysł z czytanie w trybie binarnym.
1
komentarz 28 stycznia 2022 przez Oscar Nałogowiec (29,320 p.)

Sugerujesz, że jest jakiś problem sprzętowy w tym plikiem? Całkiem możliwe, jeśli dysk ma uszkodzenie i ono wypada akurat w tym pliku. Jeśli działasz pod Linuxem (co sugeruje ścieżka do pliku) możesz poleceniem dmesg sprawdzić ostatnie komunikaty jądra - błąd odczytu z dysku będzie tam widoczny, gdzieś pod koniec. Sprawdź po uruchomieniu programu. Zwykle system próbuje odczytać uszkodzone miejsce kilka razy i trwa to sekundę - dwie - widać takie dziwne zatrzymanie.

komentarz 28 stycznia 2022 przez Marak123 Stary wyjadacz (11,190 p.)

Ogólnie rzecz biorąc działam na Windows a ścieżka jest taka bo dużo robię na Linuks i odruchowo daje slash zamiast backslash ale działa. Jeżeli chodzi o dysk to jest sprawny ponieważ plik który próbuję odczytać był łączony za pomocą cat na Linux z 3 osobnych plików i po odczytaniu jednego z plików który był łączony jest ten sam problem są dziwne znaczki i program się zatrzymuje zwracając null na tej samej linijce a usuwałem ten plik parę razy i dekompresowałem z archiwum więc zapisywał on się na nowo w innych miejscach dysku więc jest sprawny a po za tym wiem ze to nic nie zmienia ale dysk jest używany dopiero ok. 4 miesiące. Tak przeglądałem teraz sobie zmienne na microsoft stronie i znalazłem ze domyślnie char ma zakres znaków od -128 do 127 ale gdy się użyje /J parametru przy kompilacji to jest ta wartość zmieniana od 0 do 255.

Nie sprawdzałem tego jeszcze bo zrobiłem pętle która odczytuje plik w postaci binarnej znak za znakiem i sprawdza czy jest \n co prawda z tamtym plikiem to nie działało bo wywalało ale z innym już działa.

while(fread(buffer, sizeof(char), 1, fp) == 1){
      if(buffer[0] == '\n'){
         counter++;
      }
   }

Mając taki kod jest to optymalne rozwiązanie które najszybciej zliczy znak nowej lini czy jednak jest to słabe rozwiązanie pod względem szybkości??

1
komentarz 29 stycznia 2022 przez Oscar Nałogowiec (29,320 p.)
Zdecydowanie lepiej jest czytać większe bloki, tak wielokrotność dawniej 512 bajtów, współcześnie chyba 8KB. TO potrafi przyspieszyć pracę kilkukrotnie. Liczenie pojedynczych znaków ('\n') pozostaje proste, trudniej jest poszukiwać sekwensji kilkuznakowych, bo mogą zaczynać się w jedym bloku a kończyć w następnym.
komentarz 29 stycznia 2022 przez Marak123 Stary wyjadacz (11,190 p.)

Ok dzięki zrobiłem skrypt z czytaniem całych bloków 1024 bajty i liczby nowe linie(\n) w bloku(tablicy unsigned char). Czyta bloki sposobem binarnym fread() potem pętla for po tablicy bufora i zliczanie znaku za znakiem znaku nowej linii. Dzięki za podpowiedzi, plik 8.5mld linijek zlicza w ok.5 min w porównaniu ze skryptami zrobionymi wcześniej które liczyły to w 30 min jak np. w python skrypt to to jest bardzo duże przyśpieszenie.

Chcę zrobić teraz multiprocessing czyli ze będzie funkcja która się odpali w osobnym procesie i będzie np. liczyć ilość powtórzeń znaku nowej linii w przekazanym bloku danych. Jako ze działam na Windows to chcę to zrobić za pomocą CreateProcess() tylko ze z tego co widzę to odpalić muszę inny skomplikowany już program a nie tak jak to było w python samą funkcje, jakoś spróbuję to ogarnąć. Dzięki za pomoc.smileyyes

Kod do zliczania ilość znaków nowej linii w pliku:

#include <stdio.h>
#include <time.h>
#include <unistd.h>

#define BUF_SIZE 10240
#define FILENAME_FILE "C:\\Users\\user\\Desktop\\file.txt"

int main(void)
{

   FILE* fp = fopen(FILENAME_FILE, "rb");
   unsigned char buffer[BUF_SIZE];// = (unsigned char*)malloc(BUF_SIZE * sizeof(unsigned char));

   if (fp == NULL)
   {
      printf("Error opening file!\n");
      exit(1);
   }

   unsigned long long int counter = 0;

   clock_t start = clock();
   size_t loadedItem = 0;

   while((loadedItem = fread(buffer, sizeof(unsigned char), BUF_SIZE, fp)) != 0){
      for(int i = 0; i < loadedItem; i++)
         if((int)buffer[i] == 10)
            counter++;
   }

   clock_t end = clock();
   double time_spent = (double)(end - start) / CLOCKS_PER_SEC;

   printf("Filename: %s \n", FILENAME_FILE);
   printf("Time: %f\n", time_spent);

   printf("Counter: %llu\n", counter);
   printf("\nEnd of file\n");

   fclose(fp);
   system("pause");
}

 

1
komentarz 30 stycznia 2022 przez Oscar Nałogowiec (29,320 p.)
Rozróżniaj wątek od procesu - proces to oddzielny program z własną pamięcią, wątek to "taki drugi procesor" wykonujący ten sam kod w tej samej pamięci. W drugim przypadku masz wspólną pamięć i zasadniczo uruchamiasz którąś z funkcji.
komentarz 30 stycznia 2022 przez Marak123 Stary wyjadacz (11,190 p.)
Dzięki za informacje ale próbuję właśnie zrobić oddzielne procesy bo nie wiem czy dobrze myślę ale parę wątków jest bez sensowne ponieważ wykonywanie procesów będzie się zazębiał czyli raz będzie wykonywało się jedno zadanie wątku 1 a raz wątku 2 potem od nowa wątek 1 i potem wątek 2. Dobrze myślę??  

A procesy to chyba działają jednocześnie. Pewnie gadam głupoty.

Czy wątek będzie lepszym rozwiązaniem czy jednak szybciej zliczą się znaki używając osobnych procesów??
1
komentarz 30 stycznia 2022 przez Oscar Nałogowiec (29,320 p.)
Wątki też wykonują się w sposób asynchroniczny, nie ma żadnej zależności co do kolejności wykonywania, nawet wręcz instrukcje różnych wątków mogą się wykonywać zupełnie jednocześnie, szczególnie w wielordzeniowych architekturach.

W wątkach masz wspólną pamięć - zmienne (głównie globalne) różnych wątków to są te same zmienne - jak jeden wątek coś zmieni w takiej zmiennej, to i drugiemu się zmieni. Powoduje to łatwą komunikację, ale też powoduje wiele kłopotliwych sytuacji związanych z nieokreślonością kolejności wykonywania instrukcji. Te wszystkie semafory, mutexy, kolejki, zmienne warunkowe itp. Dwa oddzielne procesy wykonują się całkowicie niezależnie z wyjątkiem jawnie zaprogramowanych mechanizmów współpracy. Oczywiście pliki na dysku są, tak czy inaczej, wspólne.
komentarz 30 stycznia 2022 przez Marak123 Stary wyjadacz (11,190 p.)

Ok dzięki za wyjaśnienie czyli może lepszym wyborem będą osobne wątki a nie procesy.
Dziękismiley

Podobne pytania

0 głosów
0 odpowiedzi 145 wizyt
pytanie zadane 8 września 2022 w C i C++ przez benny13 Obywatel (1,150 p.)
0 głosów
1 odpowiedź 1,601 wizyt
pytanie zadane 28 marca 2017 w C i C++ przez Kamil Duljas Użytkownik (990 p.)
0 głosów
2 odpowiedzi 705 wizyt
pytanie zadane 29 listopada 2017 w C i C++ przez Krystek102 Bywalec (2,440 p.)

92,615 zapytań

141,465 odpowiedzi

319,778 komentarzy

61,997 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...