Czy warto przejść z C na język D?

Czy warto przejść z C na język D?

Wątek przeniesiony 2023-12-21 12:49 z C/C++ przez Althorion.

DR
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1135
1

@zkubinski Jeśli zrobią tak jak mówisz, to moim zdaniem c++ pewnie "umrze". W tym momencie jedyna dobra rzecz w c++ to właśnie c. Dzięki tym UB, ten język jest tak szybki, a jak to straci, to po co pisać w języku z ułomną składnia, który jest jeszcze wolny, jeśli na rynku jest masa innych z cukrem składniowym. Lepszą dystrybucja paczek/bibliotek czy frameworkow?

ZK
  • Rejestracja: dni
  • Ostatnio: dni
0
Dregorio napisał(a):

@zkubinski Jeśli zrobią tak jak mówisz, to moim zdaniem c++ pewnie "umrze". W tym momencie jedyna dobra rzecz w c++ to właśnie c. Dzięki tym UB, ten język jest tak szybki, a jak to straci, to po co pisać w języku z ułomną składnia, który jest jeszcze wolny, jeśli na rynku jest masa innych z cukrem składniowym. Lepszą dystrybucja paczek/bibliotek czy frameworkow?

  1. czemu miałby "umrzeć"?
  2. dlaczego uważasz, że jak C++ będzie tym "lepszym" językiem bez UB, to będzie z ułomną składnią? To jak w takim razie nazwać inne języki np rust, które pozbawione są tej wady związanej z UB?
  3. skąd wiesz, że inne języki będą "niewolne"?

Podsumowując, skoro tak, to czemu wszyscy na C/C++ psioczą? Nie mogą żyć tak jak jest i akceptować wady tegoż języka?

KR
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 2964
1

Dzięki tym UB, ten język jest tak szybki,

No jednak nie, bo safe Rust nie ma UB i jest w 99% tak samo szybki. Wykorzystanie UB do optymalizacji to nisza. A ten pozostały 1% przypadków ogarniesz przez unsafe.

AN
  • Rejestracja: dni
  • Ostatnio: dni
0

Oczywiście, że Rust ma pewne innowacje, na przykład odmienny sposób zarządzania pamięcią bez GC, ale też kompilator pilnuje żeby nie było wycieków, nieprawidłowego dostępu i innych błędów, które bez trudu można zrobić w C i C++. Z drugiej strony, moim zdaniem rezygnacja ze zwykłych wskaźników na rzecz wskaźników inteligentnych praktycznie eliminuje takie problemy. Z pamięcią w C++ bardzo łatwo o błąd trudny do znalezienia, ale nie jest to jedyny problem.

