Wątek przeniesiony 2023-03-02 11:00 z Off-Topic przez Riddle.

Instrukcja goto

1

@Satanistyczny Awatar

No tak, wiemy, a zatem?

Nadal nie jest to jakieś C, C++, C#, Javka itd. więc problem jak najbardziej jest zasadny

@furious programming

A czemu to istotne?

2
WeiXiao napisał(a):

@Satanistyczny Awatar

No tak, wiemy, a zatem?

A zatem jest to możliwe. (Inaczej nie da się odpowiedziec na to pytanie)

Nadal nie jest to jakieś C, C++, C#, Javka itd. więc problem jak najbardziej jest zasadny

Siedzę w C od jakichś 16 lat. Ten problem można po prostu weliminować na etapie projektowania biblioteki unikając dodawania nowych przypadków kodów lub grupując funkcje tak by zmiana dotyczyła szerszej grupy funkcji o jednolitym znaczeniu kodów wtedy da się to obsłużyć dosłownie jedną funkcją którą modyfikujesz doklejajac w switch case czy jumptable nowy handler. Mozna stosować więc metody ograniczające ilość wywołań tej funkcji po prostu pisząc własna funkcję która opakuje tą z biblioteki i masz dosłownie jedną funkcje do modyfikacji w całym projekcie. BTW nigdy też nie odczuwałem potrzeby dostepu do tych słynnych generyków. Pewne problemy po prostu nie istnieja jeśli zmieni się podejście do sposobu w jaki sie projektuje. Inne można po prostu nie uznawać za problem i z nimi żyć. Nie znam języka, który posiada kompilator który byłby w stanie sprawdzić wszystko na etapie kompilacji. Są też dynamicznie typowane gdzie właściwie weryfikacja czegokolwiek leży po stronie programisty. I też ludzie żyją i w tym programują. Jakby co to pracowałem m.in. przy kodzie takich kobył w C jak kernele Linux, FreeBSD, zdażało mi się też debugować teo słynnego gita czy pisać parsery i powłoki UNIX. Korzystał przy tym też z goto nawiasem mówiąc i nawiązujac do oryginalnego tematu wątku.

0

@Satanistyczny Awatar

no to zachęcam do odpisania na:

no to jak obsłużyć dobrze?

dodałeś rok temu do swojego silnika gierek bibliotekę X w wersji biblioteki 0.1

używasz z niej 30 metod, i m.in jest jedna metoda która zwraca kody jako int (-2, -1, 0)

wczoraj wyszło że w wersji 0.1 jest poważna luka bezpieczeństwa i jak najszybciej powinieneś podnieść do 0.2, a w międzyczasie w wersji 0.1b doszedł kod -3 do w/w

podnosisz, i co teraz - przechodzisz po wszystkich metodach i sprawdzasz ich kod aby zobaczyć czy doszły nowe kody? przeglądasz change notes? a co jeżeli ich nie ma?

a co jeżeli takich bibliotek masz 10? wszystkie change notes czytasz?

czy może twój error handler robi "default" i tyle?

0
WeiXiao napisał(a):

@Satanistyczny Awatar

no to zachęcam do odpisania na:

no to jak obsłużyć dobrze?

Tak by działało i byś był w satnie się w tym połapać.

dodałeś rok temu do swojego silnika gierek bibliotekę X w wersji biblioteki 0.1

używasz z niej 30 metod, i m.in jest jedna metoda która zwraca kody jako int (-2, -1, 0)

wczoraj wyszło że w wersji 0.1 jest poważna luka bezpieczeństwa i jak najszybciej powinieneś podnieść do 0.2, a w międzyczasie w wersji 0.1b doszedł kod -3 do w/w

podnosisz, i co teraz - przechodzisz po wszystkich metodach i sprawdzasz ich kod aby zobaczyć czy doszły nowe kody? przeglądasz change notes? a co jeżeli ich nie ma?

Poprzedni post na to odpowiedział, właśnie ten fragment miałem na myśli pisząc tamten post. Co do change notes - jeśli ich nie ma to czytam kod - diff jest w stanie zasygnalizować zmiany nawet jesli masz gołe pliki bez otoczki repozytorium. Problem braku kodu u mnie nie istnieje bo nie pracuję z proprietary, gdyż taki model uznaję za nie wart nerwów tak programistów jak i użytkowników, do tego ryzykowny z punktu widzenia bezpieczeństwa. Jeśli bym musiał z tym pracować to bym wysłał do developerów stosowne pytania, zwłaszcza jeśli bym musiał płacić za ową bibliotekę. Jeśli nie jest to dostępne to powodzenia w dezasemblacji czy śledzeniu przed debugger core dumpów i potem medytowaniu co właściwie się zmieniło jak nie masz kodu. Ogólnie ekstra punkty jeśli cały proces trzeba bedzie potem powtarzać tylko dlatego, że autorzy biblioteki zmienili kompilator czy linker którym wygenerowano finalne pliki i wnioski wyciągniete z dotychczasowej analizy wsteczne można wywalić w cześci lub całości do kosza.

a co jeżeli takich bibliotek masz 10? wszystkie change notes czytasz?

Oczywiście, że tak. Kompilator cię o całej masie rzeczy nie poinformuje. Nieczytanie change notes to ryzyko marnowania czasu, chyba że przeprowadzanie potencjalnie kilkudniowego śledztwa na własna rękę czemu kod nagle się nie kompiluje to twoja pasja i hobby dające ci frajdę.

0

@Satanistyczny Awatar

Nie widzę tego w praktyce.

Poza pracą dla specyficznych branż/firm/projektów np. military

Absolutnie nie widzę aby powiedzenie że idę sobie poprzeglądać kod 10 bibliotek .NETa od Microsoftu (bo wypadałoby podnieść) czy aby przypadkiem nie rozszerzyli czegoś nie spotkało się z dużym WTFem i marnotrastwem czasu jeżeli w ogóle bym na to znalazł czas.

