free(): double free detected in tcache 2

free(): double free detected in tcache 2
Silv
Moderator Wiki
  • Rejestracja:ponad 10 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Warszawa
0

Krótko. Nie znam się dobrze na C. Mam program napisany w C, a w nim m.in. następujący kod:

Kopiuj
...
FILE *inFp = fopen(argv[1], "r");
...
for (int i = 0; i < linesCount; ++i)
{
  ...
  char *line = silvGetLine(inFp);
  ...
  free(line);
  ...
}
...

Program przechodzi przez 60 iteracji tej pętli i w iteracji nr 61 po wykonaniu linijki free(line); otrzymuję komunikat w GDB:

Kopiuj
Breakpoint 2, main (argc=3, argv=0x7fffffffdea8)
    at c-extract-only-articles-content-faster-parser-multiple-files.c:84
84			free(line);
(gdb) p line
$3 = 0x4069c0 ""
(gdb) next
free(): double free detected in tcache 2

Program received signal SIGABRT, Aborted.
__GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
50	  return ret;
(gdb)

Szukałem w internecie i znalazłem kilka wzmianek o tym błędzie (błędzie?). Ale – opisywane sytuacje są bardzo specyficzne i trudno mi je zrozumieć.

Co jest nie tak? Może nie powinienem zwalniać (tutaj) pamięci?

Chciałbym, żeby działało. Ale jeśli byłoby to możliwe, chciałbym pisać maksymalnie zgodnie ze sztuką (nawet pomijając czytelność).


UPDATE: Zaktualizowałem komunikat, by zawierał więcej informacji.


UPDATE2: Funkcja silvGetLine jest moją autorską i używa malloc.


edytowany 10x, ostatnio: Silv
Patryk27
Moderator
  • Rejestracja:ponad 17 lat
  • Ostatnio:ponad rok
  • Lokalizacja:Wrocław
  • Postów:13042
1

Pokaz zrodlo Twojej funkcji.


Silv
Boję się. :P
Silv
Moderator Wiki
  • Rejestracja:ponad 10 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Warszawa
0

As requested:

Kopiuj
char *silvGetLine(FILE *fp)
{
	printf("debug: 'silvGetLine' is running...\n");

	if (fp == NULL)
	{
		fprintf(stderr, "Error: silvGetLine: fp == NULL");
	}

	char c;
	int lineSize = 0;
	while ((c = getc(fp)) != EOF && c != '\n')
	{
		++lineSize;
	}
	char *result = malloc(lineSize + 1); // +1 for NULL character
	fseek(fp, -lineSize, SEEK_CUR);
	fgets(result, lineSize + 1, fp); // +1 because fgets reads "count - 1" bytes

	return result;
}

Zobacz pozostałe 6 komentarzy
Silv
Tzn. widzę problem w tej zamianie – logiczny (w końcu czytam znaki, nie liczby), ale jest on pomijalny w sytuacji większej zgodności z sygnaturą metody bibliotecznej.
AL
Wic polega na tym, że EOF to stała liczbowa, nie znak, a znak i tak posiada reprezentację liczbową ;)
Silv
Właśnie, to jest dziwne, to w takim razie czemu nie wymyślono w czasach powstawania C czegoś, co by umożliwiało jednocześnie czytanie znaków jako znaków oraz sprawdzanie końca pliku jako końca pliku?
Silv
Widzę to tak, że obecnie obie te abstrakcje muszą być konwertowane do kolejnej abstrakcji, int.
Silv
PS. Tzn. nie, że muszą w sensie: programista musi, tylko że sam język tak to specyfikuje.
AL
  • Rejestracja:prawie 11 lat
  • Ostatnio:około 3 lata
  • Postów:1493
0

fgets zwraca Ci str (EDIT: str było ze strony z opisem API, w Twoim przypadku chodzi o result) czy coś innego?
EDIT: nie jestem pewien jak się funkcje z libki standardowej zachowają przy jakiś bardziej egzotycznym kodowaniu plików. Jakie są dane wejściowe? I co na to wszystko valgrind?

edytowany 2x, ostatnio: alagner
szweszwe
  • Rejestracja:ponad 11 lat
  • Ostatnio:2 dni
  • Lokalizacja:Kraków
  • Postów:1694