Ciekawe, czy w Rust lub w D, czy innym języku jest też taki preprocesor, jaki jest w kompilatorach C. Moim zdaniem, preprocesor nie jest elementem żadnego języka, jest to tylko prymitywny przetwarzacz tekstu, który teoretycznie da się zastosować do każdego języka, a być może nawet do zwykłych plików tekstowych. To są podstawowe rzeczy, jakimi są "tu wstaw treść pliku X" (#include "X"), "znajdź i zamień" (#define PI 3.14159), "Jeżeli X, to uwzględnij poniższe linie do odwołania, w przeciwnym wypadku pomiń te linie" (#ifdef X ... #endif).

Przykład pierwszy z brzegu: https://invisible-island.net/vttest/ Funkcjonalnie to jest bardzo prosty program, pobiera znaki ze standardowego wejścia, wypuszcza na wyjście odpowiednio przygotowane teksty, tak naprawdę nic więcej nie robi. Ewentualnie odmierza czas 60 sekund bezczynności do wyzwolenia alarmu za pomocą alarm() w systemie, w którym jest to możliwe, wysyła sygnały signal() i parę innych drobniejszych rzeczy, które zależą od tego, co udostępnia API systemu.

Jak widać, dyrektywy są tu bardzo intensywnie wykorzystane. Cel tego jest tylko jeden. Włączyć i wyłączyć różne kawałki kodu w zależności od tego, po jakim systemem się kompiluje. Jest to włączenie i wyłączenie w sensie dosłownym, tak, jakby napisać lub skasować dany fragment, albo też podstawić określony tekst w każde oznaczone miejsce, w tym celu wymyślono #define, #ifdef, #endif.

Inny przykład: https://pl.wikibooks.org/wiki/Flex_i_Bison Jakiś czas temu próbowałem coś tam podziałać z parsowaniem w stylu lex+yacc i wytworzony kod też jest pełny dyrektyw, ale za to do użycia w różnych warunkach i różnych projektach.

Oczywiście, jak ze wszystkim, że jak się nierozsądnie korzysta z dyrektyw, to będzie więcej szkody niż pożytku, ale chyba nie ma kompilatora języka innego niż C lub C++ z takimi możliwościami wstępnego przetwarzania. Jak widać, jest to przydatne, bo dokładnie te same pliki kompilują się pod Linux, Unix i może gdzieś indziej, wystarczy tylko "pokazać" fragmenty na zasadzie np. "Jeżeli to jest Linux i jest dostepne termios, to napisz ten kod, a jak nie ma to usuń ten kod". Tak samo przykłady #define SUMA(a, b) a + b i #define ILOCZYN(a, b) a * b (https://pl.wikibooks.org/wiki/C/Preprocesor) mogą powodować błędy, ale to nie dlatego, że ten przetwarzacz sam w sobie jest zły, tylko z powodu niezrozumienia jego zasady działania, że podstawienie jest robione "w ciemno" bez jakiejkolwiek analizy semantycznej. Jak się pisze pod jeden system i nie przewiduje się wykorzystania tych samych plików do współpracy z różnymi API, to i tak się tego nie używa. To też nawet ułatwia zrobienie samego kompilatora, bo ten przetwarzacz robi jeden duży plik z kodem gotowym do kompilacji, z wykonanymi już podstawieniami i z potrzebnymi fragmentami dla danej kompilacji.

Ciekawe, dlaczego wstępne przetwarzanie tekstu jest tak, jakby zapomniane?, Dzięki temu nie potrzeba tworzyć kilku wersji źródeł różniących się jakimiś niuansami, tylko dlatego, że np. do kompilacji pod Windows i pod Linux kod musi się nieco różnic ze względu na różnice w API? Oczywiście, że takie rzeczy załatwia np. biblioteka Qt, ale chyba nie warto jej angażować w celu uwzględnienia kilku pojedynczych różnic.

Pewnie w 95% projektów nie używa się dyrektyw w takiej intensywności, a w 80% to nie używa się ich wcale oprócz #include, ale nie widzę powodu, żeby od nich zupełnie odejść.

DR
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1135
1

@zkubinski
1.Napisałem dlaczego moim zdaniem miałby umrzeć.
2. Bo rust już jest, a c++ bęc podwalin z c musiałby być przeprojektowany od nowa.
3. Bo ich używam.
@Krolik tak samo szybki jak? Wykorzystanie UB w twojej bańce to nisza, ja widzę to ciągle

AN
  • Rejestracja: dni
  • Ostatnio: dni
1

Odpowiadając na tytułowe pytanie, czy warto "wejść" w język "D", moim zdaniem NIE z tego powodu, że rzadko się o nim słyszy, praktycznie nie jest on używany na szerszą skalę. Jak na własne potrzeby to tak, ale należy mieć świadomość, że jest to język mało znany, niepopularny, choć przeszkód nie ma.

To już prędzej lepiej wejść Rust. Dlaczego Rust, argumenty zostały podane w tym temacie, można się zgodzić, nie zgodzić, dyskutować.

Wykorzystanie UB w twojej bańce to nisza, ja widzę to ciągle

Jak to? UB, jak sama nazwa wskazuje, można tłumaczyć jako "nie wiadomo, co się zdarzy, nie wiadomo, jaką wartość zwróci". Wykorzystanie UB to jest moim zdaniem proszenie się o kłopoty. Kiedyś znałem jeden przypadek, gdzie kod w C++ z czystym WinAPI był owocem lat pracy, kompilował się w Visual Studio na 32-bity, program działał. Potem programista chciał przejść na nowszą wersję VS i 64-bity, program skompilował się, ale nie działał. Przyczyną było to, że jakaś funkcja w starym kompilatorze zwracała wskaźnik do czegoś i ten wskaźnik gdzieś wykorzystywał, a w nowszej wersji zwraca zero. Ja się zapytałem, czy zwracaja wartośc jest opisana w dokumentacji C++ lub WinAPI, on nie potrafił na to pytanie odpowiedzieć. Nie pamiętam teraz, ale podał nazwę funkcji. Ja sam w domu sprawdziłem i w dokumentacji nie było informacji, co zwraca ta funkcja przy wykorzystywaniu funkcji na sposób opisany przez programistę, czyli moje przypuszczenia były słuszne. W końcu, ten programista przerobił program, żeby działał na nowym kompilatorze. To nie była robota na jedną godzinę, czy nawet jeden dzień. Potrzebny jest taki problem, który nie wiadomo, kiedy wyjdzie na jaw? Wystarczyło korzystać tylko z udokumentowanych cech API i nie byłoby żadnego problemu z działaniem po samej zmianie kompilatora.

AN
  • Rejestracja: dni
  • Ostatnio: dni
0

Podsumowując, skoro tak, to czemu wszyscy na C/C++ psioczą? Nie mogą żyć tak jak jest i akceptować wady tegoż języka?

Ja żyję tak, jak jest, każdy język ma zalety i wady. Jednak większość problemów z działaniem nie wynika z cech samego języka, tylko z nieumiejętności korzystania z mechanizmów. Ja nie psioczę i cenię C++ za to, że kod jest w dużej mierze podobny do Java lub C# i nie jest trudno go przerobić. Wystarczy kopiuj+wklej i pozmieniać trochę za pomocą znajdź+zamień i parę ręcznych poprawek. Jest to główny powód plus doświadczenie w C++, dlaczego do WebAsm "wszedłem" z C++, a nie z Rust, choć mógłbym wejść z tym drugim i są podane w tym temacie konkretne argumenty, dlaczego akurat Rust.

Tak samo mogę powiedzieć o Rust, że jego wadą jest zupełnie inne posługiwanie się zmiennymi i pamięcią, niepodobne ani do C++ ani do C#. Z drugiej strony ta wada jest zaleta, bo kompilator mocno pilnuje prawidłowego zarządzania pamięcią bez GC.

AU
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 175
0

Po co się pytać o takie rzeczy?
To subiektywana opnia, kto w czym lubi pisać.

Ja już jestem tak korzeniami przyczepiony, że ani mi się nie chce innego języka uczyć, szkoda mojego czasu, kilka podstawowych języków wystarczy, tych najczęściej używanych.
A drugie i tak już bardzo dobrze ogarnąłem tłumaczenie z assemblera na C, C++ i w drugą stronę.

W dodatku język to tylko narzędzie jak już się umie dobrze to co to za różnica, teraz jakbym miał od nowa się uczyć programowania embedded w innych językach.
Jak w kernelu linuxa i windowsa pisać teraz drivery w innym języku, samego nowego języka się nauczyć bo też nie umiem.
I tak będę musiał tym innym nowym językiem podczepiać się pod istniejące struktury napisane w innym języku.

To jest tylko nadmiar pracy bezużytecznej, już wolę się zająć zrozumieniem czegoś innego niż język, dla przykładu przyczepić sobie akcelerometr i żyroskop, potem wykonać klasyfikację sygnału, żeby określić co dana osoba robi, czy zamachuje się, skacze, każdy movement będzie generował inne sygnały, które można potem przetworzyć.

A dla kogoś innego np. ciekawe będzie znanie jak wypisać stream danych na standard output w kolejnym języku, który tylko delikatnie syntax zmienił, a czasem bardziej lub kompletnie jak zupełnie innego paradygmatu język.

jarekr000000
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: U krasnoludów - pod górą
  • Postów: 4714
2
Autysta napisał(a):

Ja już jestem tak korzeniami przyczepiony, że ani mi się nie chce innego języka uczyć, szkoda mojego czasu, kilka podstawowych języków wystarczy, tych najczęściej używanych.
A drugie i tak już bardzo dobrze ogarnąłem tłumaczenie z assemblera na C, C++ i w drugą stronę.

O kurcze, wykonujesz pracę, którą wykonuje normalnie kompilator i dekompilator - długa i żmudna -> szacun.

SL
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1029
1
Autysta napisał(a):

A drugie i tak już bardzo dobrze ogarnąłem tłumaczenie z assemblera na C, C++ i w drugą stronę.

Wątpię, kompilatory robią takie cuda, że ciężko się połapać. Linusowe stwierdzenie, że piszę C i widzę jaki assembler się generuje ma o tyle sens, że pisząc dana instrukcję wiem jaki jest najgorszy sposób na wygenerowanie danego kodu i wiem jaki koszt ma mniej więcej każda linia

W C++ tak nie ma, bo za dużo magii się dzieje. W C++ bardzo łatwo zrobić np. jakąś kopię bardzo dużego obiektu, bo system typów przed tym nie chroni jak np. Rust. W C nie ma takich problemów, bo każda taka gruba operacja to wywołanie konkretnej funkcji/makra

O nie, po kompilacji są tracone metadane

Kompilator rekurencyjne aplikuje różne optymalizacje. Dziedzina funkcji dla takich przekształceń jest zbyt złożona a dodanie do tego wielu przekształceń wcale nie pomaga.

AU
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 175
0
slsy napisał(a):

Wątpię, kompilatory robią takie cuda, że ciężko się połapać. Linusowe stwierdzenie, że piszę C i widzę jaki assembler się generuje ma o tyle sens, że pisząc dana instrukcję wiem jaki jest najgorszy sposób na wygenerowanie danego kodu i wiem jaki koszt ma mniej więcej każda linia

Cuda to nie są, ale utrudnia to zrozumienie co robi tak naprawdę funkcja, żeby móc odzyskać jej znaczenie.
Można dzielenie zamienić na mnożenie z przesunięciem i to jest równoznaczne dzieleniu przez stałą, która się nigdy nie zmieni.

Tłumaczenie z assemblera na wyższy język jest zupełnie inne niż się wydaje, to nie jest że mam jakiś algorytm w C i teraz się zastanawiam jak przy pomocy tysięca instrukcji assemblera na ile różnych sposobów wygeneruję kod, który wykona to samo.

Nie o to chodzi w tym, a np. a widzisz np. dodawanie liczb n razy i podzielenie przez n lub podzielenie 1/n potem n razy wykonanie liczba pomnożona ta stała wyliczona i suma tak tych liczb.
I to tłumaczysz sobie jako średnią arytmetyczną, tylko że to jest prosty przykład bo to matematyczna funkcja.

Jak masz jakąś architekturę i wszystko jest abstrakcyjne to trudniej znaleźć odpowiednie słowa, żeby opisać co się wykonuje.
Często trzeba się wiele godzin w debuggerze patrzeć i przchodzić ponownie, żeby dostrzec małe szczegóły i zrozumieć co to za odpowiedzialność ma za funkcja lub skrawek kodu.

ledi12
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Wrocław
1

D to nawet nie jest nisza. To jest nisza niszy :P

jarekr000000
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: U krasnoludów - pod górą
  • Postów: 4714
1
Autysta napisał(a):
slsy napisał(a):

Wątpię, kompilatory robią takie cuda, że ciężko się połapać. Linusowe stwierdzenie, że piszę C i widzę jaki assembler się generuje ma o tyle sens, że pisząc dana instrukcję wiem jaki jest najgorszy sposób na wygenerowanie danego kodu i wiem jaki koszt ma mniej więcej każda linia

Serio takie coś Linus napisał?
Ja takie bzdury to robiłem, i wierzyłem w sens, jak miałem 18 lat i pryszcze. Głównie na Amidze :-), dla usprawiedliwienia - ówczesne kompilatory były naprawdę gówniane.