I w 99% przypadków to byłaby zasadna reakcja.

Don't get me wrong - ja uważam że tak powinno być, że audyt powinien być robiony, ale rzeczywistość jest inna. Ale to offtop, wróćmy do kodu.

Nieczytanie change notes to ryzyko marnowania czasu

A zatem jeżeli cenisz sobie oszczędzanie czasu, no to mam rozwiązanie:

Załóżmy że enum w C nie jest upośledzony i intly-typed, a jest zamknięty

closed enum Bool { False, True }

i teraz piszesz sobie taki kod

int M(Bool b)
{
    switch (b)
    {
        case Bool.False: return 0;
        case Bool.True: return 1;
    }
}

a gdy w następnej wersji do tego enuma ktoś doda wartość Maybe

to nagle przestanie ci się ten switch kompilować - tylko tyle, albo aż tyle?

I żadne kody, wyjątki i inne takie :D

0
WeiXiao napisał(a):

@Satanistyczny Awatar

Nie widzę tego w praktyce.

Ja to widzę zaś od lat w praktyce stosowane.

to nagle przestanie ci się ten switch kompilować

Pomijajac, że to nie jest nawet kod w C, to można założyć istnienie języka w którym istotnie się to nie skompiluje.

  • tylko tyle, albo aż tyle?

Tyle co?

0

@Satanistyczny Awatar

Ja to widzę zaś od lat w praktyce stosowane.

I nadal może to być spójne z tym co napisałem

Pomijajac, że to nie jest nawet kod w C, to można założyć istnienie języka w którym istotnie się to nie skompiluje.

Oczywiście że to nie jest C, toż to C nie jest zbyt przyjaznym programiście językiem.

1
WeiXiao napisał(a):

@Satanistyczny Awatar

Ja to widzę zaś od lat w praktyce stosowane.

I nadal może to być spójne z tym co napisałem

Ale nie musi. Taki jest problem z "może". Jak dotąd nie czytanie dokumentacji i change logów powodowało w mojej działce więcej straty czasu niż zysku.

Oczywiście że to nie jest C, toż to C nie jest zbyt przyjaznym programiście językiem.

Jezyk programowania nie jest od bycia twoim kumplem. Na piwo z nim nie wyskoczysz. Jeśli chcesz rozmawiac o C to pisz kod w C, a nie tworzysz nowe byty bo to już ocieranie się o te słynne logical fallacy. Stwarza też wrażenie że uciekasz od konkretów w rozmowie i galopujesz siną w dal w kierunku hipotetycznych problemów, które mogą ale nie muszą zajść, a po przedstawieniu alternatywnych rozwiązań do tego co postulujesz nagle ucinasz temat za pomocą pozbawionych treści semantycznej pytań na które na da się odpowiedzieć, gdyż nie wiadomo czy nawet w ogóle o coś pytasz czy to po prostu ozdobnik w stylu "Mocium Panie" Cześnika z Zmesty.

1

@Satanistyczny Awatar:

Jezyk programowania nie jest od bycia twoim kumplem

Język jest narzędziem i powinien służyć jak najlepiej się da.

a nie tworzysz nowe byty bo to już ocieranie się o te słynne logical fallacy

Jakim zatem fallacy jest przedstawienie konceptu w innej technologii niż ta, w której dany koncept jest niemożliwy do przedstawienia?

a po przedstawieniu alternatywnych rozwiązań do tego co postulujesz nagle ucinasz temat za pomocą

Tak jak pisałem wyżej - te alternatywy są po prostu mierne.

Słabo się skalują, polegają na ludziach (że wyłapiesz dziwne rzeczy w kodzie), nie przyjmą się łatwo na rynku (nikt ci w januszeksach czy innych niespecyficznych firmach/projektach nie da czasu na robienie regularnego audytu zależności)

Jako branża powinniśmy przenosić jak najwięcej na nasze narzędzia, bo one nie mają "gorszych dni", są szybsze i tańsze ;)

0
WeiXiao napisał(a):

Język jest narzędziem i powinien służyć jak najlepiej się da.

Anthropomorphic Fallacy

Jakim zatem fallacy jest przedstawienie konceptu w innej technologii niż ta, w której dany koncept jest niemożliwy do przedstawienia?

To pytanie jakie teraz zadajesz to jest straw man. Zaś to co zrobiłes w poprzednim poście i co ci wytknałem to był red herring.

Tak jak pisałem wyżej - te alternatywy są po prostu mierne.

Invincible ignorance fallacy.

Słabo się skalują,

Unwarranted assumption fallacy.

polegają na ludziach (że wyłapiesz dziwne rzeczy w kodzie), nie przyjmą się łatwo na rynku (nikt ci w januszeksach czy innych niespecyficznych firmach/projektach nie da czasu na robienie regularnego audytu zależności)

Argumentum ad populum + Unwarranted assumption fallacy.

Jako branża powinniśmy przenosić jak najwięcej na nasze narzędzia, bo one nie mają "gorszych dni", są szybsze i tańsze ;)

Unwarranted assumption fallacy.

3

@Satanistyczny Awatar:

Słabo się skalują,
Unwarranted assumption fallacy.

Na pewno bezpodstawne, bo przecież nie mamy całej branży wokół security / supply chain

która tworzy jakieś skanerki - czy to od secretów w kodzie, czy to od rzucania warningami nt. zależności z dziurami, czy to statycznej analizy kodu, czy to nawet od wkładania wysiłku w poprawę i usprawnienie komunikatów błędów w kompilatorach i kończąc na fuzzerach, o atrakcyjności Rusta też mam pisać? I o tym jak chętnie jest wpychany w Kernel?

Podawałem w poprzednich postach linki do analiz Microsoftu czy Googla/Chromium

https://msrc.microsoft.com/blog/2019/07/a-proactive-approach-to-more-secure-code/

Tu masz napisane ile wysiłku jest wkładane w to, aby odciążyć ludzi oraz jak dziurawy jest/był software w którym to ludzie mieli ogarnąć ;)

