Jaka jest twoja ulubiona sztuczka programistyczna w C? [Zamknięte]


134

Na przykład niedawno natknąłem się na to w jądrze Linuksa:

/ * Wymuś błąd kompilacji, jeśli warunek jest prawdziwy * /
# zdefiniować BUILD_BUG_ON (warunek) ((void) sizeof (char [1 - 2 * !! (warunek)]))

Tak więc w swoim kodzie, jeśli masz jakąś strukturę, która musi mieć, powiedzmy, wielokrotność 8 bajtów, być może z powodu pewnych ograniczeń sprzętowych, możesz zrobić:

BUILD_BUG_ON ((sizeof (struct mystruct)% 8)! = 0);

i nie skompiluje się, chyba że rozmiar struct mystruct jest wielokrotnością 8, a jeśli jest wielokrotnością 8, nie jest generowany żaden kod wykonawczy.

Inna sztuczka, którą znam, pochodzi z książki „Graphics Gems”, która pozwala pojedynczemu plikowi nagłówkowemu zarówno deklarować, jak i inicjować zmienne w jednym module, podczas gdy w innych modułach używających tego modułu, po prostu deklaruje je jako zewnętrzne.

#ifdef DEFINE_MYHEADER_GLOBALS
#define GLOBAL
# zdefiniować INIT (x, y) (x) = (y)
#jeszcze
#define GLOBAL extern
# zdefiniować INIT (x, y)
#endif

GLOBAL int INIT (x, 0);
GLOBAL int somefunc (int a, int b);

Dzięki temu kod, który definiuje x i somefunc, robi:

# zdefiniować DEFINE_MYHEADER_GLOBALS
#include „the_above_header_file.h”

podczas gdy kod, który używa tylko x i somefunc (), robi:

#include „the_above_header_file.h”

Otrzymujesz więc jeden plik nagłówkowy, który deklaruje zarówno instancje zmiennych globalnych, jak i prototypy funkcji tam, gdzie są potrzebne, oraz odpowiadające im deklaracje extern.

Więc jakie są twoje ulubione sztuczki programowania w C w tym zakresie?


9
Wygląda to bardziej na sztuczki preprocesora C.
jmucchiello

Jeśli chodzi o BUILD_BUG_ONmakro, co jest złego w używaniu #errorwewnątrz i #if?
Ricardo

Odpowiedzi:


80

C99 oferuje naprawdę fajne rzeczy przy użyciu anonimowych tablic:

Usuwanie bezsensownych zmiennych