AU
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 175
0
slsy napisał(a):

piszę C i widzę jaki assembler się generuje

Akurat to ma sens, bo w natywnych językach wystarczy dodać -S przy kompilacji i dostaniemy plik z kodem assemblera wygenerowanym, za nim jeszcze zostanie zamieniony na opcody, które normalnie by trzeba było jeszcze disassemblować.

Ale dalej trzeba trochę popatrzeć na wszystkie możliwe struktury w danym języku jak wyglądają, żeby rozumieć później jak to z assemblera na C++ przełożyć.

Np. obiekty w C++ działają w ten sposób, że jak alokacja jest na heapie, to z new alokuje daną ilość bajtów, czasem jakieś wyrównania struktur i adres do miejsca na heapie jest alokowany na stosie w danej ramce funkcji.
Potem w zależności od kompilatora i różnych opcji, adres tego obiektu jest ładowany np. do RCX rejestru, który jest takim this.
I potem wywoływany konstruktur.
I przy wywoływaniu innych funkcji jest po prostu ładowany ze stosu do RCX obiekt i wywoływana funkcja obiektu.
I pod koniec funkcji jest wywoływany destruktur z ustawionym this.
Virtualne metody polimorficzne są wywoływane na this->vtable->virutalna_Funkcja.

w assemblerze widzimy tylko jakieś ładowania z rsp lub rbp, 8 bajtów do rejestru rcx, wywoływanie callem jakiegoś 0x80ffaa adresu, kompletnie nie ma żadnych metadanych.
Ale znając patterny podstawowych generowanych funkcjonalności w danym języku jest się w stanie szybko rozpoznać czym dana rzecz jest.