It’s not that there are no tools to help developers write secure code. The developer has a plethora of tools at their disposal: amazingly complex static analysis tools (that take a month or two to learn), fuzzing at scale (that provides haystacks of crashes to triage), taint analysis, and constraint solvers. There is guidance to help developers adopt secure practices, too: the Secure Development Lifecycle to wade through, encyclopaedias of coding guidelines, hours of code review, plenty of training, and threat modelling guidance. We’ve changed the compilers and created mitigations to bail developers out of errors. Visual Studio even has squiggly red lines to highlight potential flaws!

A case for memory-safe languages A case for memory-safe languages

A developer’s core job is not to worry about security but to do feature work. Rather than investing in more and more tools and training and vulnerability fixes, what about a development language where they can’t introduce memory safety issues into their feature work in the first place? __That would help both the feature developers and the security engineers—and the customers.

A language considered safe from memory corruption vulnerabilities removes the onus of software security from the feature developer and puts it on the language developer. Thankfully, there are several languages available that are regarded as “safe” from memory corruption vulnerabilities, such as C#. Many development teams at Microsoft have embraced the world of using these safe languages to write new customer related features.

https://www.chromium.org/Home/chromium-security/memory-safety/

Attackers innovate, so defenders need to innovate just to keep pace.

We can no longer derive sufficient innovation from more processes or
stronger sandboxes (though such things continue to be necessary).

Therefore the cheapest way to maintain the advantage is to squash bugs at
source instead of trying to contain them later.

polegają na ludziach (że wyłapiesz dziwne rzeczy w kodzie), nie przyjmą się łatwo na rynku (nikt ci w januszeksach czy innych niespecyficznych firmach/projektach nie da czasu na robienie regularnego audytu zależności)

Unwarranted assumption fallacy.

Podobnie jak wyżej, to jeżeli faktycznie jest robiony ten audyt, to czemu gdy pojawia się jakiś problem głęboko w supply chainie, to jest spustoszenie?

Nie tak dawno Javowy Log4j, czy wcześniej jakiś Javascriptowy leftpad?

https://blog.majid.info/supply-chain-vetting/

screenshot-20230524102359.png

0

Zawsze gdy tworzę kod wykorzystujący klasę TFileStream muszę takie durne puste try except stawiać, bo jakiś geniusz wpadł na pomysł, żeby w przypadku niemożności otwarcia pliku rzucić wyjątek.

Dlaczego nie standardowa obsługa plików w Pascalu (Assign, Reset, Rewrite, Read, Write, Seek, Close, i IOResult)?

Mając obsługę błędów wewnątrz logiki, od razu wiadomo w którym miejscu błąd może zaistnieć i od razu widzimy jak jest obsługiwany. Przenoszenie obsługi błędów gdzieś indziej jest obfuskacją kodu.

To już kwestia osobistego podejścia. Wyjątki umożliwiają stosowanie obu podejść.

Z wyjątkami jest taki problem że ludzie piszą

Fakt, że ludzie niepoprawnie stosują dany mechanizm nie stanowi wady tego mechanizmu.

discriminated unions

Zasadniczo się nie wypowiem, bo nie wiem, co to te "discriminated unions". Jednak prawdopodobnie jest to jakiś dziwny feature obecny w nowych językach (takich jak Rust, Go i podobne) stworzony dla wąskiego zakresu specyficznych przypadków. Zastanawia mnie, ile doświadczenia trzeba mieć z takim Rustem (jako przykład), żeby bez celowej nauki na pamięć wymienić wszystkie słowa kluczowe języka, a najlepiej wraz z ich znaczeniem?

Co masz tutaj na myśli? Jeśli chodzi ci o wartość zwracana przez funkcję - to nie widzę zasadniczo dlaczego nie mozna tego zrealizować. Wręcz widziałem w życiu masę kodu w C gdzie właśnie tak sprawdzano wartości zwracane czy to bezpośrednio przez funkcję, czy pośrednio przez jakies zmienne globalne typu errno w POSIX.

Dobrze, że wspomniałeś o wariancie ze zmienną globalną. Moim zdaniem jest to lepsze (wydajniejsze), niż propagowanie kodu błędu poprzez wartość zwracaną.

Jeśli ktoś nie potrafi myśleć podczas klepania kodu to żadne ficzery i żadej cukier mu nie pomoże. Niektórzy po prostu nie potrafią napisać dobrego kawałka kodu, jeśli im język, kompilator, biblioteki i ChatGPT w tym nie pomogą. :P

Częściowo tak, ale niezupełnie. Teoretycznie wszystko da się zapisać w BF, a przecież w praktyce większość programistów odczuło by niemałe trudności. Wygoda do pewnego stopnia też jest ważna.

To jest problem metodyki bo dobra metodyka jest idiotoodporna i w tę stronę idzie programowanie w ostatnich czasach.

Czy jednak "programowanie idiotoodporne" to rzeczywiście w pełni dobry kierunek? Nawet gdy nie trzeba myśleć nad pisanym kodem (dzięki rożnorodnym "idiotoodpornym" metodykom), to w zamian trzeba włożyć wysiłek umysłowy w nauczenie się tych wszystkich abstrakcyjnych mechanizmów.

Poza wszystkim, co powyżej: nieco odeszliśmy od tematu wątku, jakim jest sama instrukcja skoku, a nie ogólnie obsługa błędów.

0

@Manna5: bym prosił, że jeśli cytujesz kilka postów w ramach jednego swojego posta, to abyś nie usuwał nagłówków z nickami ich autorów (i z linkami do tych postów). Trudno mi się teraz połapać czyje słowa są czyje, a linków do źródeł nie ma. :P

Manna5 napisał(a):

Dlaczego nie standardowa obsługa plików w Pascalu (Assign, Reset, Rewrite, Read, Write, Seek, Close, i IOResult)?