{
    int yes=1;
    setsockopt(yourSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
}

staje się

setsockopt(yourSocket, SOL_SOCKET, SO_REUSEADDR, (int[]){1}, sizeof(int));

Przekazywanie zmiennej ilości argumentów

void func(type* values) {
    while(*values) {
        x = *values++;
        /* do whatever with x */
    }
}

func((type[]){val1,val2,val3,val4,0});

Statyczne listy połączone

int main() {
    struct llist { int a; struct llist* next;};
    #define cons(x,y) (struct llist[]){{x,y}}
    struct llist *list=cons(1, cons(2, cons(3, cons(4, NULL))));
    struct llist *p = list;
    while(p != 0) {
        printf("%d\n", p->a);
        p = p->next;
    }
}

Jestem pewien, że jest wiele innych fajnych technik, o których nie pomyślałem.


2
Uważam, że twój pierwszy przykład można również zapisać jako &(int){1}, jeśli chcesz nieco jaśniej wyjaśnić, jaki jest twój zamiar.
Lily Ballard

67

Czytając kod źródłowy Quake 2 wymyśliłem coś takiego:

double normals[][] = {
  #include "normals.txt"
};

(mniej więcej, nie mam pod ręką kodu, żeby to sprawdzić).

Od tamtej pory na moich oczach otworzył się nowy świat twórczego wykorzystania preprocesora. Nie dołączam już tylko nagłówków, ale od czasu do czasu całe fragmenty kodu (znacznie poprawia to możliwość ponownego wykorzystania) :-p

Dzięki, John Carmack! xD


13
Nie można powiedzieć carmack w wątku optymalizacyjnym bez wspominania o szybkim odwrotnym sqrt, który znajdował się w źródle quake. en.wikipedia.org/wiki/Fast_inverse_square_root
pg1989

Skąd wziął 0x5f3759df w pierwszej kolejności?
RSH1

2
@RoryHarvey: Z tego, co mogłem znaleźć, patrząc na to, wydaje się, że było to czysto empiryczne. Niektóre badania (nie pamiętam, gdzie je widziałem) wykazały, że jest bliski optymalnego, ale nie w pełni optymalny. Podobnie wydaje się, że dla 64-bitów wartość została odkryta, a nie obliczona.
Matthieu M.,

50

Lubię używać = {0};do inicjowania struktur bez konieczności wywoływania memset.

struct something X = {0};

Spowoduje to zainicjowanie wszystkich członków struktury (lub tablicy) do zera (ale nie żadnych bajtów uzupełniających - użyj memset, jeśli chcesz wyzerować również te).

Ale powinieneś być świadomy, że istnieją pewne problemy z tym w przypadku dużych, dynamicznie alokowanych struktur .


Nawiasem mówiąc, niepotrzebne dla zmiennych globalnych.
strager

5
Niepotrzebne w przypadku zmiennych statycznych . Zmienne globalne mogą być wyzerowane, ale nie jest to wymagane.
Jamie

4
Czasami rozszerzam to na: const struct something zero_something = { 0 };a następnie mogę zresetować zmienną w locie z procedurą struct something X = zero_something;lub w jej trakcie mogę użyć „X = zero_something;”. Jedynym możliwym zarzutem jest to, że wiąże się to z odczytem danych skądś; obecnie 'memset ()' może być szybsze - ale podoba mi się przejrzystość przypisania, a także jest możliwe użycie wartości innych niż zero również w inicjatorze (i memset (), po których następują poprawki do poszczególnych członków może być wolniejsze niż zwykła kopia).
Jonathan Leffler

45

Jeśli mówimy o trikach C, moim ulubionym jest Urządzenie Duffa do rozwijania pętli! Po prostu czekam na odpowiednią okazję, aby przyjść i użyć jej w gniewie ...


4
Użyłem go kiedyś, aby uzyskać wymierny wzrost wydajności, ale obecnie nie jest to przydatne na wielu urządzeniach. Zawsze profiluj!
Dan Olson

6
Tak, ludzie, którzy nie rozumieją kontekstu, w którym stworzono urządzenie Duffa: „czytelność kodu” jest bezużyteczna, jeśli kod nie jest wystarczająco szybki, aby działać. Prawdopodobnie żadna z osób, które Cię nie zgodziły, nigdy nie musiała kodować w trudnym czasie rzeczywistym.
Rob K

1
+1, faktycznie musiałem kilka razy skorzystać z urządzenia Duffa. Za pierwszym razem była to pętla, która po prostu kopiowała rzeczy i dokonała po drodze niewielkiej transformacji. Było dużo, dużo szybsze niż prosta memcpy () w tej architekturze.
Makis

3
Złość będzie pochodzić od twoich kolegów i następców, którzy muszą utrzymywać twój kod po tobie.
Jonathan Leffler

1
Jak powiedziałem, wciąż czekam na odpowiednią okazję - ale jeszcze nikt mnie wystarczająco nie zdenerwował. Piszę C od około 25 lat, wydaje mi się, że po raz pierwszy zetknąłem się z urządzeniem Duffa na początku lat 90. i jeszcze nie musiałem go używać. Jak komentowali inni, ten rodzaj sztuczki jest coraz mniej przydatny, gdy kompilatory stają się coraz lepsze w tego rodzaju optymalizacji.
Jackson

42

używanie __FILE__i __LINE__do debugowania

#define WHERE fprintf(stderr,"[LOG]%s:%d\n",__FILE__,__LINE__);

6
W niektórych kompilatorach otrzymujesz również FUNKCJĘ .
JBRWilkinson

11
__FUNCTION__jest tylko aliasem dla __func__i __func__znajduje się w c99. Całkiem przydatne. __PRETTY_FUNCTION__w C (GCC) to tylko kolejny alias dla __func__, ale w C ++ dostaniesz pełną sygnaturę funkcji.
sklnd

FILE pokazuje pełną ścieżkę do nazwy pliku, więc używam basename ( PLIK )
Jeegar Patel

31

W C99

typedef struct{
    int value;
    int otherValue;
} s;

s test = {.value = 15, .otherValue = 16};

/* or */
int a[100] = {1,2,[50]=3,4,5,[23]=6,7};

28

Kiedyś mój kumpel i ja przedefiniowaliśmy powrót, aby znaleźć trudny błąd powodujący uszkodzenie stosu.

Coś jak:

#define return DoSomeStackCheckStuff, return

4
Miejmy nadzieję, że zostało to # zdefiniowane w treści funkcji i # undefine'd na końcu!
strager

Niezbyt to lubię - pierwszą rzeczą, która przychodzi mi do głowy, jest to, że DoSomeStackCheckStuff psuje pamięć z powodu jakiegoś błędu, a ktokolwiek czyta kod, nie jest świadomy przedefiniowania zwrotu i zastanawia się, co się dzieje / piekło /.
gilligan

8
@strager Ale to uczyniłoby go w zasadzie bezużytecznym. Chodzi o to, aby dodać śledzenie do każdego wywołania funkcji. W przeciwnym razie po prostu dodasz wywołanie do DoSomeStackCheckStufffunkcji, które chcesz śledzić.
Clueless

1
@gilligan Nie sądzę, żeby to był typ rzeczy, które zostawiasz włączone przez cały czas; wydaje się całkiem przydatny do jednorazowego debugowania.
sunetos

czy to naprawdę działa? :) Napisałbym #define return if((DoSomeStackCheckStuff) && 0) ; else return... chyba tak samo szalony!
Paolo Bonzini

