Wiem, że za wszystkimi implementacjami kompilatorów C stoi standard, więc nie powinno być żadnych ukrytych funkcji. Mimo to jestem pewien, że wszyscy programiści C mają ukryte / tajne sztuczki, których używają cały czas.
c
hidden-features
bernardn
źródło
źródło
Odpowiedzi:
Wskaźniki funkcji. Możesz użyć tabeli wskaźników funkcji do implementacji, np. Szybkich interpreterów kodu z pośrednią wątkami (FORTH) lub dyspozytorów kodu bajtowego, lub do symulacji metod wirtualnych podobnych do obiektów obiektowych.
Następnie w bibliotece standardowej znajdują się ukryte perełki, takie jak qsort (), bsearch (), strpbrk (), strcspn () [dwa ostatnie są przydatne do implementacji zamiany strtok ()].
Nieprawidłowością języka C jest to, że przepełnienie arytmetyczne ze znakiem jest niezdefiniowanym zachowaniem (UB). Więc ilekroć zobaczysz wyrażenie, takie jak x + y, oba są podpisanymi intami, może to potencjalnie spowodować przepełnienie i spowodować UB.
źródło
Bardziej sztuczka kompilatora GCC, ale możesz dać kompilatorowi wskazówki dotyczące rozgałęzienia (powszechne w jądrze Linuksa)
zobacz: http://kerneltrap.org/node/4705
Podoba mi się to, że dodaje też wyrazistości niektórym funkcjom.
źródło
Są to opcjonalne elementy w standardzie, ale muszą to być cechy ukryte, ponieważ ludzie ciągle je przedefiniowują. Jedna baza kodu, nad którą pracowałem (i nadal robię) ma wiele redefinicji, wszystkie z różnymi identyfikatorami. W większości przypadków jest to z makrami preprocesora:
I tak dalej. To sprawia, że chcę wyrywać sobie włosy. Po prostu użyj cholernych standardowych typów całkowitych!
źródło
Operator przecinka nie jest powszechnie używany. Z pewnością można go nadużywać, ale może też być bardzo przydatne. To zastosowanie jest najbardziej powszechne:
Ale możesz użyć tego operatora wszędzie. Przestrzegać:
Każda instrukcja jest oceniana, ale wartością wyrażenia będzie wartość ostatniej ocenianej instrukcji.
źródło
inicjalizacja struktury do zera
spowoduje to wyzerowanie wszystkich elementów konstrukcji.
źródło
memset
/calloc
do "wszystkie bajty zero" (tj. Fizyczne zera), co rzeczywiście nie jest zdefiniowane dla wszystkich typów.{ 0 }
gwarantuje, że wszystko zostanie zintilizowane odpowiednimi logicznymi wartościami zerowymi. Na przykład wskaźniki są gwarantowane, aby uzyskać ich prawidłowe wartości null, nawet jeśli wartość null na danej platformie to0xBAADFOOD
.memset
robi (0
jako drugi argument). Logiczne zero uzyskuje się po zainicjowaniu / przypisaniu0
(lub{ 0 }
) do obiektu w kodzie źródłowym. Te dwa rodzaje zer niekoniecznie dają ten sam wynik. Jak w przykładzie ze wskaźnikiem. Kiedy robiszmemset
na wskaźniku, otrzymasz0x0000
wskaźnik. Ale kiedy przypiszesz0
do wskaźnika, otrzymasz zerową wartość wskaźnika , która na poziomie fizycznym może być0xBAADF00D
lub cokolwiek innego.double
. Zwykle jest realizowany zgodnie ze standardem IEEE-754, w którym zero logiczne i zero fizyczne są takie same. Jednak IEEE-754 nie jest wymagany przez język. Może się więc zdarzyć, że kiedy to zrobiszdouble d = 0;
(logiczne zero), fizycznie niektóre bity w pamięcid
nie będą równe zero.Stałe wieloznakowe:
To ustawia się
x
na0x41424344
(lub0x44434241
, w zależności od architektury).EDYCJA: Ta technika nie jest przenośna, zwłaszcza jeśli serializujesz int. Jednak tworzenie samodokumentujących się wyliczeń może być niezwykle przydatne. na przykład
To sprawia, że jest to znacznie prostsze, jeśli patrzysz na surowy zrzut pamięci i potrzebujesz określić wartość wyliczenia bez konieczności jej wyszukiwania.
źródło
Nigdy nie używałem pól bitowych, ale brzmią fajnie dla rzeczy o bardzo niskim poziomie.
Oznacza to, że
sizeof(cat)
może być tak mały, jaksizeof(char)
.Włączone komentarze Aarona i leppie , dzięki chłopaki.
źródło
C ma standard, ale nie wszystkie kompilatory C są w pełni zgodne (nie widziałem jeszcze żadnego w pełni zgodnego kompilatora C99!).
To powiedziawszy, sztuczki, które preferuję, są nieoczywiste i przenośne na różnych platformach, ponieważ opierają się na semantyce C. Zwykle dotyczą makr lub arytmetyki bitowej.
Na przykład: zamiana dwóch liczb całkowitych bez znaku bez użycia zmiennej tymczasowej:
lub „rozszerzające C” do reprezentowania maszyn skończonych, takich jak:
można to osiągnąć za pomocą następujących makr:
Ogólnie jednak nie lubię sztuczek, które są sprytne, ale sprawiają, że kod jest niepotrzebnie skomplikowany do odczytania (jak przykład wymiany) i uwielbiam te, które sprawiają, że kod jest jaśniejszy i bezpośrednio przekazuje intencję (jak przykład FSM) .
źródło
Struktury z przeplotem, takie jak Urządzenie Duffa :
źródło
Bardzo lubię wyznaczone inicjatory, dodane w C99 (i obsługiwane w gcc przez długi czas):
Inicjalizacja tablicy nie jest już zależna od pozycji. Jeśli zmienisz wartości FOO lub BAR, inicjalizacja tablicy automatycznie odpowiada ich nowej wartości.
źródło
C99 ma niesamowitą inicjalizację struktury w dowolnym porządku.
źródło
anonimowe struktury i tablice to moja ulubiona. (por. http://www.run.montefiore.ulg.ac.be/~martin/resources/kung-f00.html )
lub
może być nawet używany do tworzenia połączonych list ...
źródło
gcc ma wiele rozszerzeń języka C, które mi się podobają, które można znaleźć tutaj . Niektóre z moich ulubionych to atrybuty funkcji . Niezwykle przydatnym przykładem jest atrybut format. Można tego użyć, jeśli zdefiniujesz funkcję niestandardową, która przyjmuje ciąg formatu printf. Jeśli włączysz ten atrybut funkcji, gcc sprawdzi twoje argumenty, aby upewnić się, że łańcuch formatu i argumenty pasują do siebie i odpowiednio wygeneruje ostrzeżenia lub błędy.
źródło
(ukryta) funkcja, która "zszokowała" mnie, kiedy pierwszy raz zobaczyłem, dotyczy printf. ta funkcja umożliwia używanie zmiennych do formatowania samych specyfikatorów formatu. poszukaj kodu, zobaczysz lepiej:
znak * osiąga ten efekt.
źródło
Cóż ... Myślę, że jedną z mocnych stron języka C jest jego przenośność i standardowość, więc ilekroć znajdę jakąś "ukrytą sztuczkę" w implementacji, z której obecnie korzystam, staram się jej nie używać, ponieważ staram się zachować Kod C jako standardowy i przenośny, jak to możliwe.
źródło
Asercje w czasie kompilacji, jak już omówiono tutaj .
źródło
Stała konkatenacja ciągów
Byłem dość zaskoczony, że nie widziałem tego już w odpowiedziach, ponieważ wszystkie kompilatory, które znam, obsługują to, ale wielu programistów wydaje się to ignorować. Czasami jest to bardzo przydatne i nie tylko podczas pisania makr.
Przypadek użycia, który mam w moim obecnym kodzie: mam
#define PATH "/some/path/"
w pliku konfiguracyjnym (tak naprawdę jest to ustawiane przez makefile). Teraz chcę zbudować pełną ścieżkę, w tym nazwy plików, do otwierania zasobów. Po prostu trafia do:Zamiast okropnego, ale bardzo powszechnego:
Zauważ, że powszechne okropne rozwiązanie to:
źródło
Cóż, nigdy go nie używałem i nie jestem pewien, czy kiedykolwiek poleciłbym go komukolwiek, ale czuję, że to pytanie byłoby niepełne bez wzmianki o wspólnej sztuczce Simona Tathama .
źródło
Podczas inicjowania tablic lub wyliczeń można umieścić przecinek po ostatnim elemencie na liście inicjalizacyjnej. na przykład:
Zrobiono to tak, że jeśli generujesz kod automatycznie, nie musisz się martwić o usunięcie ostatniego przecinka.
źródło
Przypisanie struktury jest fajne. Wiele osób nie zdaje sobie sprawy, że struktury są również wartościami i można je przypisać, nie ma potrzeby ich używać
memcpy()
, gdy proste przypisanie .Na przykład rozważmy pewną wyimaginowaną bibliotekę graficzną 2D, która może zdefiniować typ reprezentujący (całkowitą) współrzędną ekranu:
Teraz robisz rzeczy, które mogą wyglądać „źle”, takie jak napisanie funkcji, która tworzy punkt zainicjowany z argumentów funkcji i zwraca go, na przykład:
Jest to bezpieczne, o ile (oczywiście) wartość zwracana jest kopiowana przez wartość przy użyciu przypisania struktury:
W ten sposób możesz napisać całkiem czysty i zorientowany obiektowo kod, wszystko w prostym standardzie C.
źródło
Dziwne indeksowanie wektorów:
źródło
Kompilatory C implementują jeden z kilku standardów. Jednak posiadanie standardu nie oznacza, że wszystkie aspekty języka są zdefiniowane. Urządzenie DuffaNa przykład jest ulubioną „ukrytą” funkcją, która stała się tak popularna, że współczesne kompilatory mają specjalny kod rozpoznający, aby zapewnić, że techniki optymalizacji nie zakłócą pożądanego efektu tego często używanego wzorca.
Ogólnie rzecz biorąc, ukryte funkcje lub sztuczki językowe są odradzane, ponieważ pracujesz na skraju dowolnego standardu C, którego używa twój kompilator. Wiele takich sztuczek nie działa od jednego kompilatora do drugiego i często tego rodzaju funkcje zawodzą z jednej wersji zestawu kompilatorów danego producenta do innej wersji.
Różne sztuczki, które złamały kod C, obejmują:
Inne problemy i problemy, które pojawiają się, gdy programiści przyjmują założenia dotyczące modeli wykonania, które są określone w większości standardów języka C jako zachowanie „zależne od kompilatora”.
źródło
Podczas korzystania z sscanf możesz użyć% n, aby dowiedzieć się, gdzie powinieneś kontynuować czytanie:
Najwyraźniej nie możesz dodać kolejnej odpowiedzi, więc dołączę tutaj drugą, możesz użyć „&&” i „||” jako warunkowe:
Ten kod wyświetli:
źródło
używanie INT (3) do ustawiania punktu przerwania w kodzie jest moim ulubionym przez cały czas
źródło
Moją ulubioną "ukrytą" cechą C jest użycie% nw printf do zapisu z powrotem na stosie. Zwykle printf zdejmuje wartości parametrów ze stosu na podstawie łańcucha formatującego, ale% n może je z powrotem zapisać.
Sprawdź sekcję 3.4.2 tutaj . Może prowadzić do wielu nieprzyjemnych luk w zabezpieczeniach.
źródło
Sprawdzanie założeń w czasie kompilacji przy użyciu wyliczeń: głupi przykład, ale może być naprawdę przydatne w bibliotekach ze stałymi konfigurowalnymi w czasie kompilacji.
źródło
#define CompilerAssert(exp) extern char _CompilerAssert[(exp)?1:-1]
)Gcc (c) ma kilka fajnych funkcji, które możesz włączyć, takie jak deklaracje funkcji zagnieżdżonych i postać a?: B operatora?:, Który zwraca a, jeśli a nie jest fałszem.
źródło
Ostatnio odkryłem 0 pól bitowych.
co da układ
zamiast bez: 0;
Pole o szerokości 0 mówi, że następujące pola bitowe powinny być ustawione na następnej atomowej encji (
char
)źródło
Makra argumentów zmiennych w stylu C99, aka
który byłby używany jak
Tutaj również używam operatora stringize i ciągłej konkatentacji ciągów, innych funkcji, które naprawdę lubię.
źródło
W niektórych przypadkach przydatne są również zmienne automatyczne o zmiennej wielkości. Zostały one dodane w nC99 i przez długi czas były obsługiwane w gcc.
Otrzymujesz bufor na stosie z miejscem na nagłówek protokołu o stałym rozmiarze oraz dane o zmiennym rozmiarze. Możesz uzyskać ten sam efekt za pomocą funkcji przydzielania (), ale ta składnia jest bardziej zwarta.
Musisz upewnić się, że extraPadding jest rozsądną wartością przed wywołaniem tej procedury, w przeciwnym razie zdmuchniesz stos. Przed wywołaniem malloc lub innej techniki alokacji pamięci musiałbyś sprawdzić argumenty, więc nie jest to naprawdę niezwykłe.
źródło