Głównie dlatego, że standardowy Read jest procedurą, a mi potrzebna jest funkcja. Dlatego używam TFileStream.Read, który zwraca liczbę odczytanych bajtów, co pozwala wykryć przedwczesny koniec strumienia. Oczywiście jeśli chodzi o aplikacje desktopowe/okienkowe (np. edytor fontów/kursorów), które i tak buduję za pomocą OOP. W grze stosuję SDL_RWread, który zwraca inta z liczbą odczytanych pakietów.

To już kwestia osobistego podejścia. Wyjątki umożliwiają stosowanie obu podejść.

To nie o podejście chodzi — wyjątki muszą być obsługiwane w konkretnym miejscu (blok catch czy tam except), natomiast kody błędów z reguły obsługiwane są tuż po wywołaniu funkcji i muszą być, bo od nich zależy to co i jak dalej będzie wykonywane. Oczywiście oba sposoby pozwalają obsługiwać wszelkiego rodzaju błędy, więc kwestią jest to z którego sposobu skorzystamy. I tutaj preferencje osobiste liczą się mniej niż wymagania projektowe i technologiczne, code of conduct itd. itp.

Dobrze, że wspomniałeś o wariancie ze zmienną globalną. Moim zdaniem jest to lepsze (wydajniejsze), niż propagowanie kodu błędu poprzez wartość zwracaną.

Nie jest wydajniejsze, bo w dalszym ciągu logika tych funkcji musi sprawdzać czy dana funkcja, którą wywołuje, wykonała się czy spowodowała błąd. To jest ten sam rząd wydajności co w przypadku samych kodów błędów, ale narzutem w przypadku globali jest dodatkowy czas na odłożenie informacji o błędzie w zmiennych (jakieś kody, stringi z komunikatami itd.).

Wszystko zależy od tego jak chcemy, aby błędy były obsługiwane. W przypadku wyjątków, nie ma za bardzo pola do manewru, bo sposób ich obsługi determinowany jest na poziomie języka. Natomiast w przypadku klasycznej obsługi błędów, hulaj dusza — implementujemy to jak chcemy. Można propagować kod błędu, ale też można stan poprawności wykonania funkcji określać na podstawie rezultatu w formie boola (albo bez żadnego rezultatu, czyli void), natomiast wszelkie informacje o błędach odkładać do specjalnych zmiennych. Przy czym te zmienne mogą być typu prostego, ale też mogą być np. stosami do gromadzenia większej ilości informacji — tu mamy dowolność.

Przy czym to nie powinny być zmienne globalne, a lokalne dla danego modułu, do których dostęp powinien być realizowany za pomocą specjalnych funkcji. Czyli tak jak to jest zrobione w WinAPI, za pomocą GetLastError czy w SDL, gdzie mamy np. SDL_GetError, SDL_SetError i SDL_ClearError.

Częściowo tak, ale niezupełnie. Teoretycznie wszystko da się zapisać w BF, a przecież w praktyce większość programistów odczuło by niemałe trudności. Wygoda do pewnego stopnia też jest ważna.

Kwestią subiektywną jest to, czym dokładnie ów wygoda jest. Dla was wygodą mogą być wyjątki czy Result<T>, dla mnie wygodą są kody błędów oraz pełna i łatwa kontrola nad przepływem sterowania. Tyle że dla mnie trudnością nie jest konieczność naklepania nieco większej ilości kodu, bo kod pisze się szybko (szczególnie gdy używa się IDE), natomiast jego czytanie i debugowanie pochłania znacznie więcej czasu — dlatego wolę mieć bardzo prosty składniowo kod, niż błądzić w zawiłościach mechanizmów wykonujących czynności automatycznie, bez mojej wiedzy i poza moją kontrolą.

0

@Manna5:

Zasadniczo się nie wypowiem, bo nie wiem, co to te "discriminated unions". Jednak prawdopodobnie jest to jakiś dziwny feature obecny w nowych językach (takich jak Rust, Go i podobne) stworzony dla wąskiego zakresu specyficznych przypadków.

ale po co zakładać?

https://tonyarcieri.com/a-quick-tour-of-rusts-type-system-part-1-sum-types-a-k-a-tagged-unions

dodaj do tego

https://openjdk.org/projects/amber/design-notes/patterns/exhaustiveness

i jest tu wprost napisane

More importantly, what happens if someone later adds another constant to the Color enum? If we have an explicit match-all clause then we will only discover the new constant value if it shows up at run time. But if we code the switch to cover all the constants known at compile time and omit the match-all clause, then we will find out about this change the next time we recompile the class containing the switch — and can then choose how to handle it. A match-all clause risks sweeping exhaustiveness errors under the rug.

In conclusion: An exhaustive switch without a match-all clause is better than an exhaustive switch with one, when possible.

Czy jednak "programowanie idiotoodporne" to rzeczywiście w pełni dobry kierunek? Nawet gdy nie trzeba myśleć nad pisanym kodem (dzięki rożnorodnym "idiotoodpornym" metodykom), to w zamian trzeba włożyć wysiłek umysłowy w nauczenie się tych wszystkich abstrakcyjnych mechanizmów.

Jeżeli efektem będzie np. mniej błędów bezpieczeństwa - to tak, jest to dobry kierunek.

3

@Manna5:

Zasadniczo się nie wypowiem, bo nie wiem, co to te "discriminated unions".

Najczęściej spotkałem się z nazwą tagged union lub sum type. Założenie jest bardzo proste, jest to unia + pole, które nam mówi jaki typ jest w środku, ale zrobione w taki sposób, że nie da się tego źle użyć (bez magii). Więc przekładając na C to będzie:

struct Foo {
  enum {
    A,
    B
  } type;

  union {
    a_type a;
    b_type b;
  } data;
};

Z tą różnicą, że nie da się zrobić czegoś takiego:

struct Foo foo;

if (foo.type == A) {
  foo(foo.data.b);
}

W Ruscie to co wyżej jest zapisane jako:

