Dlaczego nie połączyć plików źródłowych C przed kompilacją? [duplikat]

To pytanie ma już odpowiedź tutaj:

Pochodzę z kontekstu skryptowego a preprocesor w C zawsze wydawał mi się brzydki. Mimo to przyjęłam go, gdy uczę się pisać małe programy w języku C. Tak naprawdę używam preprocesora tylko do włączania standardowych bibliotek i plików nagłówkowych, które napisałem dla własnych funkcji.

Moje pytanie brzmi: dlaczego programiści C po prostu nie pominą wszystkich includów i po prostu nie połączą swoich plików źródłowych C, a następnie nie skompilują ich? Jeśli umieścisz wszystkie swoje elementy w jednym miejscu, musisz tylko raz zdefiniować, czego potrzebujesz, zamiast we wszystkich plikach źródłowych.

Oto przykład tego, co opisuję. Tutaj mam trzy pliki:
// includes.c
#include <stdio.h>
// main.c
int main() {
    foo();
    printf("world\n");
    return 0;
}
// foo.c
void foo() {
    printf("Hello ");
}

Robiąc coś takiego jak cat *.c > to_compile.c && gcc -o myprogram to_compile.c w moim Makefile mogę zmniejszyć ilość kodu, który piszę.

Oznacza to, że nie muszę pisać pliku nagłówkowego dla każdej tworzonej funkcji (ponieważ są one już w głównym pliku źródłowym), a także oznacza, że nie muszę dołączać standardowych bibliotek do każdego tworzonego pliku. To wydaje się być świetnym pomysłem na ja!

Zdaję sobie jednak sprawę, że C jest bardzo dojrzałym językiem programowania i wyobrażam sobie, że ktoś inny o wiele mądrzejszy ode mnie miał już ten pomysł i postanowił go nie używać. Dlaczego nie?

Author: Aaron Hall, 2017-02-09

10 answers

Niektóre programy są budowane w ten sposób.

Typowym przykładem jest SQLite . Czasami jest kompilowany jako amalgamacja (wykonywana w czasie kompilacji z wielu plików źródłowych).

Ale to podejście ma swoje plusy i minusy.

Oczywiście czas kompilacji znacznie się wydłuży. Więc jest to praktyczne tylko wtedy, gdy kompilujesz te rzeczy rzadko.

Być może kompilator mógłby nieco bardziej zoptymalizować. Ale z optymalizacją czasu łącza (np. Jeśli za pomocą Ostatnie GCC, kompilowanie i linkowanie z gcc -flto -O2) można uzyskać ten sam efekt (oczywiście kosztem wydłużonego czasu kompilacji).

Nie muszę pisać pliku nagłówkowego dla każdej funkcji

To jest złe podejście (posiadanie jednego pliku nagłówkowego na funkcję). W przypadku projektu jednoosobowego (mniej niż sto tysięcy linii kodu, a.K.a. KLOC = kilowa linia } kodu ), jest całkiem rozsądne - przynajmniej dla małych projektów - mieć pojedynczy wspólny plik nagłówkowy (który możesz wstępnie skompilować , jeśli użyjesz GCC ), który będzie zawierał deklaracje wszystkich publicznych funkcji i typów, a być możedefinicje Z static inline funkcji (tych wystarczająco małych i wywoływanych wystarczająco często, aby czerpać zyski zinlining ). Na przykładsash shell jest zorganizowany w ten sposób (i tak jest lout formatera , z 52 KLOC).

Możesz też mieć kilka plików nagłówkowych i być może mieć jakiś pojedynczy nagłówek" grouping", który #include - s wszystkie z nich (i który można wstępnie skompilować). Zobacz na przykład jansson (który faktycznie ma jeden publiczny plik nagłówkowy) i GTK (który ma wiele wewnętrznych nagłówków, ale większość aplikacji używających go ma tylko jeden#include <gtk/gtk.h>, który z kolei zawiera wszystkie wewnętrzne nagłówki). Po przeciwnej stronie, POSIX ma dużą ilość plików nagłówkowych i dokumentuje, które z nich powinny być zawarte i w jakiej kolejności.