0
alagner napisał(a):

fgets zwraca Ci str czy coś innego?

fgets to jest z cstdio funkcja http://www.cplusplus.com/reference/cstdio/fgets/

AL
no przecież wiem. "If a read error occurs, the error indicator (ferror) is set and a null pointer is also returned (but the contents pointed by str may have changed)." Na tej samej stronie byłem, stąd str, chodziło mi o result
AL
  • Rejestracja:prawie 11 lat
  • Ostatnio:około 3 lata
  • Postów:1493
1

Sprawa nr 2: na pewno nic Ci się nie dzieje z tym line po drodze? Tzn. problem pojawia się nawet jeżeli zrobisz tak:

Kopiuj
FILE *inFp = fopen(argv[1], "r");
for (int i = 0; i < linesCount; ++i)
{
  char *line = silvGetLine(inFp);
  free(line);
}

Tzn. wybacz, że tak dopytuję, ale obecność "..." wskazuje, że tam się po drodze wiele może dziać ;)

Silv
Moderator Wiki
  • Rejestracja:ponad 10 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Warszawa
0
alagner napisał(a):

fgets zwraca Ci str (EDIT: str było ze strony z opisem API, w Twoim przypadku chodzi o result) czy coś innego?

No… fgets zwraca to, co podane w API.

EDIT: nie jestem pewien jak się funkcje z libki standardowej zachowają przy jakiś bardziej egzotycznym kodowaniu plików. Jakie są dane wejściowe?

Hm... Plik inFp ma ponad 6,5 miliona linijek, więc całego Ci nie podam… Linijka nr 61 wygląda tak:

Kopiuj
<text xml:space="preserve">#REDIRECT [[Computer accessibility]]

Ale GDB, jak napisałem, mówi, że jest pusta (tak przynajmniej sądzę z tego, co wyświetlił).

I co na to wszystko valgrind?

Nie wiem, nie znam człowieka… Mówisz, że powinienem go użyć? Może się przydać? Wolałbym pozostać przy GDB jak długo się da…

alagner napisał(a):

Sprawa nr 2: na pewno nic Ci się nie dzieje z tym line po drodze? Tzn. problem pojawia się nawet jeżeli zrobisz tak:

Kopiuj
FILE *inFp = fopen(argv[1], "r");
for (int i = 0; i < linesCount; ++i)
{
  char *line = silvGetLine(inFp);
  free(line);
}

Tzn. wybacz, że tak dopytuję, ale obecność "..." wskazuje, że tam się po drodze wiele może dziać ;)

Dzieje się, są cztery wywołania funkcji, które pobierają line jako argument. Jeśli to pomoże, a nie zaciemni obraz, to proszę – cały program:

Kopiuj
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <math.h>

// There is the assumption that sizeof(char) == 1

int silvLinesCount(FILE *fp);
char *silvGetLine(FILE *fp);
char *silvConcat(char *s1, char *s2);
char *silvItos(int i);
char *silvSubstr(char *s, int from, int to);
bool silvStrEq(char *s1, char *s2);
int silvMin(int i1, int i2);

int main(int argc, char *argv[])
{
	printf("debug: 'main' is running...\n");

	if (argc < 3)
	{
		fprintf(stderr, "Error: main: there are less arguments than 2");
		return -1;
	}

	// The input file
	FILE *inFp = fopen(argv[1], "r");

	// size_t n = 0;

	// size_t readCharactersNumber;
	bool isContent = NULL;
	FILE *outFp = NULL;
	int fileNumber = 0;
	int linesCount = silvLinesCount(inFp);
	// printf("debug: \tlinesCount == %d\n", linesCount);

	// while ((readCharactersNumber = getline(&line, &n, inFp)) != -1)
	for (int i = 0; i < linesCount; ++i)
	{
		char *line = silvGetLine(inFp);
		char *filenameSuffix = NULL;
		char *outFilePath = NULL;

		char *expectedStart = "      <text xml";
		char *actualStart = silvSubstr(
			line,
			0,
			silvMin(strlen(line), strlen(expectedStart)));
		// printf("\tdebug: actualStart == %s\n", actualStart);

		char *expectedEnd = "</text>";
		char *actualEnd = strlen(line) > strlen(expectedEnd)
							  ? silvSubstr(
									line,
									strlen(line) - strlen(expectedEnd),
									strlen(line))
							  : line;
		// printf("\tdebug: actualEnd == %s\n", actualEnd);

		if (silvStrEq(actualStart, expectedStart))
		{
			isContent = true;
			++fileNumber;
			filenameSuffix = silvItos(fileNumber);
			outFilePath = silvConcat(argv[2], filenameSuffix);
			outFp = fopen(outFilePath, "a");
		}

		// if (isContent == true)
		// {
		// 	fputs(line, outFp);
		// }

		if (silvStrEq(actualEnd, expectedEnd))
		{
			isContent = false;
			fclose(outFp);
		}

		free(actualStart);
		free(actualEnd);
		free(line);
		free(filenameSuffix);
		free(outFilePath);

		// printf("\rLines read: %d, files created: %d...", i, fileNumber);
	}

	fclose(inFp);

	return 0;
}