enum Foo {
  A(AType),
  B(BType)
}

// …

let foo: Foo = // …;

if let A(a) = foo {
  // mamy dostęp do `AType`
}

Więc mamy rozwiązanie zdecydowanie mniej problematyczne (union w stylu C też jest dostępne, ale wyciąganie danych z tego jest domyślnie unsafe).

Sama idea jest niemal równie stara co C (1972), bo podobny koncept jest w Pascalu (1970) i MLu (1973).

Moim zdaniem jest to lepsze (wydajniejsze), niż propagowanie kodu błędu poprzez wartość zwracaną.

Sądzę, że obecnie różnica wydajnościowa jest pomijalna, a jak już to może to spowodować spowolnienie działania programu przez konieczność synchronizacji w przypadku pracy współbieżnej programu.

Czy jednak "programowanie idiotoodporne" to rzeczywiście w pełni dobry kierunek? Nawet gdy nie trzeba myśleć nad pisanym kodem (dzięki rożnorodnym "idiotoodpornym" metodykom), to w zamian trzeba włożyć wysiłek umysłowy w nauczenie się tych wszystkich abstrakcyjnych mechanizmów.

Czy spadochrony w samolotach to rzeczywiście dobry kierunek? Nawet gdy nie trzeba myśleć jak lecisz samolotem, to w zamian trzeba włożyć wysiłek umysłowy w nauczenie się tych wszystkich linek.

W przypadku np. takiego Rusta to ownership to nie jest coś co musisz się nauczyć ekstra, bo pisząc w C też musisz to wiedzieć, po prostu nie masz nikogo, kto Ci powie, że "ej stary, ale tutaj skrewiłeś".

EDIT:

Zastanawia mnie, ile doświadczenia trzeba mieć z takim Rustem (jako przykład), żeby bez celowej nauki na pamięć wymienić wszystkie słowa kluczowe języka, a najlepiej wraz z ich znaczeniem?

A umiesz to zrobić dla C? Bo właśnie sprawdziłem i Rust ma mniej słów kluczowych niż C - 51 vs 60. I w C nie policzyliśmy do tego słów kluczowych preprocesora, więc jesteśmy tutaj też troszeczkę łaskawsi dla C.

0

@furious programming

bym prosił, że jeśli cytujesz kilka postów w ramach jednego swojego posta, to abyś nie usuwał nagłówków z nickami ich autorów (i z linkami do tych postów). Trudno mi się teraz połapać czyje słowa są czyje, a linków do źródeł nie ma. :P

Nie używam przycisku Odpowiedz (ponieważ cytuje cały post, a mi chodzi o fragmenty), tylko wpisuję znak > i wklejam tekst za nim, a więc nie mam co usuwać. Teraz dodałem mentiony informujące o autorach.

Oczywiście jeśli chodzi o aplikacje desktopowe/okienkowe (np. edytor fontów/kursorów), które i tak buduję za pomocą OOP. W grze stosuję SDL_RWread, który zwraca inta z liczbą odczytanych pakietów.

Nie rozumiem, dlaczego używasz w różnych przypadkach odmiennych podejść. Szczególnie, że część kodu mógłbyś współdzielić pomiędzy grą, a pomocniczymi edytorami - np. wczytywanie tych kursorów z pliku.

używam TFileStream.Read, który zwraca liczbę odczytanych bajtów, co pozwala wykryć przedwczesny koniec strumienia

Jest przecież funkcja Eof. Korzystając z niej można napisać wrapper łączący odczyt i sprawdzenie końca (chociaż trzeba byłoby jakoś rozwiązać problem z tym, że Read przyjmuje zmienną dowolnego typu przez adres, a we własnej funkcji - w tym wrapperze - na ile wiem, tak nie można).

@WeiXiao

Jeżeli efektem będzie np. mniej błędów bezpieczeństwa - to tak, jest to dobry kierunek.

Może i tak... Gdy kierunek ten stanie się popularniejszy w zastosowaniu, to pojawią się miarodajne statystyki, które to zweryfikują.

@hauleth

Sama idea jest niemal równie stara co C (1972), bo podobny koncept jest w Pascalu (1970) i MLu (1973).

Rzeczywiście, widziałem coś takiego w Pascalu. Nie skojarzyłem nazwy. Jednak taka funkcjonalność ma ograniczenia, ponieważ czasami wariant unii można "zgadnąć" sposobem bardziej złożonym niż prosty znacznik.

w przypadku pracy współbieżnej programu

Działanie współbieżne to oczywiście inna sprawa, wtedy na zmienne globalne (i statyczne) trzeba uważać.

A umiesz to zrobić dla C?

Tak, byłbym w stanie wymienić słowa kluczowe ANSI C. Tutaj tego nie zrobię, bo i tak nie będzie wiarygodne (pisząc na forum miałbym czas sprawdzić w Internecie).

właśnie sprawdziłem i Rust ma mniej słów kluczowych niż C - 51 vs 60

Akurat to mnie zaskoczyło. Być może dlatego, że te funkcjonalności są implementowane raczej przez operatory niż słowa.

0

@Manna5:

Może i tak... Gdy kierunek ten stanie się popularniejszy w zastosowaniu, to pojawią się miarodajne statystyki, które to zweryfikują.

te miarodajne statystyki które linkuje już 3 raz? :P

https://msrc.microsoft.com/blog/2019/07/a-proactive-approach-to-more-secure-code/

https://www.chromium.org/Home/chromium-security/memory-safety/

0
Manna5 napisał(a):

Nie używam przycisku Odpowiedz (ponieważ cytuje cały post, a mi chodzi o fragmenty), tylko wpisuję znak > i wklejam tekst za nim, a więc nie mam co usuwać. Teraz dodałem mentiony informujące o autorach.

