Historia o C++

#automotive to stan umysłu. Nie pracowałem wcześniej w automotive, szczerze mówiąc nie znam reguł tych autosarów, a do projektu trafiłem dość przypadkiem. Po prostu znam wiele reguł programowania i dobrych praktyk i okazuje się, że nawet daję radę. Automotive miałem za dział w którym naprawdę dba się o jakość kodu, no bo kurde, jednak ktoś kiedyś może umrzeć XD ale co się wydarzyło, czego się nauczyłem:

Robię przegląd kodu na finiszu cyklu wydawniczego (korp. przedreleasowe review). Jest sobie funkcja, jako parametr przyjmuje inta i widzę że ten int jest bezrefleksyjnie, bez żadnego sprawdzania rzutowany na na typ enum class. No i cyk, mamy to, tak się nie robi, z tego co wiem, standard nie opisuje co ma się zadziać gdy enum zawiera wartości 1 i 2, a my chcemy rzutować wartość 3 na ten enum - innymi słowy, używamy wartości spoza zakresu. Z tego co wiem, nazywa się to "undefined behavior" i unika się takich rzeczy, no bo teoretycznie każdy kompilator może dowolnie przetworzyć taką operację i nie ma pewności co z tego wyjdzie.

Poprawiam więc aby było po bożemu, ze sprawdzaniem wartości, odpalam testy i co? Nie przechodzą! Sprawdzam scenariusze testów, a tam na legalu, gość sumuje praktycznie wszystkie wartości tego enuma w zmiennej typu int i woła tą funkcję, co w efekcie jest właśnie jawnym gwałceniem tego, co napisałem xD

Okazuje się, że to nie jedyny przypadek, bo są inne testy, które robą podobną rzecz. Wychodzi na to, że możemy w praktyce pakować do jednej zmiennej co się da, bo każda zmienna enuma to inna flaga, a potem maskujemy ją i patrzymy w tej zmiennej jakie flagi są włączone. No wszystko fajnie, ale enum przecież nie do tego służy xD więc albo scenariusze są wadliwe, albo design. Napisałem więc do autora by spytać, co miał na myśli, a ten na to, że wszystko jest w porządku i tak ma być.


Otóż w automotive jest wzorzec zwany flag enum i dosłownie gwałci się enuma w ten sposób, chociaż moim zdaniem powinno się inaczej rozwiązywać tego typu zagadnienie. Niesamowita sprawa, mózg rozjebany. Poniżej wątek na stackoverflow, który prezentuje takie podejście xD

Jestem teraz ciekaw ile analizatorów kodu rozumie taką konstrukcję i ją przepuszcza. Ale też podejrzewam, że ten "undefined behavior" w takim razie jest martwy, bo co najmniej IAR czy inne kompilatory automotive, jasno wiedzą co z tym zrobić. Dlaczego więc GCC czy Clang miałby nie wiedzieć?


https://stackoverflow.com/questions/1448396/how-to-use-enums-as-flags-in-c


#cpp #c #programowanie #programista15k

Komentarze (13)

MostlyRenegade

@ZohanTSW ale że flagi po prostu? Toż to jest starsze niż języki wysokopoziomowe i używane jest praktycznie wszędzie.

W C# jest nawet specjalny atrybut [Flags] dla enumów.

ZohanTSW

@MostlyRenegade no cóż, jak używałem zmiennej jako flag to używałem normalnej zmiennej a nie typu enumeracyjnego. Można np też użyć struktury z polem bitowym. Jest sporo alternatyw i moim zdaniem używanie enuma w ten sposób nie jest najlepszym rozwiązaniem

MostlyRenegade

@ZohanTSW użycie enuma ma tę zaletę, że masz od razu ograniczenie zakresu i nie musisz posługiwać się magicznymi liczbami.

O UB też nie ma się co martwić, bo pod spodem enuma i tak masz inta. Konwersja jest więc bezproblemowa. Co najwyżej poleci exception jeśli będziesz próbował podstawić pod enuma wartość spoza zakresu. A to jest nawet lepsze niż podstawienie wartości spoza zakresu pod zwykłego inta, bo jak operujesz intem, to program nawet nie wie, że jest poza zakresem i operuje na śmieciach.