int silvLinesCount(FILE *fp)
{
	printf("debug: 'silvLinesCount' is running...\n");

	if (fp == NULL)
	{
		fprintf(stderr, "Error: silvLinesCount: fp == NULL");
	}

	char c;
	int lineCount = 0;
	while ((c = fgetc(fp)) != EOF)
	{
		if (c == '\n')
		{
			++lineCount;
		}
	}
	fseek(fp, 0, SEEK_SET);

	return lineCount;
}

// Needs a corresponding call for "free" by the user
char *silvGetLine(FILE *fp)
{
	printf("debug: 'silvGetLine' is running...\n");

	if (fp == NULL)
	{
		fprintf(stderr, "Error: silvGetLine: fp == NULL");
	}

	char c;
	int lineSize = 0;
	while ((c = getc(fp)) != EOF && c != '\n')
	{
		++lineSize;
	}
	char *result = malloc(lineSize + 1); // +1 for NULL character
	fseek(fp, -lineSize, SEEK_CUR);
	fgets(result, lineSize + 1, fp); // +1 because fgets reads "count - 1" bytes

	return result;
}

// Needs a corresponding call for "free" by the user
char *silvConcat(char *s1, char *s2)
{
	printf("debug: 'silvConcat' is running...\n");

	if (s1 == NULL || s2 == NULL)
	{
		fprintf(stderr, "Error: silvConcat: either s1 == NULL or s2 == NULL");
	}

	char *result = malloc(strlen(s1) + strlen(s2) + 1); // +1 for NULL character
	memcpy(result, s1, strlen(s1));
	memcpy(result + strlen(s1), s2, strlen(s2));
	memcpy(result + strlen(s1) + strlen(s2), "\0", 1); // +1 for NULL character

	return result;
}

// Needs a corresponding call for "free" by the user
char *silvItos(int i)
{
	printf("debug: 'silvItos' is running...\n");

	if (i < 0)
	{
		fprintf(stderr, "Error: silvItos: i < 0");
	}

	int charactersNumber = (int)(floor(log10(i)) + 1); // +1 cause there's floor
	char *result = malloc(charactersNumber + 1);	   // +1 for NULL character
	sprintf(result, "%d", i);

	if (result == NULL)
	{
		fprintf(
			stderr,
			"Warning: silvItos: something went wrong, result == NULL");
	}

	return result;
}

// Needs a corresponding call for "free" by the user
char *silvSubstr(char *s, int from, int to)
{
	printf("debug: 'silvSubstr' is running...\n");

	if (s == NULL)
	{
		fprintf(stderr, "Error: silvSubstr: s == NULL");
	}

	if (from > to)
	{
		fprintf(stderr, "Error: silvSubstr: from > to");
	}

	int substrSize = to - from;
	char *result = malloc(substrSize + 1); // +1 for NULL character
	memcpy(result, s + from, substrSize);
	memcpy(result + substrSize, "\0", 1);

	return result;
}

bool silvStrEq(char *s1, char *s2)
{
	printf("debug: 'silvStrEq' is running...\n");

	if (s1 == NULL || s2 == NULL)
	{
		fprintf(stderr, "Error: silvStrEq: either s1 == NULL or s2 == NULL");
	}

	if (strcmp(s1, s2) == 0)
	{
		return true;
	}
	else
	{
		return false;
	}
}