Sugeruję zacząć używać tego przycisku i wbudowanego cytowania, bo obecnie pomijasz kluczowe informacje — link do cytowanego posta. Mentiony nie wystarczą, bo nie są dowodem na napisanie cytowanych słów. Najłatwiej jest po prostu zacytować cały post, pousuwać zbędne fragmenty, a te pozostałe rozdzielić własnymi komentarzami, z zachowaniem nagłówka (z linkiem do źródła).

Nie rozumiem, dlaczego używasz w różnych przypadkach odmiennych podejść. Szczególnie, że część kodu mógłbyś współdzielić pomiędzy grą, a pomocniczymi edytorami - np. wczytywanie tych kursorów z pliku.

Dlatego że kod silnika jest strukturalno-proceduralny, a edytory powstają w oparciu o LCL, który jest obiektowy.

Jest przecież funkcja Eof.

EoF w niczym mi nie pomoże, a tylko wymusi pisanie większej ilości kodu — tak samo jak IOResult. Jedynym sensowniejszym odpowiednikiem byłoby BlockRead albo systemowy ReadFile, ale nie ma co mieszać paradygmatów, skoro tak czy siak LCL operuje na swoich klasach strumieni.

Dobra, dość off-topu, czas najwyższy wrócić do tematu goto. ;)

1
Manna5 napisał(a):

Jeżeli efektem będzie np. mniej błędów bezpieczeństwa - to tak, jest to dobry kierunek.

Może i tak... Gdy kierunek ten stanie się popularniejszy w zastosowaniu, to pojawią się miarodajne statystyki, które to zweryfikują.

No jak na razie to i firmy przechodzą na inne języki jak i agencje rządowe sugerują zmianę technologii na takie, które wymuszają bezpieczeństwo pamięci.

Rzeczywiście, widziałem coś takiego w Pascalu. Nie skojarzyłem nazwy. Jednak taka funkcjonalność ma ograniczenia, ponieważ czasami wariant unii można "zgadnąć" sposobem bardziej złożonym niż prosty znacznik.

Skoro jest on bardziej złożony, to po co zgadywać? Nie rozumiem za bardzo jakie to ma tutaj zastosowanie ani jakie ma to znaczenie w dyskusji.

w przypadku pracy współbieżnej programu

Działanie współbieżne to oczywiście inna sprawa, wtedy na zmienne globalne (i statyczne) trzeba uważać.

Coraz częściej oprogramowanie jest współbieżne, bo docieramy do granic wydajnościowych. Chcesz być szybki? Wtedy najczęściej potrzebujesz jakiejś współbieżności (niekoniecznie musi być ona zrównoleglona nawet).

A umiesz to zrobić dla C?

Tak, byłbym w stanie wymienić słowa kluczowe ANSI C. Tutaj tego nie zrobię, bo i tak nie będzie wiarygodne (pisząc na forum miałbym czas sprawdzić w Internecie).

właśnie sprawdziłem i Rust ma mniej słów kluczowych niż C - 51 vs 60

Akurat to mnie zaskoczyło. Być może dlatego, że te funkcjonalności są implementowane raczej przez operatory niż słowa.

Może i tak, ale to dalej 32 słowa kluczowe dla samego języka, do tego jeszcze dodajmy 13 "słów kluczowych" będącymi dostępnymi tokenami w preprocesorze + asm i znów mamy 55 słów.

A Rust nie ma aż tak dużo nowych operatorów względem C:

Rust C
.. ++a
..val a++
a..b --a
..= a--
:: (namespace) a ? b : c
a? +a
pattern @ expr ~a

Więc praktycznie wychodzi po równo.

0

@furious programming: A co byś powiedział, żeby napisac dwie proste aplikacje. Jedną z goto i Twoim podejściem że errory są zwracane przez return-type'y, a drugą bez goto i z wyjątkami?

Żeby porównać podejścia?

1

Skojarzenie goto z obsługą błędów przez wartości zwracane jest bardzo dalekie.

0

@TomRiddle: szkoda czasu na pisanie takich programów. Zobacz na dowolny kod pisany przy użyciu czystego Win32 API — w takim kodzie używa się bardzo wielu funkcji, które zwracają boole, uchwyty oraz kody błędów i się na nie na bieżąco reaguje (rozróżnia ifami i odpowiednio przekierowuje sterowanie).

goto do obsługi błędów jest namiętnie wykorzystywany w C, kiedy istnieje potrzeba skoku na koniec funkcji, do wspólnej częsci robiącej cleanup. Innej formy obsługi błędów przy użyciu goto jakoś sobie nie wyobrażam, bo ta instrukcja nie dla nich została stworzona — to zwykły skok bezwarunkowy, może być używany do czegokolwiek.

2
furious programming napisał(a):

goto do obsługi błędów jest namiętnie wykorzystywany w C, kiedy istnieje potrzeba skoku na koniec funkcji, do wspólnej częsci robiącej cleanup.

I to ma sens, ale nie z tego powodu, że goto jest dobre, ale z tego powodu, że C jest ułomne i się nie da inaczej.

1

@hauleth: przecież to na jedno wychodzi. ;)

Z drugiej strony, goto to nie tylko obsługa błędów — skoki można wykonywać kiedy się chce i gdzie się chce (w ramach scope'u). Co zabawne, żaden ”nowoczesny” język programowania, który nie ma goto, wspiera co najwyżej kilka zamienników goto (jak defer zamiast wymienionego cleanupu czy etykietowane case'y instrukcji wyboru), a reszty nie, bo skoki są złe. W rezultacie, w C można wyrzeźbić dowolny przepływ sterowania zachowując minimalną ilość kodu, a w innych językach nie da się tego zreprodukować (nie ma zamienników, to np. tworzymy funkcje jednorazowe i rozpycha się kod).

No i który język jest ułomny? Ten, w którym można robić co i jak się chce, czy ten, który traktuje kodera jak idiotę i na niewiele pozwala, bo nie daj bug koder źle coś napisze i błąd spowoduje, albo wyciek pamięci? Współczesne języki są jak nadopiekuńczy rodzice — irytujące i ograniczające.