22

Podoba mi się „struct hack” za obiekt o dynamicznych rozmiarach. Ta strona również dobrze to wyjaśnia (chociaż odnoszą się do wersji C99, w której można napisać „str []” jako ostatni element struktury). możesz utworzyć łańcuch „obiekt” w ten sposób:

struct X {
    int len;
    char str[1];
};

int n = strlen("hello world");
struct X *string = malloc(sizeof(struct X) + n);
strcpy(string->str, "hello world");
string->len = n;

tutaj przydzieliliśmy strukturę typu X na stercie, która jest wielkością int (dla len) plus długość „hello world” plus 1 (ponieważ str 1 jest zawarty w sizeof (X).

Jest to ogólnie przydatne, gdy chcesz mieć „nagłówek” tuż przed niektórymi danymi o zmiennej długości w tym samym bloku.


Osobiście łatwiej jest po prostu sam malloc () i realloc () i użyć strlen (), gdy potrzebuję znaleźć długość, ale jeśli potrzebujesz programu, który nigdy nie zna długości ciągu i prawdopodobnie będzie musiał go znaleźć wiele razy to prawdopodobnie lepsza droga.
Chris Lutz

4
"... wersja C99, w której można napisać" str [] "" Widziałem tablice o zerowej wielkości w takim kontekście, jak str [0]; całkiem często. Myślę, że to C99. Wiem, że starsze kompilatory narzekają jednak na tablice o zerowej wielkości.
smcameron

3
Ten też mi się podoba, jednak powinieneś użyć czegoś takiego jak malloc (offsetof (X, str) + numbytes), w przeciwnym razie coś pójdzie nie tak z powodu problemów z dopełnieniem i wyrównaniem. Np. Sizeof (struktura X) może wynosić 8, a nie 5.
Fozi

3
@Fozi: Właściwie nie sądzę, żeby to był problem. Ponieważ ta wersja ma str[1](nie str[]) 1 bajt str jest zawarty w pliku sizeof(struct X). Obejmuje to wszelkie dopełnienia między lena str.
Evan Teran,

2
@Rusky: Jaki miałoby to negatywny wpływ na cokolwiek? Załóżmy, że po pliku jest „dopełnienie” str. OK, kiedy przydzielam sizeof(struct X) + 10Następnie to strskutecznie 10 - sizeof(int)(lub więcej, ponieważ powiedzieliśmy, że jest wypełnienie) duże. To nakładki str i wszelkie wyściółki po nich. Jedynym sposobem, w jaki miałoby to jakąkolwiek różnicę, jest to, że gdyby istniał członek, po strktórym i tak wszystko zrywa, elastyczni członkowie muszą być ostatni. Każde dopełnienie na końcu może tylko spowodować przydzielenie zbyt dużej ilości danych. Podaj konkretny przykład tego, jak mogło się to nie udać.
Evan Teran

17

Kod zorientowany obiektowo w C, poprzez emulację klas.

Po prostu utwórz strukturę i zestaw funkcji, które przyjmą wskaźnik do tej struktury jako pierwszy parametr.


2
Czy jest jeszcze coś, co tłumaczy C ++ na C, tak jak kiedyś cfront?
MarkJ

11
To raczej nie jest orientacja obiektowa. W przypadku obiektów obiektowych z dziedziczeniem musisz dodać do struktury obiektu jakąś wirtualną tabelę funkcji, która może być przeciążona przez „podklasy”. Istnieje wiele niedopracowanych frameworków w stylu "C z klasami" do tego celu, ale polecam trzymać się tego z daleka.
exDM69

Trzeba to powiedzieć. +1 za to.
Amit S,

3
@ exDM69, orientacja obiektowa jest zarówno sposobem myślenia o problemie, jak i paradygmatem kodowania; możesz to zrobić z powodzeniem bez dziedziczenia. Zrobiłem to na kilku projektach, zanim wskoczyłem do C ++.
Mark Ransom,

16

Zamiast

printf("counter=%d\n",counter);

Posługiwać się

#define print_dec(var)  printf("%s=%d\n",#var,var);
print_dec(counter);

14

Używanie głupiej sztuczki z makrami, aby ułatwić zarządzanie definicjami rekordów.

#define COLUMNS(S,E) [(E) - (S) + 1]

typedef struct
{
    char studentNumber COLUMNS( 1,  9);
    char firstName     COLUMNS(10, 30);
    char lastName      COLUMNS(31, 51);

} StudentRecord;

11

Do tworzenia zmiennej, która jest tylko do odczytu we wszystkich modułach oprócz tego, który jest zadeklarowany w:

// Header1.h:

#ifndef SOURCE1_C
   extern const int MyVar;
#endif

// Source1.c:

#define SOURCE1_C
#include Header1.h // MyVar isn't seen in the header

int MyVar; // Declared in this file, and is writeable

// Source2.c

#include Header1.h // MyVar is seen as a constant, declared elsewhere

To niebezpieczne. Są to niezgodne deklaracje i definicje. Podczas kompilacji Source2.ckompilator może założyć, że MyVarnie zmieni się, nawet w wywołaniu funkcji Source1.c. (Zauważ, że to, jako rzeczywista zmienna const, różni się od wskaźnika do stałej. W tym drugim przypadku wskazywany obiekt może być nadal modyfikowany za pomocą innego wskaźnika.)
jilles

1
Nie tworzy to zmiennej, która jest tylko do odczytu w niektórych jednostkach kompilacji. Daje to niezdefiniowane zachowanie (patrz p. 6.2.7.2 normy ISO 9899, ​​a także s. 6.7.3.5).
Ales Hakl

8

Przesunięcia bitowe są definiowane tylko do wartości przesunięcia 31 (na 32-bitowej liczbie całkowitej).

Co robisz, jeśli chcesz mieć obliczoną zmianę, która musi pracować również z wyższymi wartościami przesunięcia? Oto, jak robi to kodek wideo Theora:

unsigned int shiftmystuff (unsigned int a, unsigned int v)
{
  return (a>>(v>>1))>>((v+1)>>1);
}

Lub znacznie bardziej czytelne:

unsigned int shiftmystuff (unsigned int a, unsigned int v)
{
  unsigned int halfshift = v>>1;
  unsigned int otherhalf = (v+1)>>1;

  return (a >> halfshift) >> otherhalf; 
}

Wykonanie zadania w sposób pokazany powyżej jest o wiele szybsze niż użycie takiej gałęzi:

unsigned int shiftmystuff (unsigned int a, unsigned int v)
{
  if (v<=31)
    return a>>v;
  else
    return 0;
}

... i gcc faktycznie to wstawia :) +1
Tim Post