int silvMin(int i1, int i2)
{
	printf("debug: 'silvMin' is running...\n");

	if (i1 <= i2)
	{
		return i1;
	}
	else
	{
		return i2;
	}
}

edytowany 1x, ostatnio: Silv
AL
  • Rejestracja:prawie 11 lat
  • Ostatnio:około 3 lata
  • Postów:1493
0

No… fgets zwraca to, co podane w API.

Chodziło mi o to, czy nie sygnalizuje błędu w postaci NULLa.

To mi śmierdzi

Kopiuj
        char *actualEnd = strlen(line) > strlen(expectedEnd)
                              ? silvSubstr(
                                    line,
                                    strlen(line) - strlen(expectedEnd),
                                    strlen(line))
                              : line; //szybki test: wsadź tu strdup(line)

/* ciach */
        free(actualEnd);
        free(line);

Generalnie polecałbym objechać debugerem te ostatni blok free i zobaczyć czy coś Ci się nie zazębia/powtarza.

EDIT:
a to je valgrind
https://valgrind.org/
Niestety Linux only (nie wiem jak z BSD). Na Macu Instruments z XCode'a działają też całkiem przyzwoicie.
Na Windzie nie wiem ;P

edytowany 4x, ostatnio: alagner
Silv
Moderator Wiki
  • Rejestracja:ponad 10 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Warszawa
0

@alagner: teraz tak patrzę… Czy dobrze widzę, że powinienem kopiować, a ja sobie radośnie przypisuję wskaźnik? A może to jest zgodnie ze sztuką (to char *actualEnd = … ? … : line)?


AL
  • Rejestracja:prawie 11 lat
  • Ostatnio:około 3 lata
  • Postów:1493
0

Jedno i drugie jest zgodne ze sztuką, pytanie jak to potem chcesz zwolnić. Ale radziłbym być konsekwentnym

edytowany 1x, ostatnio: alagner
Silv
Moderator Wiki
  • Rejestracja:ponad 10 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Warszawa
0

Hm, hm, hm… sam nie wiem. :/

Ale już chyba rozumiem… Pomijając to, że linijka 61 nie jest pusta, to skoro program myśli, że jest pusta, to przypisuje wskaźnik na nią zmienną przechowującą wskaźnik na nią do zmiennej actualEnd, a następnie chce zwolnić zarówno pamięć za pośrednictwem zmiennej actualEnd – która przechowuje ten sam wskaźnik, co zmienna line – jak i pamięć za pośrednictwem zmiennej line

Pytanie teraz, dlaczego linijka 61 jest pusta? Ale to chyba nie należy do tego wątku.

Jeszcze to spróbuję dziś w tym kierunku przetestować i napiszę, co mi wyszło.


edytowany 3x, ostatnio: Silv
AL
  • Rejestracja:prawie 11 lat
  • Ostatnio:około 3 lata
  • Postów:1493
0

Pytanie czy faktycznie trafiasz w ten warunek i robisz to przypisanie wskaźnika.
Ja za Ciebie tego nie sprawdzę. ;) Mogę co najwyżej powiedzieć, co wygląda podejrzanie.
Tak jak mówię, pierwsze co to sprawdziłbym wartości wszystkich zmiennych z bloku free.

BTW, jaka to platforma i czemu koniecznie C? Użycie std::stringa z C++ bardzo wiele by ułatwiło.

Silv
Moderator Wiki
  • Rejestracja:ponad 10 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Warszawa
0

Odpowiedź na BTW: system operacyjny to Fedora, jeśli to masz na myśli pytając o platformę. C++ nie, ponieważ C++ – tak, jak ja to widzę – wprowadza zarówno własne konstrukcje, jak i konstrukcje C. Żebym się w tym nie pogubił, wybrałem C, które ma tylko konstrukcje C.


PS. Pisząc "pogubił" mam na myśli zarówno sam język, jak i narzędzia do niego – kompilatory, linkery itp.


edytowany 2x, ostatnio: Silv
AL
  • Rejestracja:prawie 11 lat
  • Ostatnio:około 3 lata
  • Postów:1493
0

IMHO to błąd bo skazujesz się na ręczne zarządzanie pamięcią, ale Twój wybór.