Wolałbym w drugą stronę — wziąć takie C i go jeszcze bardziej zunsafe'ować. :D

3
furious programming napisał(a):

No i który język jest ułomny? Ten, w którym można robić co i jak się chce, czy ten, który traktuje kodera jak idiotę i na niewiele pozwala, bo nie daj bug koder źle coś napisze i błąd spowoduje, albo wyciek pamięci? Współczesne języki są jak nadopiekuńczy rodzice — irytujące i ograniczające.

Ten drugi. Tu nie chodzi o to, że język czegoś zabrania tylko stara się minimalizować przypadki, gdzie programista doprowadza do sytuacji, gdzie dzieje się coś niespodziewanego. goto sprawia, że możesz łatwo sobie zaszkodzić. Tak samo języki takie jak C++/Rust bardzo ułatwiają zarządzanie pamięcią dynamiczną dzięki czemu praktycznie wszystkie sytuacje obsługują się automatycznie dzięki abstrakcjom języka

Wolałbym w drugą stronę — wziąć takie C i go jeszcze bardziej zunsafe'ować. :D

Takie podejście przypomina koszenie trawnika nożyczkami. Prawdą jest, że nożyczki są prostsze od kosiarki i pozwalają na wszystko (np. dowolna wysokość koszenia), ale ludzie to ludzie i przyspieszacze są potrzebne, bo inaczej jest większa szansa na wykonanie błędów a o niższej wydajności już nie wspomnę

1
furious programming napisał(a):