Niektórzy wolą mieć dużo plików nagłówkowych(a niektórzy nawet wolą umieszczać pojedynczą deklarację funkcji we własnym nagłówku). Nie wiem (w przypadku osobistych projektów, lub małych projektów, w których tylko dwie lub trzy osoby popełniłyby kod), ale {88]}jest to kwestia gustu {48]}. BTW, gdy projekt rośnie dużo, zdarza się dość często, że zestaw plików nagłówkowych (i jednostek tłumaczeniowych) zmienia się znacząco. Zajrzyj także do REDIS (posiada 139 .h plików nagłówkowych i 214 .c plików tj. jednostki tłumaczeniowe łącznie 126 KLOC).

Posiadanie jednej lub kilku jednostek tłumaczeniowych jest również kwestią gustu (oraz wygody, nawyków i konwencji). Preferuję pliki źródłowe (czyli jednostki tłumaczeniowe), które nie są zbyt małe, zazwyczaj po kilka tysięcy linii i często mają (dla małego projektu poniżej 60 KLOC) wspólny pojedynczy plik nagłówkowy. Nie zapomnij użyć jakiegoś build automatyzacja narzędzie takie jak GNU make (często z równoległym budowanym przez make -j; wtedy będziesz mieć kilka procesów kompilacyjnych działających jednocześnie). Zaletą posiadania takiej organizacji plików źródłowych jest to, że kompilacja jest dość szybka. BTW, w niektórych przypadkach podejście metaprogramowanie jest warte zachodu: niektóre z twoich (wewnętrznych nagłówków lub jednostek translacyjnych) C "źródłowych" plików mogą być wygenerowane przez coś innego (np. jakiś skrypt w AWK , jakiś wyspecjalizowany program C jak bison lub Twoja własna rzecz).

[35]} pamiętaj, że C został zaprojektowany w latach 70., dla komputerów znacznie mniejszych i wolniejszych niż twój ulubiony laptop dzisiaj (zazwyczaj pamięć była w tym czasie co najwyżej megabajt, a nawet kilkaset kilobajtów, a komputer był co najmniej tysiąc razy wolniejszy niż twój telefon komórkowy dzisiaj).

Zdecydowanie sugeruję, aby przestudiować kod źródłowy i zbudować jakieś istniejące projekty wolnego oprogramowania (np. projekty na GitHub lub SourceForge lub Twoja ulubiona dystrybucja Linuksa). Dowiesz się, że są to różne podejścia. Pamiętaj, że w C konwencje i nawyki mają duże znaczenie w praktyce , więc istnieją} różne sposoby organizacji projektu w plikach .c i .h {91]}. Przeczytaj o preprocesorze C .

Oznacza to również, że nie muszę Dołącz standardowe biblioteki do każdego pliku, który tworzę

Dołączasz pliki nagłówkowe, nie biblioteki (ale powinieneś link biblioteki). Ale możesz umieścić je w każdym pliku .c (i wiele projektów to robi), lub możesz umieścić je w jednym nagłówku i wstępnie skompilować ten nagłówek, lub możesz mieć tuzin nagłówków i dołączać je po nagłówkach systemowych w każdej jednostce kompilacji. YMMV. zauważ, że czas przetwarzania wstępnego jest szybki NA dzisiejsze komputery (przynajmniej, gdy poprosisz kompilator o optymalizację, ponieważ optymalizacja zajmuje więcej czasu niż parsowanie i wstępne przetwarzanie).