Zobacz pozostałe 13 komentarzy
Silv
U mnie bardziej pamięć jest problemem niż czas. Nie, testów jednostkowych nie będę tutaj pisać. :P Choć, wiesz, w ogólności to jestem wielkim fanem koncepcji testowania na bieżąco (jeszcze nie wiem, czy TDD).
AL
@Silv pamięć, czy wąskie gardło w postaci IO? Bo tak Bogiem a prawdą, to szybciej by Ci mogło być lecieć buforując ten plik np. co 4KB. No ale musisz w sumie zmierzyć...
Silv
Ale przecież IO to kwestia czasu, nie pamięci? — Tutaj kod ma być logiczny i self-descriptive, niekoniecznie wydajny pod względem pamięci czy czasu.
Silv
A C, z tego co zauważyłem, jest o wiele wydajniejsze od Basha, i na razie to mi wystarcza. Masz rację: jak okaże się, że parsowanie całego pliku zajmuje godzinę, pomyślę np. o buforowaniu czy o czymś.
Silv
@alagner: zmierzyłem czas wykonania. Parsowanie pliku zajęło ok. 30 sekund – w tym tworzenie prawie 20 tysięcy plików wynikowych. (To było podstawowe parsowanie; zaawansowane chyba już w kolejnym skrypcie). To o wiele lepiej niż kilka godzin w Bashu (może być też, że na Bashu znam się jeszcze mniej niż na C, dlatego tak długo tamto szło).
tajny_agent
  • Rejestracja:ponad 11 lat
  • Ostatnio:ponad rok
  • Postów:1340
0

Odchodząc troszeczkę od głównego problemu.
Pomyśl nad funkcją wczytującą wiersz z pliku do prealokowanego bufora. W obecnej wersji, skoro plik ma ~6,5mln wierszy = 6,5mln alokacji. To nie będzie wydajne nawet w C ;)
Albo jeśli możesz to skorzystaj z gotowca: getline


"I love C++. It's the best language in the world right now for me to write the code that i need and want to write"
~ Herb Sutter
Zobacz pozostałe 8 komentarzy
AL
no napisałem to tak, że mogłoby to zostać odebrane agresywnie a nie było to moim celem.
Silv
Aha. Nie, spokojnie, nie odebrałem tego tak. Zwyczajne komentarze z Twojej strony. Trochę byłoby mi szkoda, gdybym nie zakładał, bo nie byłoby ciekawych dyskusji w komentarzach. ;)
Silv
A dziękuję, zapiszę sobie w zakładkach. :)
Silv
Moderator Wiki
  • Rejestracja:ponad 10 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Warszawa
0

Sprostowanie: mój błąd, przepraszam Was, w pliku wejściowym jest około 4,6 mln linii, a nie 6,5 mln, jak pisałem w komentarzach.


UPDATE: Ale w sumie to przypadek, mogłem wybrać i większy plik.


edytowany 1x, ostatnio: Silv
Silv
Moderator Wiki
  • Rejestracja:ponad 10 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Warszawa
0