2
Na moim komputerze gcc-4.3.2 pozbywa się gałęzi w drugiej za pomocą instrukcji cmov (ruch warunkowy)
Adam Rosenfield

3
„dużo szybciej niż używanie gałęzi”: różnica polega na tym, że gałąź jest poprawna dla wszystkich wartości v, podczas gdy halfshiftsztuczka podwaja dopuszczalny zakres do 63 w architekturze 32-bitowej i 127 w architekturze 64-bitowej.
Pascal Cuoq,

8

Deklarowanie tablicy wskaźników do funkcji implementujących maszyny skończone.

int (* fsm[])(void) = { ... }

Najbardziej przyjemną zaletą jest to, że łatwo jest wymusić na każdym bodźcu / stanie sprawdzenie wszystkich ścieżek kodu.

W systemie osadzonym często mapuję ISR, aby wskazywał na taką tabelę i poprawiałam ją w razie potrzeby (poza ISR).


Jedną z technik, które lubię w tym przypadku, jest to, że jeśli masz funkcję, która wymaga inicjalizacji, inicjalizujesz wskaźnik za pomocą wywołania procedury inicjalizacji. Kiedy to działa, ostatnią rzeczą, jaką robi, jest zastąpienie wskaźnika wskaźnikiem do rzeczywistej funkcji, a następnie wywołanie tej funkcji. W ten sposób inicjalizator jest wywoływany automatycznie przy pierwszym wywołaniu funkcji, a funkcja rzeczywista jest wywoływana za każdym kolejnym razem.
TMN