Zauważ, że to, co wchodzi do jakiegoś pliku #include - d jest konwencjonalnym (i nie jest zdefiniowane przez specyfikację C). Niektóre programy mają część swojego kodu w takim pliku(który nie powinien być nazywany "nagłówkiem", tylko "dołączonym plikiem"; i który nie powinien mieć przyrostka .h, ale coś innego jak .inc). Poszukaj np. w XPM pliki. Z drugiej strony, możesz w zasadzie nie mieć żadnych własnych plików nagłówkowych (nadal potrzebujesz plików nagłówkowych z implementacji, takich jak <stdio.h> lub <dlfcn.h> z twojego systemu POSIX) oraz kopiować i wklejać zduplikowany kod w swoich plikach .c- np. mieć linię int foo(void); w każdym pliku .c, ale jest to bardzo zła praktyka i jest źle widziana. Jednak niektóre programy generują pliki C dzielące się wspólną treścią.

BTW, C lub C++14 nie mają modułów (jak OCaml ma). Innymi słowy, w C moduł jest w większości konwencją .

(zauważ, że mając wiele tysięcy bardzo małych .h i .c pliki składające się tylko z kilkudziesięciu linii mogą znacznie spowolnić twój czas kompilacji; posiadanie setek plików po kilkaset linii jest bardziej rozsądne, jeśli chodzi o czas kompilacji.)

Jeśli zaczniesz pracować nad jednoosobowym projektem w C, sugerowałbym najpierw mieć jeden plik nagłówkowy (i wstępnie skompilować it) oraz kilka .c jednostek tłumaczeniowych. W praktyce pliki .c będą zmieniane znacznie częściej niż .h. Gdy masz więcej niż 10 KLOC, możesz przekształcić go w kilka plików nagłówkowych. Taka refaktoryzacja jest trudna w projektowaniu, ale łatwa do zrobienia (po prostu dużo kopiowania i wklejania fragmentów kodów). Inni ludzie będą mieli różne sugestie i wskazówki (i to jest ok!). Ale nie zapomnij włączyć wszystkich ostrzeżeń i informacji debugowania podczas kompilacji (więc skompilować z gcc -Wall -g, być może ustawienie CFLAGS= -Wall -g W Twoje Makefile). Użyj debugera gdb (oraz valgrind...). Poproś o optymalizacje (-O2), gdy porównujesz już debugowany program. Użyj również systemu kontroli wersji, takiego jak Git .

Wręcz przeciwnie, jeśli projektujesz większy projekt, w którym pracowałoby kilka osób , lepiej byłoby mieć kilka plików-nawet kilka plików nagłówkowych - (intuicyjnie, każdy plik ma jedną osobę głównie odpowiedzialną za niego, a inni robią drobne błędy). wkład do tego pliku).

W komentarzu dodajesz:

Mówię o pisaniu mojego kodu w wielu różnych plikach, ale używając pliku Makefile do ich połączenia

Nie widzę powodu, dla którego byłoby to przydatne (z wyjątkiem bardzo dziwnych przypadków). O wiele lepiej (i bardzo powszechną i powszechną praktyką) skompilować każdą jednostkę tłumaczenia (np. każdy plik .c) do swojego pliku obiektowego (a .o ELF plik na Linuksie) i link później. Jest to łatwe z make (w praktyce, gdy zmienisz tylko jeden plik .c, np. aby naprawić błąd, tylko ten plik zostanie skompilowany, a Przyrostowy kompilator jest naprawdę szybki), i możesz poprosić go o skompilowanie plików obiektowych w parallel za pomocą make -j (a następnie twoja kompilacja idzie naprawdę szybko na wielordzeniowym procesorze).

 103
Author: Basile Starynkevitch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:15

Ty mógłbyś to zrobić, ale lubimy rozdzielać programy C na oddzielnejednostki tłumaczeniowe, głównie dlatego, że:

  1. Przyspiesza budowanie. Wystarczy tylko odbudować pliki, które się zmieniły, a te mogą być połączone z innymi skompilowanymi plikami, aby utworzyć ostateczny program.

  2. Biblioteka standardowa C składa się z wstępnie skompilowanych komponentów. Naprawdę chcesz to przekompilować?

  3. Łatwiej współpracować z innymi programistami, jeśli baza kodu jest podzielona na różne pliki.

 26