SL
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1029
0
Autysta napisał(a):

Np. obiekty w C++ działają w ten sposób, że jak alokacja jest na heapie, to z new alokuje daną ilość bajtów, czasem jakieś wyrównania struktur i adres do miejsca na heapie jest alokowany na stosie w danej ramce funkcji.

Chyba, że clang (nie wiem czy gcc) zaaplikuje ci escape analysis i obiekt wyląduje na stercie.

Potem w zależności od kompilatora i różnych opcji, adres tego obiektu jest ładowany np. do RCX rejestru, który jest takim this.
I potem wywoływany konstruktur.
I przy wywoływaniu innych funkcji jest po prostu ładowany ze stosu do RCX obiekt i wywoływana funkcja obiektu.
I pod koniec funkcji jest wywoływany destruktur z ustawionym this.
Virtualne metody polimorficzne są wywoływane na this->vtable->virutalna_Funkcja.

Do tego wszystkiego masz tyle różnych optymalizacji:

  • inilinowanie wywołania
  • wyciąganie części funkcji do innej (uwspólnianie, wyciąganie sekcji cold z hot)
  • dewirtualizacja (dużo lepsza, gdy mamy LTO i PGO)
  • funkcje, które nic nie robią mogą zniknąć

w assemblerze widzimy tylko jakieś ładowania z rsp lub rbp, 8 bajtów do rejestru rcx, wywoływanie callem jakiegoś 0x80ffaa adresu, kompletnie nie ma żadnych metadanych.
Ale znając patterny podstawowych generowanych funkcjonalności w danym języku jest się w stanie szybko rozpoznać czym dana rzecz jest.

