Po kilku latach programowania w C ++ zaproponowano mi ostatnio pracę z kodowaniem w C, w polu embedded.
Pomijając pytanie, czy odrzucanie C ++ w polu embedded jest dobre czy złe, jest kilka funkcji / idiomów w C ++, których bardzo bym brakował. Żeby wymienić tylko kilka:
- Ogólne, bezpieczne dla typów struktury danych (przy użyciu szablonów).
- RAII. Zwłaszcza w funkcjach z wieloma punktami powrotu, np. Nie trzeba pamiętać o zwolnieniu muteksu w każdym punkcie powrotu.
- Destruktory w ogóle. Oznacza to, że raz napiszesz d'tor dla MyClass, a następnie jeśli instancja MyClass jest członkiem MyOtherClass, MyOtherClass nie musi jawnie deinicjalizować instancji MyClass - jej d'tor jest wywoływany automatycznie.
- Przestrzenie nazw.
Jakie są Twoje doświadczenia związane z przejściem z C ++ do C?
Jakie substytuty C znalazłeś dla swoich ulubionych funkcji / idiomów C ++? Czy odkryłeś jakieś funkcje C, które chciałbyś mieć C ++?
Odpowiedzi:
Pracując nad projektem osadzonym, próbowałem raz pracować w języku C i po prostu nie mogłem tego znieść. Było tak rozwlekłe, że trudno było cokolwiek przeczytać. Podobały mi się również napisane przeze mnie kontenery zoptymalizowane pod kątem osadzania, które musiały zmienić się w znacznie mniej bezpieczne i trudniejsze do naprawienia
#define
bloki.Kod, który w C ++ wyglądał następująco:
if(uart[0]->Send(pktQueue.Top(), sizeof(Packet))) pktQueue.Dequeue(1);
zamienia się w:
if(UART_uchar_SendBlock(uart[0], Queue_Packet_Top(pktQueue), sizeof(Packet))) Queue_Packet_Dequeue(pktQueue, 1);
co wielu ludzi prawdopodobnie powie, że jest w porządku, ale staje się śmieszne, jeśli musisz zrobić więcej niż kilka wywołań „metod” w jednej linii. Dwa wiersze C ++ zamieniłyby się w pięć C (ze względu na 80-znakowe ograniczenia długości wiersza). Oba generowałyby ten sam kod, więc nie obchodziło się to z procesorem docelowym!
Pewnego razu (w 1995) próbowałem napisać dużo C dla wieloprocesorowego programu do przetwarzania danych. Rodzaj, w którym każdy procesor ma własną pamięć i program. Kompilator dostarczony przez dostawcę był kompilatorem C (czymś w rodzaju pochodnej HighC), ich biblioteki były zamkniętymi źródłami, więc nie mogłem używać GCC do budowania, a ich API zostały zaprojektowane z myślą, że twoje programy będą przede wszystkim inicjalizacją / procesem / terminate, więc komunikacja między procesorami była w najlepszym przypadku elementarna.
Dostałem około miesiąca, zanim się poddałem, znalazłem kopię cfront i włamałem ją do plików makefile, abym mógł używać C ++. Cfront nawet nie obsługiwał szablonów, ale kod C ++ był dużo, dużo jaśniejszy.
Ogólne, bezpieczne dla typów struktury danych (przy użyciu szablonów).
Najbliższą rzeczą, jaką C ma do szablonów, jest zadeklarowanie pliku nagłówkowego z dużą ilością kodu, który wygląda następująco:
TYPE * Queue_##TYPE##_Top(Queue_##TYPE##* const this) { /* ... */ }
następnie pociągnij go za pomocą czegoś takiego:
#define TYPE Packet #include "Queue.h" #undef TYPE
Zauważ, że to nie zadziała dla typów złożonych (np. Bez kolejek
unsigned char
), chyba że zrobisztypedef
pierwszy.Aha, i pamiętaj, jeśli ten kod nie jest nigdzie używany, to nawet nie wiesz, czy jest poprawny składniowo.
EDYCJA: Jeszcze jedno: musisz ręcznie zarządzać tworzeniem kodu. Jeśli Twój kod „szablonu” nie obejmuje wszystkich funkcji wbudowanych, musisz mieć pewną kontrolę, aby upewnić się, że instancje są tworzone tylko raz, aby linker nie wypluł stosu błędów „wielu wystąpień Foo” .
Aby to zrobić, musisz umieścić elementy niewymienione w sekcji „implementacja” w pliku nagłówkowym:
#ifdef implementation_##TYPE /* Non-inlines, "static members", global definitions, etc. go here. */ #endif
Następnie w jednym miejscu, w całym kodzie na wariant szablonu , musisz:
#define TYPE Packet #define implementation_Packet #include "Queue.h" #undef TYPE
Ponadto ta sekcja implementacji musi znajdować się poza standardową
#ifndef
/#define
/#endif
litanią, ponieważ możesz dołączyć plik nagłówkowy szablonu do innego pliku nagłówkowego, ale musisz później utworzyć instancję w.c
pliku.Tak, szybko robi się brzydko. Dlatego większość programistów C nawet nie próbuje.
RAII.
Zwłaszcza w funkcjach z wieloma punktami powrotu, np. Nie trzeba pamiętać o zwolnieniu muteksu w każdym punkcie powrotu.
Cóż, zapomnij o swoim ładnym kodzie i przyzwyczaj się do tego, że wszystkie punkty powrotu (z wyjątkiem końca funkcji) są
goto
s:TYPE * Queue_##TYPE##_Top(Queue_##TYPE##* const this) { TYPE * result; Mutex_Lock(this->lock); if(this->head == this->tail) { result = 0; goto Queue_##TYPE##_Top_exit:; } /* Figure out `result` for real, then fall through to... */ Queue_##TYPE##_Top_exit: Mutex_Lock(this->lock); return result; }
Destruktory w ogóle.
Oznacza to, że raz napiszesz d'tor dla MyClass, a następnie jeśli instancja MyClass jest członkiem MyOtherClass, MyOtherClass nie musi jawnie deinicjalizować instancji MyClass - jej d'tor jest wywoływany automatycznie.
Konstrukcja obiektu musi być jawnie obsługiwana w ten sam sposób.
Przestrzenie nazw.
W rzeczywistości jest to proste do naprawienia: po prostu umieść przedrostek na każdym symbolu. Jest to główna przyczyna wzdęcia źródła, o którym mówiłem wcześniej (ponieważ klasy są niejawnymi przestrzeniami nazw). Ludzie z C żyją tak, cóż, od zawsze i prawdopodobnie nie zobaczą, o co chodzi.
YMMV
źródło
AT91C_BASE_PMC->PMC_MOR = (0x37 << 16) | BOARD_OSCOUNT | AT91C_CKGR_MOSCRCEN | AT91C_CKGR_MOSCXTEN | AT91C_CKGR_MOSCSEL;
BOARD_OSCOUNT
(jaka jest wartość limitu czasu oczekiwania na przełączenie zegara; wyczyść, co?) Jest w rzeczywistości#define
inboard.h
. W tej samej funkcji znajduje się również dużo kodu pętli spinowej kopiuj i wklej, który powinien zostać przekształcony w dwuwierszowy#define
(a kiedy to zrobiłem, zapisał kilka bajtów kodu i utworzył bardziej czytelną funkcją dzięki wyraźniejszym zestawom rejestrów i pętlom spinowym). Jednym z głównych powodów używania C jest to, że pozwala on na mikrozarządzanie i optymalizację wszystkiego, ale większość kodu, który widziałem, nie przeszkadza.Przeszedłem z C ++ na C z innego powodu (jakaś reakcja alergiczna;) i jest tylko kilka rzeczy, za którymi tęsknię i kilka rzeczy, które zyskałem. Jeśli będziesz trzymać się C99, jeśli możesz, istnieją konstrukcje, które w szczególności pozwalają ci programować całkiem przyjemnie i bezpiecznie
for
-scope zmienna może pomóc w zarządzaniu zasobami związanymi z zakresem , w szczególności w celu zapewnieniaunlock
muteksów lubfree
tablic, nawet przy wstępnych zwrotach funkcji__VA_ARGS__
makra mogą być używane do posiadania domyślnych argumentów funkcji i do rozwijania koduinline
funkcje i makra, które dobrze się łączą, zastępując (w pewnym sensie) przeciążone funkcjeźródło
for
lunet, wylądujesz na P99, gdzie możesz również rozejrzeć się po przykłady i opisy innych części.void DoSomething(unsigned char* buf, size_t bufSize) { unsigned char temp[bufSize]; ... }
) i inicjalizację struktury według nazw pól (np.struct Foo bar = { .field1 = 5, .field2 = 10 };
), te ostatnie, które chciałbym zobaczyć w C ++, szczególnie w przypadku obiektów innych niż POD (np.UART uart[2] = { UART(0x378), UART(0x278) };
) .Nie istnieje nic takiego jak STL dla C.
Dostępne są biblioteki, które zapewniają podobną funkcjonalność, ale nie są już wbudowane.
Myślę, że byłby to jeden z moich największych problemów ... Wiedzieć, za pomocą którego narzędzia mógłbym rozwiązać problem, ale nie mam narzędzi dostępnych w języku, którego muszę używać.
źródło
Różnica między C i C ++ polega na przewidywalności zachowania kodu.
Łatwiej jest przewidzieć z dużą dokładnością, co zrobi twój kod w C, w C ++ może być nieco trudniej wymyślić dokładną prognozę.
Przewidywalność w C zapewnia lepszą kontrolę nad tym, co robi twój kod, ale oznacza to również, że musisz robić więcej rzeczy.
W C ++ możesz napisać mniej kodu, aby zrobić to samo, ale (przynajmniej dla mnie) czasami mam problemy ze zrozumieniem, jak kod obiektowy jest ułożony w pamięci i jakie jest oczekiwane zachowanie.
źródło
-s
flagę do,gcc
aby uzyskać zrzut zestawu, wyszukuję funkcję, której dotyczy problem, i zaczynam czytać. To świetny sposób na poznanie dziwactw dowolnego języka kompilowanego.W mojej pracy - nawiasem mówiąc, osadzonej - nieustannie przełączam się między C i C ++.
Kiedy jestem w C, tęsknię za C ++:
szablony (w tym między innymi kontenery STL). Używam ich do takich rzeczy, jak specjalne liczniki, pule buforów itp. (Zbudowałem własną bibliotekę szablonów klas i szablonów funkcji, których używam w różnych projektach osadzonych)
bardzo wydajna biblioteka standardowa
destruktory, które oczywiście umożliwiają RAII (muteksy, wyłączanie przerwań, śledzenie itp.)
specyfikatory dostępu, aby lepiej wymusić, kto może używać (nie widzieć) czego
Używam dziedziczenia w większych projektach, a wbudowane w C ++ wsparcie dla tego jest znacznie czystsze i ładniejsze niż "hack" C polegający na osadzaniu klasy bazowej jako pierwszego elementu członkowskiego (nie wspominając o automatycznym wywoływaniu konstruktorów, list inicjujących itp. ), ale te wymienione powyżej to te, których najbardziej mi brakuje.
Ponadto prawdopodobnie tylko około jednej trzeciej osadzonych projektów C ++, w których pracuję, używa wyjątków, więc przyzwyczaiłem się do życia bez nich, więc nie tęsknię za nimi zbytnio, kiedy wracam do C.
Z drugiej strony, kiedy wracam do projektu w C ze znaczną liczbą programistów, istnieją całe klasy problemów C ++, które zwykłem wyjaśniać ludziom, a które odchodzą. Głównie problemy wynikają ze złożoności C ++ i ludzi, którzy myślą, że wiedzą, co się dzieje, ale tak naprawdę są w części „C z klasami” krzywej pewności C ++ .
Mając wybór, wolałbym używać C ++ w projekcie, ale tylko wtedy, gdy zespół jest dość solidny w języku. Oczywiście zakładając, że nie jest to projekt 8K μC, w którym i tak efektywnie piszę "C".
źródło
Kilka obserwacji
źródło
Prawie te same powody, dla których używam C ++ lub mieszanki C / C ++ zamiast czystego C. Mogę żyć bez przestrzeni nazw, ale używam ich cały czas, jeśli pozwala na to standard kodu. Powodem jest to, że w C ++ możesz napisać znacznie bardziej zwarty kod. Jest to dla mnie bardzo przydatne, piszę serwery w C ++, które mają tendencję do awarii. W tym momencie bardzo pomaga, jeśli kod, na który patrzysz, jest krótki i spójny. Weźmy na przykład pod uwagę następujący kod:
uint32_t ScoreList::FindHighScore( uint32_t p_PlayerId) { MutexLock lock(m_Lock); uint32_t highScore = 0; for(int i = 0; i < m_Players.Size(); i++) { Player& player = m_Players[i]; if(player.m_Score > highScore) highScore = player.m_Score; } return highScore; }
W C wygląda to tak:
uint32_t ScoreList_getHighScore( ScoreList* p_ScoreList) { uint32_t highScore = 0; Mutex_Lock(p_ScoreList->m_Lock); for(int i = 0; i < Array_GetSize(p_ScoreList->m_Players); i++) { Player* player = p_ScoreList->m_Players[i]; if(player->m_Score > highScore) highScore = player->m_Score; } Mutex_UnLock(p_ScoreList->m_Lock); return highScore; }
Nie ma wielkiej różnicy. Jeszcze jedna linia kodu, ale to się sumuje. Zwykle starasz się, aby był czysty i szczupły, ale czasami musisz zrobić coś bardziej złożonego. W takich sytuacjach cenisz sobie liczbę linii. Jeszcze jedna linia to jeszcze jedna rzecz, na którą należy zwrócić uwagę, próbując dowiedzieć się, dlaczego sieć rozgłoszeniowa nagle przestaje dostarczać wiadomości.
W każdym razie stwierdzam, że C ++ pozwala mi robić bardziej złożone rzeczy w bezpieczny sposób.
źródło
Myślę, że głównym problemem, dlaczego c ++ jest trudniejsze do zaakceptowania w środowisku osadzonym, jest brak inżynierów, którzy rozumieją, jak prawidłowo używać c ++.
Tak, to samo rozumowanie można zastosować również do C, ale na szczęście w C nie ma tylu pułapek, które mogą strzelić sobie w stopę. C ++ z drugiej strony, musisz wiedzieć, kiedy nie używać niektórych funkcji w C ++.
Podsumowując, lubię c ++. Używam tego w warstwie usług O / S, sterowniku, kodzie zarządzania itp. Ale jeśli Twój zespół nie ma w tym wystarczającego doświadczenia, będzie to trudne wyzwanie.
Miałem doświadczenie z oboma. Kiedy reszta zespołu nie była na to gotowa, była to totalna katastrofa. Z drugiej strony było to dobre doświadczenie.
źródło
tak! poznałem oba te języki i stwierdziłem, że C ++ jest bardziej przyjaznym językiem. Ułatwia dzięki większej liczbie funkcji. Lepiej jest powiedzieć, że C ++ jest nadzbiorem języka C, ponieważ zapewnia dodatkowe funkcje, takie jak polimorfizm, interitance, przeciążanie operatorów i funkcji, typy danych zdefiniowane przez użytkownika, które nie są tak naprawdę obsługiwane w C. Tysiąc linii kodu jest zredukowanych do kilku wierszy z pomoc programowania obiektowego to główny powód przejścia z C do C ++.
źródło