Author: Bathsheba,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-10 14:13:11
  • dzięki modułowości możesz udostępniać swoją bibliotekę bez udostępniania kodu.
  • w przypadku dużych projektów, jeśli zmienisz pojedynczy plik, skończy się to kompilowanie całego projektu.
  • może zabraknąć pamięci łatwiej, gdy próbujesz skompilować duże projekty.
  • możesz mieć kołowe zależności w modułach, modułowość pomaga w ich utrzymaniu.

Mogą być pewne korzyści w twoim podejściu, ale dla języków takich jak C, kompilowanie każdego modułu sprawia, że więcej sens.

 16
Author: Mohit Jain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-09 11:38:12

Twoje podejście do konkatenacji .pliki c są całkowicie zepsute:

  • Mimo że polecenie cat *.c > to_compile.c umieści wszystkie funkcje w jednym pliku, kolejność ma znaczenie: każda funkcja musi być zadeklarowana przed jej pierwszym użyciem.

    Oznacza to, że masz zależności między swoimi .pliki c, które wymuszają określony porządek. Jeśli Twoje polecenie konkatenacji nie dotrzyma tego rozkazu, nie będziesz w stanie skompilować wyniku.

    Również, jeśli masz dwie funkcje, które rekurencyjnie używać siebie, nie ma absolutnie żadnego sposobu na napisanie deklaracji forward dla co najmniej jednej z dwóch. Równie dobrze możesz umieścić te deklaracje w pliku nagłówkowym, w którym ludzie oczekują ich znalezienia.

  • Kiedy połączysz wszystko w jeden plik, wymusisz pełną przebudowę za każdym razem, gdy zmieni się pojedyncza linia w projekcie.

    Z klasykiem .c/.H split compilation approach, zmiana implementacji funkcji wymaga rekompilacja dokładnie jednego pliku, podczas gdy zmiana nagłówka wymaga rekompilacji plików, które faktycznie zawierają ten nagłówek. Może to z łatwością przyspieszyć odbudowę po niewielkiej zmianie o współczynnik 100 lub więcej (w zależności od liczby.pliki c).
  • Tracisz wszystkie możliwości kompilacji równoległej , gdy połączysz wszystko w jeden plik.

    Czy duży, 12-rdzeniowy procesor z włączoną hyper-threading? Szkoda, Twój konkatenowany plik źródłowy jest kompilowany przez pojedynczy wątek. Właśnie straciłeś przyspieszenie o czynnik większy niż 20... Ok, to jest ekstremalny przykład, ale mam już oprogramowanie do budowania z make -j16 i mówię ci, może to zrobić ogromną różnicę.

  • Czasy kompilacji są zazwyczaj , a nie liniowe.

    Zwykle Kompilatory zawierają co najmniej pewne algorytmy, które mają kwadratowe zachowanie środowiska wykonawczego. W związku z tym zwykle istnieje pewien próg, od którego na zbiorczej kompilacji jest w rzeczywistości wolniejsza od kompilacji niezależnych części.

    Oczywiście dokładna lokalizacja tego progu zależy od kompilatora i FLAG optymalizacji, które mu przekazujesz, ale widziałem, że kompilator zajmuje ponad pół godziny na jednym ogromnym pliku źródłowym. Nie chcesz mieć takiej przeszkody w pętli change-compile-test.

Nie popełnij błędu: nawet jeśli chodzi o te wszystkie problemy, są ludzie, którzy używają .konkatenacja pliku c w praktyce oraz niektórzy programiści C++ dostają się prawie do tego samego punktu, przenosząc wszystko do szablonów (tak, że implementacja znajduje się w .plik hpp i nie ma powiązanych .plik cpp), pozwalając preprocesorowi wykonać konkatenację. Nie rozumiem, jak mogą ignorować te problemy, ale tak jest.

Należy również zauważyć, że wiele z tych problemów stają się widoczne tylko przy większych rozmiarach projektu. Jeśli twój projekt ma mniej niż 5000 linii kodu, to nadal nie ma znaczenia, w jaki sposób go skompilujesz. Ale jeśli masz więcej niż 50000 linii kodu, zdecydowanie potrzebujesz Systemu Budowania, który obsługuje Kompilacje przyrostowe i równoległe. W przeciwnym razie marnujesz czas pracy.

 16