Po zmianie z przypisywania wskaźnika na łańcuch znaków na kopiowanie łańcucha znaków (o której pisałem tutaj: https://4programmers.net/Forum/C_i_C++/334473-free_double_free_detected_in_tcache_2?p=1642968#id1642968), program leci już do końca pliku. Nie jest przerywany ani wspomnianym w tytule, ani żadnym innym błędem.

Co prawda są wciąż inne / doszły nowe problemy, ale one już nie dotyczą tego wątku.


PS. To znaczy, zmieniłem to:

Kopiuj
        char *actualEnd = strlen(line) > strlen(expectedEnd)
                              ? silvSubstr(
                                    line,
                                    strlen(line) - strlen(expectedEnd),
                                    strlen(line))
                              : line;

na to:

Kopiuj
		char *actualEnd = strlen(line) > strlen(expectedEnd)
							  ? silvSubstr(
									line,
									strlen(line) - strlen(expectedEnd),
									strlen(line))
							  : silvStrCpy(line);

gdzie silvStrCpy to nowo utworzona funkcja, która wygląda tak:

Kopiuj
// Needs a corresponding call for "free" by the user
char *silvStrCpy(const char *src)
{
	char *result = malloc(strlen(src) + 1); // +1 for NULL character
	strcpy(result, src);

	return result;
}

UPDATE: Głupi ja, taka funkcja jest już w standardzie, jak @alagner był uprzejmy mi powiedzieć w komentarzach: strdup. Co prawda należy zauważyć, że, jak piszą na stronie en.cppreference.com:

Merged into ISO C The functionality described on this page was merged into the mainline ISO C standard as of 6/2019, see strdup (since C2x)


edytowany 11x, ostatnio: Silv
Zobacz pozostałe 13 komentarzy
AL
Default to chyba -std=gnu89. To trochę zmienia: https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html
Silv
Nie, obecnie tak piszą: The default, if no C language dialect options are given, is -std=gnu11. — źródło: https://gcc.gnu.org/onlinedocs/gcc/Standards.html
Silv
Ale teraz to jestem skonfudowany: czemu tutaj https://gcc.gnu.org/ piszą, że wersja GCC 7.5 jest wydana po wersji 9.2?
AL
bo to różne wersje, które żyją równocześnie https://www.gnu.org/software/gcc/develop.html
Silv
Ach, to brzmi rozsądnie. Dzięki.
DA
  • Rejestracja:około 6 lat
  • Ostatnio:około 13 godzin
  • Postów:142
0

Ta funkcja wczytująca wiersze tekstu to Ci produkuje dobre łańcuchy znaków? Bo nie chce mi się dziś testować kompilatorem, ale wygląda, że zliczasz znaki do osiągnięcia \n, ale zatrzymujesz się po przeczytaniu \n przez co seek w tył robisz o jeden znak za mało. Zanim jednak zaczniesz poprawiać rozważ problem z Windows, gdzie koniec wiersza ma fizycznie znaki \r\n. Lepiej byłoby przed pętlą zapamiętać pozycję long bookmark = ftell( fp );, a po pętli odtworzyć fseek( fp, bookmark, SEEK_SET );.
Inny sposób na czytanie wiersza o nieznanej długości, to wczytywanie kolejnych kawałków do tymczasowej tablicy, po zapełnieniu której zgromadzone znaki są dołączane do budowanego łańcucha wyjściowego.

edytowany 1x, ostatnio: -daniel-
Silv
Którą funkcję masz na myśli? silvGetLine czy silvLinesCount? Przejrzę wyniki i Ci powiem. — Co do Windows, hm, ten skrypt ma być raczej ilustracją podejścia, a nie być "ponownie używalny"; przeznaczony jest do jednorazowego użycia (albo wielorazowego w wypadku, gdybym usuwał pliki wynikowe (z jakiegoś powodu)). — Ten drugi sposób zakłada, że jakaś część przydzielonej pamięci może zostać niewykorzystana, prawda?
Silv
Przejrzałem wyniki i nie widzę, żeby gdzieś ucinało początek.
Silv
Jeśli chodzi Ci o funkcję silvGetLine, to powiem Ci, że właśnie jeśli chodzi o fragment seek, miałem wielki ból głowy, zanim doszedłem do tego, co jest teraz. Nie wiedziałem, dlaczego pierwsza linijka jest poprawnie wczytywana (od pierwszego do ostatniego znaku drukowalnego, czyli bez \n), a już kolejne linijki są puste. Miało to związek z tym, że program "nie przechodził przez \n". Nie wiem dokładnie, dlaczego nie przechodził, ale kod fseek(fp, 1, SEEK_CUR); naprawił sprawę.Nadal chciałbym to zrozumieć, ale przy tak prostym skrypcie nie ma sensu tracić czasu.
DA
Chodzi o silvGetLine. Załóżmy, że mamy wiersz z literą W, czyli "W\n". Ustawiasz lineSize na 0, getc czyta W, warunek spełniony, więc lineSize zwiększasz na 1. Następnie getc czyta \n i ustawia pozycję w pliku za \n. Warunek niespełniony, wychodzisz z pętli z lineSize równym 1. fseek przesuwa pozycję w pliku o -1, czyli pomiędzy W i \n, w konsekwencji fgets czyta pusty wiersz pomijając W. Piszę to w oparciu o postać funkcji z trzeciego i siódmego posta.
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)