Za dużo jest tych optymalizacji, to nie jest funkcja odwracalna

AU
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 175
0
slsy napisał(a):

Za dużo jest tych optymalizacji, to nie jest funkcja odwracalna

Ty chyba nic nie rozumiesz na temat Reverse engineeringu.

A co to za różnica, czy w orginalnym kodzie była funkcja wywołana w main.
A w wygenerowanym kodzie już inline ta funkcja została podstawiona.
I kod został odtworzony tak, jakby od zawsze był ten kod w main, bez żadnej funkcji.

To nie ma znaczenia, tu chodzi żeby zrozumieć tylko i otrzymać czytelny kod, żeby łatwiej można było tworzyć modyfikacje w binarce lub ją patchować.
Albo wyciągnąć do innego pliku kod i przejechać SMT solverem.

jarekr000000
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: U krasnoludów - pod górą
  • Postów: 4714
2
slsy napisał(a):

Za dużo jest tych optymalizacji, to nie jest funkcja odwracalna

Zabawne będzie jak ktoś się będzie bawił w mocne meta programming i kod wynikowy będzie postaci print(42).

Marcin Borawski
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1
0

Super język, może nie na polski rynek, ale sam sobie w nim dłubię.
Prywatne "side-projects" w tym piszę jak tylko mogę.
Nie trzeba się użerać z cmake jak w C/C++, bo jest DUB, trochę jak szybszy Python...
W pakiecie ze znaną składnią z C/C++, dostajemy menadżer pakietów i prosty build system, który wymaga tylko pliku json (dla mnie czytelniejsze niż Cmake - ale tutaj nie mam dużo doświadczenia, pewnie jak ktoś siedzi w pracy nad LLVM to będzie bronił Cmake).

Disclaimer: w pracy korzystam głównie z Pythona, więc D jest dla mnie odskocznią na przypominanie sobie jak to się korzysta poprawnie ze wskaźników (i po co).

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.