Author: cmaster,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-10 11:32:23

Ponieważ dzielenie rzeczy jest dobrym projektem programu. Dobry projekt programu polega na modułowości, autonomicznych modułach kodu i ponownej użyteczności kodu. Jak się okazuje, zdrowy rozsądek zaprowadzi cię bardzo daleko podczas projektowania programu: rzeczy, które nie należą do siebie, nie powinny być umieszczone razem.

Umieszczenie niezwiązanego kodu w różnych jednostkach tłumaczeniowych oznacza, że można zlokalizować zakres zmiennych i funkcji w jak największym stopniu.

Łączenie rzeczy razem tworzy tight coupling , czyli niezręczne zależności między plikami kodu, które naprawdę nie powinny nawet wiedzieć o ich istnieniu. Dlatego " globalny.h", który zawiera wszystkie elementy w projekcie, jest złą rzeczą, ponieważ tworzy ścisłe powiązanie między każdym niezwiązanym plikiem w całym projekcie.

Załóżmy, że piszesz firmware do sterowania samochodem. Jeden moduł w programie steruje samochodowym radiem FM. Następnie ponownie użyj kodu radiowego w innym projekcie, aby kontrolować radio FM w smartfonie. A potem twój kod radiowy się nie skompiluje, ponieważ nie może znaleźć hamulców, kół, biegów itp. Rzeczy, które nie mają najmniejszego sensu dla radia FM, nie mówiąc już o inteligentnym telefonie.

Co gorsza, jeśli masz ciasne połączenie, błędy nasilają się w całym programie, zamiast pozostawać lokalnie w module, w którym znajduje się błąd. To sprawia, że konsekwencje błędów są znacznie poważniejsze. Wpisujesz błąd w kodzie radia FM, a następnie nagle hamulce samochodu przestają działać. Nawet jeśli nie dotknąłeś kodu hamulca z aktualizacją, która zawierała błąd.

Jeśli błąd w jednym module łamie całkowicie niezwiązane z nim rzeczy, to prawie na pewno jest to spowodowane słabą konstrukcją programu. A pewnym sposobem na osiągnięcie słabego projektu programu jest połączenie wszystkiego w projekcie w jedną wielką plamę.

 15
Author: Lundin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-11 08:39:32

Pliki nagłówkowe powinny definiować interfejsy - jest to pożądana konwencja do naśladowania. Nie są one przeznaczone do deklarowania wszystkiego, co znajduje się w odpowiednim pliku .c lub grupie plików .c. Zamiast tego deklarują wszystkie funkcje w plikach .c, które są dostępne dla ich użytkowników. Dobrze zaprojektowany plik .h zawiera podstawowy dokument interfejsu wystawiony przez KOD w pliku .c, nawet jeśli nie ma w nim ani jednego komentarza. Jednym ze sposobów podejścia do projektowania modułu C jest napisanie najpierw plik nagłówkowy, a następnie zaimplementuj go w jednym lub więcej plikach .c.

Następstwo: funkcje i struktury danych wewnętrzne implementacji pliku .c zwykle nie należą do pliku nagłówkowego. Możesz potrzebować deklaracji forward, ale powinny one być lokalne, a wszystkie zmienne i funkcje zadeklarowane i zdefiniowane powinny być static: jeśli nie są częścią interfejsu, linker nie powinien ich widzieć.

 11
Author: Kuba Ober,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-10 15:04:58

Głównym powodem jest czas kompilacji. Skompilowanie jednego małego pliku po jego zmianie może zająć niewiele czasu. Jeśli jednak skompilujesz cały projekt za każdym razem, gdy zmienisz jedną linię, skompilujesz - na przykład-10 000 plików za każdym razem, co może trwać znacznie dłużej.