ZohanTSW

@MostlyRenegade czekaj czekaj


użycie enuma ma tę zaletę, że masz od razu ograniczenie zakresu


jak operujesz intem, to program nawet nie wie, że jest poza zakresem


No czegoś tu nie rozumiem. Nie widzę tego zabezpieczenia w enumie. To prawda, że zachowuje się jak int. Do bólu. Tutaj program również nie wie, że jest poza zakresem. Może jest jakaś przełączka kompilatora, nie wiem


https://godbolt.org/z/EK8Eh9jvj


Nie neguję tego, że rozwiązanie widocznie jest znane i stare jak świat, ale nie zmienia to faktu, że dziś jest nieeleganckie. Według mnie o niebo lepiej jest używać struktur z polami bitowymi bo przynajmniej nie łamią tej jednej reguły xd

MostlyRenegade

@ZohanTSW w przypadku flag pilnowanie zakresu nawet nie ma sensu, bo i tak testujesz konkretne flagi. Aczkolwiek lepiej jest użyć dedykowanych operatorów niż bezmyślnie dodawać inty, a potem testować maskami.


Nie wiem, jak to dokładnie wygląda w c++, ale w c# do flag najlepiej właśnie stosować enumy, bo masz do nich gotowe operatory i kompilator wie, że chodzi o zbiór wielu stanów, a nie tylko jednego.

ZohanTSW

@MostlyRenegade w c++ właśnie do takich rzeczy najlepiej się nadają imho struktury z polami bitowymi, czyli opisujesz konkretne bity w bajcie i możesz się do nich odwoływać. Do tego jest to w pełni kompatybilne z intami czy ile tych bitów potrzebujesz

baklazan

@ZohanTSW Enumy w C++ to w zasadzie niewiele więcej niż sterta stałych, niestety, enum class jest już trochę lepszy, ale jak chcesz w C++ coś zgwałcić to masz do tego zawsze narzędzia. Szczerze mówiąc to ja bym takich rzeczy nie wpuścił, bo to jest zło, ale nie przeorasz kodu który już istnieje i polega na takich trickach. Sprawdź tylko czy enum class bazuje na int (domyślnie), bo nie musi : P.

A Automotive to tylko działka, przyjmują tam wszystkich co znają odpowiednie technologie.


Ostatnio dużo się robi w Rust np : >

ZohanTSW

@baklazan wszystko prawda. Tak, ten enum class dziedziczył z uint32_t.

Co do Rusta, to niestety, programiści są, ale nie znajdziesz pracy poza crypto. Chyba że będzie jakiś biały kruk xd

baklazan

@ZohanTSW W takim razie pracuję w białym kruku xD

ZohanTSW

@baklazan gratulacje, mam nadzieję że ten język będzie się popularyzował

baklazan

@ZohanTSW Póki co mam mieszane uczucia: z jednej strony są rzeczy które zachwycają (np rustowe enumy), z drugiej strony bardzo ograniczone generyki (np. brak variadic'ów powoduje że nie zrobisz generyka przyjmującego więcej niż N typów). Do multum rzeczy musi być w makrach: makra są dużo zajebistsze niż w C++, ale musisz je klepać zamiast zajmować się generykami.

Do tego mam wrażenie, że jest zbyt wymagający jeśli chodzi o poprawność kodu: jako stary wyjadacz C++sa, wiem o wszystkich dziurach które mogą się wydarzyć, ale też wiem kiedy dane sytuacje są niemożliwe, więc mogę sobie odpuścić kolejną warstwę zabezpieczeń. W Rustcie musisz je mieć zawsze i używać ich explicite, co powoduje że mnożą się byty niepotrzebnie.


To są moje pierwsze wrażenia, więc pewnie za rok będę miał więcej mądrzejszych wniosków.

Catharsis

@ZohanTSW Zaproponuj aby przepisali to na Rusta xDD

9b743107-611e-470d-b94b-4ac40d99273b
ZohanTSW

@Catharsis zesrali by się w tym języku xD

Zaloguj się aby komentować