Z drugiej strony, goto to nie tylko obsługa błędów — skoki można wykonywać kiedy się chce i gdzie się chce (w ramach scope'u).

Tylko jakie jest sensowne zastosowanie tego? Bo ja nie widzę zalety nad wywoływaniem funkcji. Poza tym w C da się mieć skoki pomiędzy scope (goto działa w obrębie funkcji, a nie w obrębie scope, bo musi by działało wyskakiwanie z zagnieżdżonych pętli):

void foo() {
    {
    a:
        int a = 20;

        printf("%d\n", a);
    }
    int a = 10;

    printf("%d\n", a);
    goto a;
}

Co zabawne, żaden ”nowoczesny” język programowania, który nie ma goto, wspiera co najwyżej kilka zamienników goto (jak defer zamiast wymienionego cleanupu czy etykietowane case'y instrukcji wyboru), a reszty nie, bo skoki są złe. W rezultacie, w C można wyrzeźbić dowolny przepływ sterowania zachowując minimalną ilość kodu, a w innych językach nie da się tego zreprodukować (nie ma zamienników, to np. tworzymy funkcje jednorazowe i rozpycha się kod).

Ale za to masz większą czytelność przepływu i zmiennych + nie musimy pilnować jakie zmienne mają jakie nazwy, bo możemy użyć tej samej nazwy zmiennej w każdym scope bez kombinowania. Jak masz TCO to wydajnościowo będzie tak samo.

No można wyrzeźbić dowolny przepływ, ale to nie ilość kodu jest problemem. Jakby ilość kodu była problemem, to byśmy pisali w APLu. Istotniejsza jest czytelność tego przepływu niż ilość kodu jaki jest potrzebny by go zrozumieć. Więcej, nie tylko istotna jest ilość kodu, ale również to, jak łatwo ten przepływ testować. A w tym przypadku funkcje często będą zdecydowanie czytelniejsze i łatwiejsze w testowaniu.

No i który język jest ułomny? Ten, w którym można robić co i jak się chce, czy ten, który traktuje kodera jak idiotę i na niewiele pozwala, bo nie daj bug koder źle coś napisze i błąd spowoduje, albo wyciek pamięci?

Jeśli błąd w programie może spowodować miliony strat, bo się przekręcił licznik, to jak najbardziej będzie to ułomny język. Jeśli błąd spowoduje, że ludzie będą umierać, to jak najbardziej będzie to ułomny język. Tak, programistę należy traktować jak idiotę. A większość języków o których tu mówimy pozwala na wszystko na co pozwala C, ale po prostu ogranicza przestrzeń w której może Ci to wybuchnąć w twarz.

Więc tak, najczęściej języki, które nakładają pewne ograniczenia na to co programista może zrobić, są językami, które wg mnie są lepiej zaprojektowane. Gdyby było inaczej, to dalej byśmy pisali w asemblerze, a nie w językach wyższego poziomu jak Pascal czy C, bo przecież one też nakładają na nas ograniczenia, a to przecież bez sensu.

Współczesne języki są jak nadopiekuńczy rodzice — irytujące i ograniczające.

Z drugiej strony CVE i historia błędów pokazuje, że jak najbardziej jest to potrzebne. Przy czym to nie tak, że np. ownership to jest coś, czego w C czy Pascalu nie ma, oczywiście, że jest, z tą jedną różnicą, że jest to niejawnie i to programista musi o tym wiedzieć i pamiętać, a nie ma mechanizmu, który go automatycznie w tym wspomoże. Albo trzeba uciekać się do 3rd party (które często mają więcej false positives), albo używać mechanizmów runtime, które nie zawsze mogą być użyte.

Wolałbym w drugą stronę — wziąć takie C i go jeszcze bardziej zunsafe'ować. :D

Masz C--, nie przyjęło się zbytnio, alternatywnie możesz pisać w LLVM IR. Ale w ogólnym rozrachunku to C też obecnie ciężko nazwać low-level skoro działa w pewnej abstrakcyjnej maszynie, która niekoniecznie współgra ze współczesnym sprzętem (przez co niektóre rzeczy są bezsensownie trudne, mimo iż koncepcyjnie takie być nie powinny).

1
slsy napisał(a):

Ten drugi. Tu nie chodzi o to, że język czegoś zabrania tylko stara się minimalizować przypadki, gdzie programista doprowadza do sytuacji, gdzie dzieje się coś niespodziewanego. goto sprawia, że możesz łatwo sobie zaszkodzić.

Dlatego właśnie napisałem, że tego typu języki są jak nadopiekuńczy rodzice. Rzeczy niespodziewane dzieją się wtedy, kiedy programista sam nie wie co robi i nie potrafi poprawnie zaimplementować algorytmu, a nie przez goto czy jakiekolwiek inne konstrukcje języka.

Takie podejście przypomina koszenie trawnika nożyczkami. Prawdą jest, że nożyczki są prostsze od kosiarki i pozwalają na wszystko (np. dowolna wysokość koszenia), ale ludzie to ludzie i przyspieszacze są potrzebne, bo inaczej jest większa szansa na wykonanie błędów a o niższej wydajności już nie wspomnę

Niższej wydajności czego? Kodu czy klepania kodu? Bo jeśli chodzi o wydajność kodu, to żaden nowoczesny język napakowany zabezpieczeniami i automatami nie pobił go w tej kwestii.


hauleth napisał(a):

Tylko jakie jest sensowne zastosowanie tego? Bo ja nie widzę zalety nad wywoływaniem funkcji.

Przykłady były już podawane wielokrotnie — goto może zastąpić jednorazowe funkcje, może zastąpić pętle, wykluczyć konieczność mnożenia zmiennych loklanych, zmniejszyć ilość kodu do pisania i trzymać daną logikę w jednym miejscu, co zdecydowanie ułatwia nie tylko analizę, ale i debugowanie (w końcu wszystko jest w jednym miejscu, w obrębie wzroku).

Poza tym w C da się mieć skoki pomiędzy scope (goto działa w obrębie funkcji, a nie w obrębie scope, bo musi by działało wyskakiwanie z zagnieżdżonych pętli):

Masło maślane. ”Scope” to z angielskiego ”zakres”/”zasięg”, goto ma określony zasięg i działa w jego obrębie. W każdym jezyku goto pozwala na skoki w obrębie danej funkcji, a w niektórych nawet i poza daną funkcję (np. we Free Pascalu z włączonym {$NONLOCALGOTO}).

Ale za to masz większą czytelność przepływu i zmiennych + nie musimy pilnować jakie zmienne mają jakie nazwy, bo możemy użyć tej samej nazwy zmiennej w każdym scope bez kombinowania. Jak masz TCO to wydajnościowo będzie tak samo.

Nie ma ”bardziej czytelnego” przepływu sterowania niż jawne konstrukcje i instrukcje, które służa do branchowania. To logiczne, że im więcej się widzi i im mniej jest ukrywane, tym łatwiej taki kod zrozumieć, analizować i debugować. Choć mam wrażenie, że nie podzielacie mojego zdania. :P

Jeśli błąd w programie może spowodować miliony strat, bo się przekręcił licznik, to jak najbardziej będzie to ułomny język. Jeśli błąd spowoduje, że ludzie będą umierać, to jak najbardziej będzie to ułomny język.

To nie język jest ułomny, a napisany w nim kod. Poza tym za testowanie odpowiadają testy, a nie język, więc jeśli ich zabraknie lub są niedostatecznie dobre, to język nie ma większego znaczenia, bo tak czy siak błędy będą. Podsumować to mogę w ten sposób, że jeśli dla was goto jest problemem, to i if czy switch również są problematyczne.

0

@Riddle

@furious programming: A co byś powiedział, żeby napisac dwie proste aplikacje. Jedną z goto i Twoim podejściem że errory są zwracane przez return-type'y, a drugą bez goto i z wyjątkami?

Żeby porównać podejścia?

I jak byś porównał potem te aplikacje? Załóżmy, że obie by działały - oczywiście. Można jeszcze zmierzyć wydajność, ale walorów takich jak wygoda wprowadzania zmian nie zmierzysz.

@furious programming:

...a w niektórych nawet i poza daną funkcję (np. we Free Pascalu z włączonym {$NONLOCALGOTO}).

W językach, gdzi nie ma sformalizowanego zwracania wartości (asembler), to rozumiem. Ale jak w tym Free Pascalu wywołam funkcję zwracającą int (Integer), z której przeskoczę do funkcji, która zwraca Char, to co zostanie zwrócone? Jak to jest - z ciekawości - rozwiązane wtedy?

0
Manna5 napisał(a):

@Riddle

@furious programming: A co byś powiedział, żeby napisac dwie proste aplikacje. Jedną z goto i Twoim podejściem że errory są zwracane przez return-type'y, a drugą bez goto i z wyjątkami?

Żeby porównać podejścia?

I jak byś porównał potem te aplikacje? Załóżmy, że obie by działały - oczywiście. Można jeszcze zmierzyć wydajność, ale walorów takich jak wygoda wprowadzania zmian nie zmierzysz.

Czytając obie, potem próbując dodać jakąś zmianę do obu i zobaczyć commity z takimi zmianami.

@furious programming:

...a w niektórych nawet i poza daną funkcję (np. we Free Pascalu z włączonym {$NONLOCALGOTO}).

W językach, gdzi nie ma sformalizowanego zwracania wartości (asembler), to rozumiem. Ale jak w tym Free Pascalu wywołam funkcję zwracającą int (Integer), z której przeskoczę do funkcji, która zwraca Char, to co zostanie zwrócone? Jak to jest - z ciekawości - rozwiązane wtedy?

Widocznie nie wiesz jak działa goto skoro zadajesz takie głupie pytania.

W momencie w którym zawołasz goto to kontrola przepływu skacze do innego miejsca, nie zostaje nic zwrócone z funkcji, zmienna do której jest przypisany jej wynik pozostaje w takim samym stanie jak przed jej wywołaniem, dalszy flow idzie z innego miejsca.

0

To nie takie proste — pozostaje jeszcze kwestia stosu. Sam nie używam takich globalnych skoków, bo jakoś nie widzę dla nich praktycznego zastosowania (prócz robienia spaghetii), więc na to pytanie nie odpowiem. Ale podpytam o konkrety i dam znać.

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