Jeśli masz - jak w powyższym przykładzie-10 000 plików źródłowych, a kompilowanie jednego zajmuje 10 ms, to cały projekt buduje się stopniowo (po zmianie pojedynczego pliku) albo w (10 ms + linkowanie czas), jeśli skompilujesz tylko ten zmieniony plik, lub (10 ms * 10000 + krótki czas łączenia), jeśli skompilujesz wszystko jako pojedynczy, skonkatenowany obiekt blob.

 8
Author: Freddie Chopin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-11 07:35:34

Podczas gdy możesz napisać swój program w sposób modułowy i zbudować go jako pojedynczą jednostkę tłumaczeń, ominiesz wszystkie mechanizmy, które C dostarcza, aby wymusić tę modułowość . Z wieloma jednostkami tłumaczeniowymi masz doskonałą kontrolę nad interfejsami modułów za pomocą np. extern i static słów kluczowych.

Łącząc swój kod w jedną jednostkę tłumaczeniową, ominiesz wszelkie problemy z modułowością, ponieważ kompilator nie będzie Cię o nich ostrzegał. W dużym projekcie to w końcu spowoduje niezamierzone zależności rozprzestrzeniające się wokół. W końcu będziesz miał problem ze zmianą dowolnego modułu bez tworzenia globalnych efektów ubocznych w innych modułach.

 7
Author: Dmitry Grigoryev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-09 13:07:00

Jeśli umieścisz wszystkie pliki w jednym miejscu, będziesz musiał zdefiniować to, czego potrzebujesz tylko raz, a nie we wszystkich plikach źródłowych.

To jest cel .h plików, więc można zdefiniować to, czego potrzebujesz raz i umieścić go wszędzie. Niektóre projekty mają nawet nagłówek everything.h, który zawiera każdy pojedynczy plik .h. Tak więc twój pro {[10] } może być również osiągnięty za pomocą oddzielnych plików .c.

Oznacza to, że nie muszę pisać pliku nagłówkowego dla każda funkcja którą tworzę [...]

I tak nie powinieneś pisać jednego pliku nagłówkowego dla każdej funkcji. Powinieneś mieć jeden plik nagłówkowy dla zestawu powiązanych funkcji. Więc twój con {[10] } też nie jest ważny.

 4
Author: DepressedDaniel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-11 08:41:20

Oznacza to, że nie muszę pisać pliku nagłówkowego dla każdej tworzonej funkcji (ponieważ są one już w głównym pliku źródłowym), a także oznacza, że nie muszę dołączać standardowych bibliotek do każdego tworzonego pliku. Dla mnie to świetny pomysł!

Plusy, które zauważyłeś, są w rzeczywistości powodem, dla którego czasami robi się to w mniejszej skali.

W przypadku dużych programów jest to niepraktyczne. Podobnie jak inne dobre odpowiedzi wymienione, może to wydłużyć czas budowania zasadniczo.

Może być jednak użyty do rozbicia jednostki tłumaczeniowej na mniejsze bity, które współdzielą dostęp do funkcji w sposób przypominający dostępność pakietu Javy.

Sposób osiągnięcia powyższego wymaga pewnej dyscypliny i pomocy preprocesora.

Na przykład, możesz podzielić jednostkę tłumaczenia na dwa pliki:

// a.c

static void utility() {
}

static void a_func() {
  utility();
}

// b.c

static void b_func() {
  utility();
}

Teraz dodajesz plik do swojej jednostki tłumaczeniowej:

// ab.c

static void utility();

#include "a.c"
#include "b.c"

A Twój system budowania też nie buduje a.c lub b.c, ale zamiast tego buduje tylko ab.o z ab.c.

Co ab.c osiąga?

Zawiera oba pliki do generowania pojedynczej jednostki tłumaczenia i zapewnia prototyp dla narzędzia. Aby Kod zarówno a.c, jak i b.c mógł go zobaczyć, niezależnie od kolejności, w jakiej są zawarte, i bez wymogu, aby funkcja była extern.

 2
Author: StoryTeller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-09 11:57:07