7

Inną fajną „sztuczką” preprocesora jest użycie znaku „#” do wypisania wyrażeń debugujących. Na przykład:

#define MY_ASSERT(cond) \
  do { \
    if( !(cond) ) { \
      printf("MY_ASSERT(%s) failed\n", #cond); \
      exit(-1); \
    } \
  } while( 0 )

edycja: poniższy kod działa tylko w C ++. Dzięki smcameron i Evan Teran.

Tak, asercja czasu kompilacji jest zawsze świetna. Można go również zapisać jako:

#define COMPILE_ASSERT(cond)\
     typedef char __compile_time_assert[ (cond) ? 0 : -1]

Makra COMPILE_ASSERT nie można jednak użyć dwukrotnie, ponieważ zanieczyszcza przestrzeń nazw typedef, a drugie użycie otrzymuje: error: redefinition of typedef '__compile_time_assert'
smcameron

Naprawdę tego próbowałeś? Możesz "typedef foo;" tyle razy, ile chcesz. Tak właśnie postępujesz z wcześniejszymi deklaracjami. Używam go od 2,5 roku na kilku kompilatorach, zarówno gcc, VC, jak i kompilatorze dla środowiska osadzonego i nigdy nie napotkałem żadnych trudności.
Gilad Naor

Nienawidzę preprocesora C ... :(
hasen

1
Tak, próbowałem. Wytnąłem i wkleiłem komunikat o błędzie z kompilatora, którym był gcc.
smcameron

1
@Gilad: w C ++ dozwolone jest posiadanie redundantnych czcionek, ale nie w c.
Evan Teran

6

Naprawdę nie nazwałbym tego ulubioną sztuczką, ponieważ nigdy jej nie użyłem, ale wzmianka o urządzeniu Duffa przypomniała mi o tym artykule o wdrażaniu Coroutines w C. Zawsze mnie chichocze, ale jestem pewien, że tak. przyda się kiedyś.


Właściwie zastosowałem tę technikę w praktyce, aby kod sterujący sekwencją zależnych asynchronicznych operacji we / wy był niejasno czytelny dla człowieka. Główną różnicą jest to, że nie przechowuję stanu coroutine w staticzmiennej, ale zamiast tego przydzielam strukturę dynamicznie i przekazuję do niej wskaźnik do funkcji coroutine. Kilka makr czyni to bardziej przyjemnym. To nie jest fajne, ale lepsze niż wersja async / callback, która przeskakuje wszędzie. Gdybym swapcontext()mógł, użyłbym zielonych wątków (przez * nixes).
pmdj

6
#if TESTMODE == 1    
    debug=1;
    while(0);     // Get attention
#endif

Podczas gdy (0); nie ma wpływu na program, ale kompilator wyświetli ostrzeżenie o tym, że „to nic nie robi”, co wystarczy, żebym spojrzał na nieprawidłową linię i zobaczył prawdziwy powód, dla którego chciałem zwrócić na nią uwagę.


9
nie mógłbyś zamiast tego użyć #warning?
Stefano Borini

Najwyraźniej mogłem. Nie jest to całkowicie standardowe, ale działało w kompilatorach, których używam. Co ciekawe, wbudowany kompilator przetłumaczył #define, podczas gdy gcc nie.
gbarry

6

Jestem fanem hacków xor:

Zamień 2 wskaźniki bez trzeciego wskaźnika temp:

int * a;
int * b;
a ^= b;
b ^= a;
a ^= b;

Lub bardzo podoba mi się lista połączona z xor z tylko jednym wskaźnikiem. (http://en.wikipedia.org/wiki/XOR_linked_list)

Każdy węzeł na połączonej liście jest Xor poprzedniego i następnego węzła. Aby przejść dalej, adresy węzłów znajdują się w następujący sposób:

LLNode * first = head;
LLNode * second = first.linked_nodes;
LLNode * third = second.linked_nodes ^ first;
LLNode * fourth = third.linked_nodes ^ second;

itp.

lub aby przejść wstecz:

LLNode * last = tail;
LLNode * second_to_last = last.linked_nodes;
LLNode * third_to_last = second_to_last.linked_nodes ^ last;
LLNode * fourth_to_last = third_to_last.linked_nodes ^ second_to_last;

itp.

Chociaż nie jest to strasznie przydatne (nie można rozpocząć przechodzenia z dowolnego węzła), uważam to za bardzo fajne.


5

Ten pochodzi z książki „Dość liny, by strzelić sobie w stopę”:

W nagłówku zadeklaruj

#ifndef RELEASE
#  define D(x) do { x; } while (0)
#else
#  define D(x)
#endif

W Twoim kodzie umieść instrukcje testujące, np .:

D(printf("Test statement\n"));

Do / while pomaga w przypadku, gdy zawartość makra rozszerza się na wiele instrukcji.

Instrukcja zostanie wydrukowana tylko wtedy, gdy flaga '-D RELEASE' dla kompilatora nie zostanie użyta.

Możesz wtedy np. przekazać flagę do pliku makefile itp.

Nie jestem pewien, jak to działa w systemie Windows, ale w * nix działa dobrze


Możesz chcieć rozwinąć D (x) do {}, gdy zdefiniowane jest RELEASE, aby dobrze współgrało z instrukcjami if. W przeciwnym razie "if (a) D (x);" rozwinie się tylko do "if (a)", gdy zdefiniujesz RELEASE. To da ci kilka fajnych błędów w wersji RELEASE
MarkJ

3
@MarkJ: NIE. Tak jest, "if (a) D (x);" rozwija się do "if (a);" co jest w porządku. Gdybyś miał D (x) rozwinąć do {}, to "if (a) if (b) D (x); else foo ();" rozwinąłby się NIEPRAWIDŁOWO do „if (a) if (b) {}; else foo ();”, powodując dopasowanie „else foo ()” do drugiego if zamiast pierwszego if.
Adam Rosenfield

Szczerze mówiąc, najczęściej używam tego makra do testowania instrukcji print, lub gdybym miał instrukcję warunkową, załączyłbym to wszystko, np. D (jeśli (a) foo (););
Simon Walker

1
@AdamRosenfield: Użycie #define D(x) do { } while(0)zamiast tego obsługuje ten przypadek (i może być zastosowane do gałęzi, która również wstawia xw celu zachowania spójności)
rpetrich

3

Rusty faktycznie wyprodukował cały zestaw warunków kompilacji w ccan , sprawdź moduł asercji kompilacji:

#include <stddef.h>
#include <ccan/build_assert/build_assert.h>

struct foo {
        char string[5];
        int x;
};

char *foo_string(struct foo *foo)
{
        // This trick requires that the string be first in the structure
        BUILD_ASSERT(offsetof(struct foo, string) == 0);
        return (char *)foo;
}

W samym nagłówku znajduje się wiele innych pomocnych makr, które można łatwo wstawić.

Z całych sił staram się oprzeć ciemnej stronie (i nadużyciom preprocesora), trzymając się głównie funkcji wbudowanych, ale lubię sprytne, przydatne makra, takie jak te, które opisałeś.


Tak, niedawno natknąłem się na ccan i rozważałem wniesienie jakiegoś kodu, ale jeszcze nie zwróciłem uwagi na „sposób ccan”. Jednak dzięki za link, więcej motywacji do przyjrzenia się ccan, co, mam nadzieję, zyska na popularności.
smcameron

Cóż, nie przejmowałbym się zbytnio „drogą ccan”, dopóki jej bardziej ugruntowana… w tej chwili ccan-lint jest proponowany jako projekt GSOC. Jest to mała i raczej przyjazna grupa… i świetne miejsce do zrzucania fragmentów :)
Tim Post

Przy okazji, zauważyłem, że BuILD_ASSERT Rusty'ego jest takie samo jak makro z jądra Linuksa (nic dziwnego), ale brakuje mu jednego z "nie" (lub grzywki, lub!) I zauważając, myślę, że moje przykładowe użycie makra, które opublikowałem, to niepoprawne. Powinno być: „BUILD_BUG_ON ((sizeof (struct mystruct)% 8))”
smcameron

3

Dwie dobre książki źródłowe do tego typu rzeczy to The Practice of Programming and Writing Solid Code . Jeden z nich (nie pamiętam, który) mówi: Preferuj enum do #define, gdzie możesz, ponieważ enum jest sprawdzane przez kompilator.


1
AFAIK, w C89 / 90 NIE ma sprawdzania typu dla wyliczeń. wyliczenia są po prostu wygodniejsze #defines.
cschol

U dołu strony 39, 2nd ED K&R. Jest przynajmniej możliwość sprawdzenia.
Jonathan Watmough,

3

Nie dotyczy C, ale zawsze lubiłem operator XOR. Jedną fajną rzeczą, jaką może zrobić, jest „zamiana bez wartości temp”:

int a = 1;
int b = 2;

printf("a = %d, b = %d\n", a, b);

a ^= b;
b ^= a;
a ^= b;

printf("a = %d, b = %d\n", a, b);

Wynik:

a = 1, b = 2

a = 2, b = 1


a = 1; b = 2; a = a + b; b = ab; a = ab; daje ten sam wynik
Grambot

Spowoduje to również zamianę a i b: a ^ = b ^ = a ^ = b;
vikhyat

@TheCapn: dodatek może się jednak przepełnić.
Michael Foukarakis


2

Podoba mi się koncepcja container_ofużywana na przykład w listach. Zasadniczo nie musisz określać nexti lastpól dla każdej struktury, która będzie na liście. Zamiast tego należy dołączyć nagłówek struktury listy do rzeczywistych połączonych elementów.

Spójrz na include/linux/list.hprzykłady z życia wzięte.


1

Myślę, że użycie wskaźników userdata jest całkiem fajne. Moda traci dziś na popularności. Nie jest to funkcja C, ale dość łatwa w użyciu w C.


1
Żałuję, że nie rozumiem, co masz na myśli. Czy mógłbyś wyjaśnić więcej? Co to jest wskaźnik danych użytkownika?
Zan Lynx


służy głównie do wywołań zwrotnych. Są to dane, które chciałbyś otrzymać z powrotem za każdym razem, gdy wywoływane jest wywołanie zwrotne. Szczególnie przydatne do przekazywania C ++ wskaźnika this do wywołania zwrotnego, dzięki czemu można powiązać obiekt ze zdarzeniem.
Evan Teran,

O tak. Dzięki. Często tego używam, ale nigdy tak tego nie nazwałem.
Zan Lynx

1

Używam X-Macros, aby umożliwić prekompilatorowi wygenerowanie kodu. Są szczególnie przydatne do definiowania wartości błędów i powiązanych ciągów błędów w jednym miejscu, ale mogą wykraczać daleko poza to.


1

Nasza baza kodów ma podobną sztuczkę do

#ifdef DEBUG

#define my_malloc(amt) my_malloc_debug(amt, __FILE__, __LINE__)
void * my_malloc_debug(int amt, char* file, int line)
#else
void * my_malloc(int amt)
#endif
{
    //remember file and line no. for this malloc in debug mode
}

co pozwala na śledzenie wycieków pamięci w trybie debugowania. Zawsze myślałem, że to fajne.


1

Zabawa z makrami:

#define SOME_ENUMS(F) \
    F(ZERO, zero) \
    F(ONE, one) \
    F(TWO, two)

/* Now define the constant values.  See how succinct this is. */

enum Constants {
#define DEFINE_ENUM(A, B) A,
    SOME_ENUMS(DEFINE_ENUMS)
#undef DEFINE_ENUM
};

/* Now a function to return the name of an enum: */

const char *ToString(int c) {
    switch (c) {
    default: return NULL; /* Or whatever. */
#define CASE_MACRO(A, B) case A: return #b;
     SOME_ENUMS(CASE_MACRO)
#undef CASE_MACRO
     }
}

0

Oto przykład, jak sprawić, aby kod C był całkowicie nieświadomy tego, co jest faktycznie używane przez sprzęt do uruchamiania aplikacji. Plik main.c wykonuje konfigurację, a następnie wolną warstwę można zaimplementować na dowolnym kompilatorze / arch. Myślę, że jest to całkiem fajne do trochę abstrakcji kodu C, więc nie musi być zbyt sprecyzowane.

Dodanie tutaj pełnego kompilowalnego przykładu.

/* free.h */
#ifndef _FREE_H_
#define _FREE_H_
#include <stdio.h>
#include <string.h>
typedef unsigned char ubyte;

typedef void (*F_ParameterlessFunction)() ;
typedef void (*F_CommandFunction)(ubyte byte) ;

void F_SetupLowerLayer (
F_ParameterlessFunction initRequest,
F_CommandFunction sending_command,
F_CommandFunction *receiving_command);
#endif

/* free.c */
static F_ParameterlessFunction Init_Lower_Layer = NULL;
static F_CommandFunction Send_Command = NULL;
static ubyte init = 0;
void recieve_value(ubyte my_input)
{
    if(init == 0)
    {
        Init_Lower_Layer();
        init = 1;
    }
    printf("Receiving 0x%02x\n",my_input);
    Send_Command(++my_input);
}

void F_SetupLowerLayer (
    F_ParameterlessFunction initRequest,
    F_CommandFunction sending_command,
    F_CommandFunction *receiving_command)
{
    Init_Lower_Layer = initRequest;
    Send_Command = sending_command;
    *receiving_command = &recieve_value;
}

/* main.c */
int my_hw_do_init()
{
    printf("Doing HW init\n");
    return 0;
}
int my_hw_do_sending(ubyte send_this)
{
    printf("doing HW sending 0x%02x\n",send_this);
    return 0;
}
F_CommandFunction my_hw_send_to_read = NULL;

int main (void)
{
    ubyte rx = 0x40;
    F_SetupLowerLayer(my_hw_do_init,my_hw_do_sending,&my_hw_send_to_read);

    my_hw_send_to_read(rx);
    getchar();
    return 0;
}

4
Chcesz to rozwinąć, może wyjaśnić praktyczne zastosowanie?
Leonardo Herrera

Jako przykład, jeśli muszę napisać program testowy przy użyciu interfejsu HW, który generuje przerwy w końcu. Następnie ten moduł może być skonfigurowany do wykonywania funkcji poza normalnym zakresem jako procedura obsługi sygnału / przerwania.
eaanon01

0
if(---------)  
printf("hello");  
else   
printf("hi");

Wypełnij puste pola, aby na wyjściu nie pojawiały się ani cześć, ani cześć.
ans:fclose(stdout)


możesz formatować kod za pomocą {}przycisku paska narzędzi (zrobiłem to za Ciebie). Przycisk „Cytuj” nie zachowuje białych znaków ani nie stosuje podświetlania składni.
Álvaro González
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.