Apa trik pemrograman C favorit Anda? [Tutup]

134

Misalnya, saya baru-baru ini menemukan ini di kernel linux:

/ * Paksa kesalahan kompilasi jika kondisinya benar * /
#menentukan BUILD_BUG_ON (kondisi) ((kosong) ukuran (char [1 - 2 * !! (kondisi)]))

Jadi, dalam kode Anda, jika Anda memiliki beberapa struktur yang harus, katakanlah kelipatan ukuran 8 byte, mungkin karena beberapa kendala perangkat keras, Anda dapat melakukan:

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

dan itu tidak akan dikompilasi kecuali ukuran struct mystruct adalah kelipatan 8, dan jika kelipatan 8, tidak ada kode runtime yang dihasilkan sama sekali.

Trik lain yang saya tahu adalah dari buku "Graphics Gems" yang memungkinkan satu file header untuk mendeklarasikan dan menginisialisasi variabel dalam satu modul sementara di modul lain menggunakan modul itu, cukup mendeklarasikannya sebagai eksternal.

#ifdef DEFINE_MYHEADER_GLOBALS
#define GLOBAL
# Tentukan INIT (x, y) (x) = (y)
#lain
#define GLOBAL extern
# Tentukan INIT (x, y)
#berakhir jika

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

Dengan itu, kode yang mendefinisikan x dan somefunc:

#tentukan DEFINE_MYHEADER_GLOBALS
#include "the_above_header_file.h"

sedangkan kode yang hanya menggunakan x dan somefunc () melakukan:

#include "the_above_header_file.h"

Jadi, Anda mendapatkan satu file header yang mendeklarasikan instance global dan prototipe fungsi jika diperlukan, dan deklarasi eksternal terkait.

Jadi, apa trik pemrograman C favorit Anda di sepanjang garis itu?

smcameron
sumber
9
Ini sepertinya lebih seperti trik praprosesor C.
jmucchiello
Tentang BUILD_BUG_ONmakro, apa salahnya menggunakan #errorinside dan #if?
Ricardo

Jawaban:

80

C99 menawarkan beberapa hal yang sangat keren menggunakan array anonim:

Menghapus variabel yang tidak berguna

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

menjadi

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

Meneruskan Jumlah Argumen Variabel

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

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

Daftar tertaut statis

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;
    }
}

Saya yakin banyak teknik keren lainnya yang belum saya pikirkan.

Evan Teran
sumber
2
Saya yakin contoh pertama Anda juga bisa ditulis &(int){1}, jika Anda ingin memperjelas maksud Anda di sini.
Lily Ballard
67

Saat membaca kode sumber Quake 2, saya menemukan sesuatu seperti ini:

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

(kurang lebih, saya tidak memiliki kode yang berguna untuk memeriksanya sekarang).

Sejak itu, dunia baru penggunaan kreatif preprocessor terbuka di depan mata saya. Saya tidak lagi hanya menyertakan header, tetapi seluruh potongan kode sekarang dan nanti (ini meningkatkan banyak kegunaan kembali) :-p

Terima kasih John Carmack! xD

fortran
sumber
13
Anda tidak dapat mengatakan carmack dalam utas pengoptimalan tanpa menyebutkan sqrt terbalik cepat yang ada di sumber gempa. en.wikipedia.org/wiki/Fast_inverse_square_root
pg1989
Dari mana dia mendapatkan 0x5f3759df?
RSH1
2
@RoryHarvey: Dari apa yang dapat saya temukan saat mencarinya, tampaknya itu murni empiris. Beberapa penelitian (saya tidak ingat di mana saya melihatnya) menunjukkan bahwa itu mendekati optimal, tetapi tidak sepenuhnya optimal. Demikian juga, tampaknya untuk 64bits nilainya ditemukan, bukan komputasi.
Matthieu M.
50

Saya suka menggunakan = {0};untuk menginisialisasi struktur tanpa perlu memanggil memset.

struct something X = {0};

Ini akan menginisialisasi semua anggota struct (atau array) ke nol (tapi bukan byte padding - gunakan memset jika Anda juga perlu nol).

Namun Anda harus menyadari bahwa ada beberapa masalah dengan ini untuk struktur besar yang dialokasikan secara dinamis .

John Carter
sumber
Ngomong-ngomong, tidak diperlukan untuk variabel global.
strager
5
Tidak diperlukan untuk variabel statis . Variabel global mungkin menjadi nol, tetapi itu bukan persyaratan.
Jamie
4
Saya terkadang memperluas ini menjadi: const struct something zero_something = { 0 };dan kemudian saya dapat mengatur ulang variabel dengan cepat struct something X = zero_something;atau sebagian melalui rutinitas. Saya dapat menggunakan 'X = zero_something;'. Keberatan satu-satunya yang mungkin adalah bahwa ini melibatkan membaca data dari suatu tempat; akhir-akhir ini, 'memset ()' mungkin lebih cepat - tetapi saya suka kejelasan tugas, dan juga memungkinkan untuk menggunakan nilai selain nol di penginisialisasi juga (dan memset () diikuti dengan penyesuaian ke anggota individu mungkin lebih lambat dari salinan biasa).
Jonathan Leffler
45

Jika kita berbicara tentang trik c favorit saya harus Perangkat Duff untuk membuka gulungan! Saya hanya menunggu kesempatan yang tepat untuk datang bersama saya untuk benar-benar menggunakannya dalam kemarahan ...

Jackson
sumber
4
Saya telah menggunakannya sekali untuk menghasilkan peningkatan kinerja yang terukur, tetapi belakangan ini tidak berguna pada banyak perangkat keras. Selalu profil!
Dan Olson
6
Ya, jenis orang yang tidak memahami konteks perangkat Duff dibuat: "keterbacaan kode" tidak berguna jika kode tidak cukup cepat untuk bekerja. Mungkin tidak ada orang yang memberi suara negatif kepada Anda yang pernah membuat kode untuk waktu nyata.
Rob K
1
+1, Saya sebenarnya perlu menggunakan perangkat Duff beberapa kali. Pertama kali adalah loop yang pada dasarnya hanya menyalin hal-hal dan melakukan beberapa transformasi kecil di jalan. Itu jauh, jauh lebih cepat daripada memcpy () sederhana dalam arsitektur itu.
Makis
3
Kemarahan akan berasal dari kolega dan penerus Anda yang harus menjaga kode Anda setelah Anda.
Jonathan Leffler
1
Seperti yang saya katakan, saya masih menunggu kesempatan yang tepat - tetapi belum ada yang cukup mengganggu saya. Saya telah menulis C selama sekitar 25 tahun sekarang, saya pikir saya pertama kali menemukan perangkat Duff di awal 90-an dan saya belum menggunakannya. Seperti yang dikomentari orang lain, trik semacam ini semakin tidak berguna sekarang karena kompiler menjadi lebih baik dalam pengoptimalan semacam ini.
Jackson
42

menggunakan __FILE__dan __LINE__untuk debugging

#define WHERE fprintf(stderr,"[LOG]%s:%d\n",__FILE__,__LINE__);
Pierre
sumber
6
Pada beberapa kompiler, Anda juga mendapatkan FUNCTION .
JBRWilkinson
11
__FUNCTION__hanya alias untuk __func__, dan __func__di c99. Cukup berguna. __PRETTY_FUNCTION__di C (GCC) hanyalah alias lain untuk __func__, tetapi di C ++ ini akan memberi Anda tanda tangan fungsi lengkap.
sklnd
FILE menunjukkan jalur lengkap nama file jadi saya menggunakan nama dasar ( FILE )
Jeegar Patel
31

Di 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};
Jasper Bekkers
sumber
28

Setelah teman saya dan saya mendefinisikan ulang kembali untuk menemukan bug korupsi tumpukan yang rumit.

Sesuatu seperti:

#define return DoSomeStackCheckStuff, return
Andrew Barrett
sumber
4
Mudah-mudahan itu # terdefinisi di badan fungsi dan # undefine'd di akhir!
strager
Tidak terlalu suka itu - hal pertama yang terlintas di benak saya adalah bahwa DoSomeStackCheckStuff mengacaukan memori karena beberapa bug dan siapa pun yang membaca kode tidak menyadari redefinisi pengembalian dan bertanya-tanya apa yang sedang terjadi.
gilligan
8
@strager Tapi itu pada dasarnya akan membuatnya tidak berguna. Intinya adalah menambahkan beberapa penelusuran ke setiap panggilan fungsi. Jika tidak, Anda hanya perlu menambahkan panggilan ke DoSomeStackCheckStufffungsi yang ingin Anda lacak.
Clueless
1
@gilligan Saya rasa ini bukan jenis hal yang Anda biarkan diaktifkan sepanjang waktu; tampaknya cukup berguna untuk pekerjaan debugging satu kali.
sunetos
apakah itu benar-benar berhasil? :) Saya akan menulis #define return if((DoSomeStackCheckStuff) && 0) ; else return... sama gilanya saya kira!
Paolo Bonzini
22

Saya suka "struct hack" karena memiliki objek berukuran dinamis. Situs ini menjelaskannya dengan cukup baik juga (meskipun mereka mengacu pada versi C99 di mana Anda dapat menulis "str []" sebagai anggota terakhir dari sebuah struct). Anda bisa membuat string "objek" seperti ini:

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;

di sini, kita telah mengalokasikan struktur tipe X pada heap yang berukuran int (untuk len), ditambah panjang "hello world", ditambah 1 (karena str 1 termasuk dalam sizeof (X).

Biasanya berguna jika Anda ingin memiliki "header" tepat sebelum beberapa data panjang variabel di blok yang sama.

Evan Teran
sumber
Saya pribadi merasa lebih mudah untuk hanya malloc () dan realloc () sendiri dan menggunakan strlen () setiap kali saya perlu mencari panjangnya, tetapi jika Anda memerlukan program yang tidak pernah tahu panjang string dan kemungkinan akan perlu menemukannya banyak kali, ini mungkin jalan yang lebih baik.
Chris Lutz
4
"... versi C99 di mana Anda dapat menulis" str [] "" Saya telah melihat array berukuran nol dalam konteks seperti itu, seperti str [0]; cukup sering. Saya pikir itu C99. Saya tahu kompiler lama mengeluh tentang array berukuran nol.
smcameron
3
Saya suka yang ini juga, namun, Anda harus menggunakan sesuatu seperti malloc (offsetof (X, str) + numbytes) jika tidak, semuanya akan salah karena masalah padding dan alignment. Misalnya sizeof (struct X) mungkin 8, bukan 5.
Fozi
3
@Fozi: Saya sebenarnya tidak berpikir itu akan menjadi masalah. Karena versi ini memiliki str[1](tidak str[]) 1 byte str disertakan dalam file sizeof(struct X). Ini termasuk bantalan apa pun antara lendan str.
Evan Teran
2
@ Rusky: Bagaimana hal itu berdampak negatif pada sesuatu? Misalkan ada "padding" setelahnya str. OK, Ketika saya mengalokasikan sizeof(struct X) + 10Kemudian ini membuat strefektif 10 - sizeof(int)(atau lebih, karena kami mengatakan ada padding) besar. Ini overlay str dan padding apapun setelahnya. Satu-satunya cara agar ada perbedaan, adalah jika ada anggota yang kemudian strmerusak semuanya, anggota yang fleksibel harus menjadi yang terakhir. Padding apa pun di bagian akhir hanya akan menyebabkan terlalu banyak yang dialokasikan. Berikan contoh spesifik tentang bagaimana hal itu sebenarnya bisa salah.
Evan Teran
17

Kode berorientasi objek dengan C, dengan meniru kelas.

Cukup buat struct dan satu set fungsi yang mengarahkan pointer ke struct itu sebagai parameter pertama.

Brian R. Bondy
sumber
2
Apakah masih ada sesuatu di luar sana yang menerjemahkan C ++ menjadi C, seperti cfront dulu?
MarkJ
11
Ini bukanlah orientasi objek. Untuk OO dengan pewarisan, Anda perlu menambahkan beberapa jenis tabel fungsi virtual ke struct objek Anda, yang dapat diisi oleh "subclass". Ada banyak kerangka kerja gaya "C dengan kelas" setengah matang di luar sana untuk tujuan ini, tetapi saya sarankan untuk tidak melakukannya.
exDM69
Itu perlu dikatakan. 1 untuk itu.
Amit S
3
@ exDM69, orientasi objek adalah cara berpikir yang sama banyaknya dengan masalah seperti halnya paradigma pengkodean; Anda dapat melakukannya dengan sukses tanpa warisan. Saya melakukan ini pada beberapa proyek sebelum terjun penuh ke C ++.
Mark Ransom
16

Dari pada

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

Menggunakan

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

Menggunakan trik makro yang bodoh untuk membuat definisi rekaman lebih mudah dipertahankan.

#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;
EvilTeach
sumber
11

Untuk membuat variabel yang hanya-baca di semua modul kecuali yang dideklarasikan di:

// 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
Steve Melnikoff
sumber
Ini terasa berbahaya. Ini adalah deklarasi dan definisi yang tidak cocok. Saat mengompilasi Source2.c, kompilator mungkin menganggap itu MyVartidak berubah, bahkan di seluruh panggilan fungsi ke Source1.c. (Perhatikan bahwa ini, sebagai variabel const yang sebenarnya, berbeda dari satu pointer ke const. Dalam kasus terakhir, objek yang diarahkan ke mungkin masih dapat dimodifikasi melalui pointer yang berbeda.)
jilles
1
Ini tidak menghasilkan variabel yang hanya bisa dibaca di beberapa unit kompilasi. Ini menghasilkan perilaku tidak terdefinisi (lihat hal. 6.2.7.2 dari ISO 9899 dan juga hal. 6.7.3.5).
Ales Hakl
8

Pergeseran bit hanya ditentukan hingga jumlah pergeseran 31 (pada integer 32 bit) ..

Apa yang Anda lakukan jika Anda ingin memiliki shift yang dihitung yang perlu bekerja dengan nilai shift yang lebih tinggi juga? Berikut adalah bagaimana Theora vide-codec melakukannya:

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

Atau lebih mudah dibaca:

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

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

Melakukan tugas dengan cara yang ditunjukkan di atas jauh lebih cepat daripada menggunakan cabang seperti ini:

unsigned int shiftmystuff (unsigned int a, unsigned int v)
{
  if (v<=31)
    return a>>v;
  else
    return 0;
}
Nils Pipenbrinck
sumber
... dan gcc sebenarnya memasukkannya :) +1
Pos Tim
2
Di komputer saya, gcc-4.3.2 menghilangkan cabang di yang kedua dengan menggunakan instruksi cmov (langkah bersyarat)
Adam Rosenfield
3
"jauh lebih cepat daripada menggunakan cabang": perbedaannya adalah bahwa cabang tersebut benar untuk semua nilai v, sedangkan halfshifttriknya hanya menggandakan rentang yang diizinkan menjadi 63 pada arsitektur 32-bit, dan 127 pada arsitektur 64-bit.
Pascal Cuoq
8

Mendeklarasikan array penunjuk ke fungsi untuk mengimplementasikan mesin status hingga.

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

Keuntungan yang paling menyenangkan adalah sederhana untuk memaksa setiap stimulus / status untuk memeriksa semua jalur kode.

Dalam sistem tertanam, saya akan sering memetakan ISR untuk menunjuk ke tabel seperti itu dan merombaknya sesuai kebutuhan (di luar ISR).

Jamie
sumber
Salah satu teknik yang saya suka dengan ini adalah, jika Anda memiliki fungsi yang memerlukan inisialisasi, Anda menginisialisasi penunjuk dengan panggilan ke rutin inisialisasi. Saat itu berjalan, hal terakhir yang dilakukannya adalah mengganti penunjuk dengan penunjuk ke fungsi sebenarnya, lalu memanggil fungsi itu. Dengan begitu, penginisialisasi secara otomatis dipanggil saat pertama kali fungsi dipanggil, dan fungsi sebenarnya dipanggil setiap kali berikutnya.
TMN
7

"Trik" pra-prosesor bagus lainnya adalah menggunakan karakter "#" untuk mencetak ekspresi debugging. Sebagai contoh:

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

edit: kode di bawah ini hanya berfungsi pada C ++. Terima kasih untuk smcameron dan Evan Teran.

Ya, waktu kompilasi menegaskan selalu bagus. Itu juga bisa ditulis sebagai:

#define COMPILE_ASSERT(cond)\
     typedef char __compile_time_assert[ (cond) ? 0 : -1]
Gilad Naor
sumber
The COMPILE_ASSERT makro tidak dapat digunakan dua kali meskipun, karena mencemari namespace dengan typedef, dan 2 penggunaan mendapat: error: redefinisi typedef '__compile_time_assert'
smcameron
Apakah Anda benar-benar mencoba ini? Anda dapat "typedef foo;" sebanyak yang Anda suka. Begitulah cara Anda melakukan predeklarasi. Saya telah menggunakannya selama 2,5 tahun sekarang pada beberapa kompiler, baik gcc, VC dan kompiler untuk lingkungan tertanam, dan tidak pernah menemui kesulitan apapun.
Gilad Naor
Aku benci praprosesor C ... :(
hasen
1
Ya, saya mencobanya. Saya memotong dan menempelkan pesan kesalahan dari kompiler, yaitu gcc.
smcameron
1
@Gilad: legal di c ++ untuk memiliki typedef yang berlebihan, tetapi tidak di c.
Evan Teran
6

Saya tidak akan benar-benar menyebutnya sebagai trik favorit, karena saya tidak pernah menggunakannya, tetapi penyebutan Perangkat Duff mengingatkan saya pada artikel tentang penerapan Coroutine di C. Itu selalu membuat saya tertawa, tetapi saya yakin itu bisa berguna suatu saat.

Dan Olson
sumber
Saya sebenarnya telah menggunakan teknik ini dalam praktiknya untuk membuat kode penggerak urutan tergantung asynchronous I / O samar-samar dapat dibaca manusia. Perbedaan utama adalah bahwa saya tidak menyimpan status coroutine dalam staticvariabel tetapi mengalokasikan struct secara dinamis dan meneruskan pointer ke fungsi coroutine. Banyak makro membuat ini lebih enak. Ini tidak bagus tapi lebih baik daripada versi async / callback yang melompat ke mana-mana. Saya akan menggunakan utas hijau (melalui swapcontext()on * nixes) jika saya bisa.
pmdj
6
#if TESTMODE == 1    
    debug=1;
    while(0);     // Get attention
#endif

Sementara (0); tidak berpengaruh pada program, tetapi kompilator akan mengeluarkan peringatan tentang "ini tidak melakukan apa-apa", yang cukup untuk membuat saya melihat baris yang menyinggung dan kemudian melihat alasan sebenarnya saya ingin menarik perhatiannya.

gbarry
sumber
9
tidak bisakah kamu menggunakan #warning sebagai gantinya?
Stefano Borini
Ternyata, saya bisa. Ini tidak sepenuhnya standar, tetapi berfungsi di kompiler yang saya gunakan. Menariknya, kompilator tertanam menerjemahkan #define, sedangkan gcc tidak.
gbarry
6

Saya penggemar xor hacks:

Tukar 2 pointer tanpa pointer suhu ketiga:

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

Atau saya sangat suka daftar tertaut xor dengan hanya satu penunjuk. (http://en.wikipedia.org/wiki/XOR_linked_list)

Setiap node dalam daftar tertaut adalah Xor dari node sebelumnya dan node berikutnya. Untuk melintasi ke depan, alamat node ditemukan dengan cara berikut:

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

dll.

atau untuk melintasi mundur:

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;

dll.

Meskipun tidak terlalu berguna (Anda tidak dapat mulai melakukan traverse dari node arbitrer) menurut saya ini sangat keren.

hamiltop
sumber
5

Yang ini berasal dari buku 'Cukup tali untuk menembak diri sendiri di kaki':

Di header menyatakan

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

Dalam pernyataan pengujian tempat kode Anda misalnya:

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

Do / while membantu jika konten makro diperluas ke beberapa pernyataan.

Pernyataan hanya akan dicetak jika tanda '-D RELEASE' untuk kompilator tidak digunakan.

Anda kemudian bisa misalnya. berikan bendera ke makefile Anda, dll.

Tidak yakin bagaimana ini bekerja di windows tetapi di * nix bekerja dengan baik

Simon Walker
sumber
Anda mungkin ingin memperluas D (x) menjadi {} saat RELEASE didefinisikan, sehingga berfungsi dengan baik dengan pernyataan if. Sebaliknya "jika (a) D (x);" akan berkembang menjadi hanya "jika (a)" ketika Anda telah menentukan RELEASE. Itu akan memberi Anda beberapa bug bagus di RELEASE verison
MarkJ
3
@ MarkJ: TIDAK. Apa adanya, "if (a) D (x);" diperluas menjadi "if (a);" yang baik-baik saja. Jika Anda memiliki D (x) perluas ke {}, lalu "if (a) if (b) D (x); else foo ();" akan diperluas secara TIDAK BENAR menjadi "if (a) if (b) {}; else foo ();", menyebabkan "else foo ()" cocok dengan if yang kedua bukan if yang pertama.
Adam Rosenfield
Sejujurnya saya kebanyakan menggunakan makro ini untuk menguji pernyataan cetak, atau jika saya memiliki pernyataan bersyarat, saya akan menyertakan semuanya misalnya. D (jika (a) foo (););
Simon Walker
1
@AdamRosenfield: Menggunakan #define D(x) do { } while(0)sebagai gantinya menangani kasus itu (dan dapat diterapkan ke cabang yang menyisipkan xjuga untuk konsistensi)
rpetrich
3

Rusty sebenarnya menghasilkan seluruh rangkaian kondisional build di ccan , lihat modul build assert:

#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;
}

Ada banyak makro bermanfaat lainnya di tajuk sebenarnya, yang mudah ditempatkan.

Saya mencoba, dengan segenap kekuatan saya untuk menahan tarikan sisi gelap (dan penyalahgunaan preprocessor) dengan menggunakan sebagian besar fungsi inline, tetapi saya menikmati makro yang pintar dan berguna seperti yang Anda jelaskan.

Tim Post
sumber
Ya, saya baru-baru ini menemukan ccan, dan sedang mempertimbangkan untuk menyumbangkan beberapa kode, tetapi belum memahami "cara ccan". Terima kasih untuk tautannya, lebih banyak motivasi untuk melihat ccan, yang saya sangat berharap mendapat daya tarik.
smcameron
Saya tidak akan terlalu khawatir tentang 'ccan way' sampai lebih mapan ... sekarang ccan-lint sedang diusulkan sebagai proyek GSOC. Ini adalah kelompok kecil dan agak ramah .. dan tempat yang bagus untuk membuang potongan :)
Tim Post
BTW, saya melihat BuILD_ASSERT Rusty seperti makro dari kernel linux (tidak mengejutkan) tetapi tidak memiliki salah satu "nots" (atau poni, atau! 'S) dan memperhatikan itu, saya pikir contoh penggunaan makro yang saya posting adalah tidak benar. Seharusnya: "BUILD_BUG_ON ((sizeof (struct mystruct)% 8))"
smcameron
3

Dua buku sumber yang bagus untuk hal-hal semacam ini adalah The Practice of Programming dan Penulisan Kode Padat . Salah satunya (saya tidak ingat yang mana) mengatakan: Lebih suka enum untuk #define di mana Anda bisa, karena enum diperiksa oleh kompilator.

Yuval F
sumber
1
AFAIK, di C89 / 90 TIDAK ADA pemeriksaan ketik untuk enum. enum entah bagaimana lebih nyaman #defines.
cschol
Bagian bawah halaman 39, ED K&R ke-2. Setidaknya ada kesempatan untuk memeriksa.
Jonathan Watmough
3

Tidak spesifik untuk C, tapi saya selalu menyukai operator XOR. Satu hal keren yang dapat dilakukan adalah "swap tanpa nilai 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);

Hasil:

a = 1, b = 2

a = 2, b = 1

Karl
sumber
a = 1; b = 2; a = a + b; b = ab; a = ab; memberikan hasil yang sama juga
Grambot
Ini juga akan menukar a dan b: a ^ = b ^ = a ^ = b;
vikhyat
@TheCapn: penambahan mungkin melimpah.
Michael Foukarakis
2

Saya suka konsep yang container_ofdigunakan misalnya dalam daftar. Pada dasarnya, Anda tidak perlu menentukan nextdan lastkolom untuk setiap struktur yang akan ada di daftar. Sebagai gantinya, Anda menambahkan header struktur daftar ke item tertaut yang sebenarnya.

Lihat include/linux/list.hcontoh kehidupan nyata.

Viliam
sumber
1

Saya pikir penggunaan pointer userdata cukup rapi. Mode kehilangan pijakan sekarang. Ini bukan fitur C tetapi cukup mudah digunakan di C.

epatel
sumber
1
Saya berharap saya mengerti apa yang Anda maksud di sini. Bisakah Anda menjelaskan lebih lanjut? Apa itu penunjuk data pengguna?
Zan Lynx
1
Silakan
ini terutama untuk panggilan balik. Ini adalah beberapa data yang ingin Anda berikan kembali kepada Anda setiap kali callback diaktifkan. Sangat berguna untuk meneruskan C ++ pointer ini ke callback sehingga Anda bisa mengikat objek ke acara.
Evan Teran
Ah iya. Terima kasih. Saya sering menggunakan ini, tetapi saya tidak pernah menyebutnya begitu.
Zan Lynx
1

Saya menggunakan X-Macros untuk membiarkan pra-kompilator menghasilkan kode. Mereka sangat berguna untuk mendefinisikan nilai kesalahan dan string kesalahan terkait di satu tempat, tetapi mereka bisa lebih dari itu.

JayG
sumber
1

Basis kode kami memiliki trik yang mirip dengan

#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
}

yang memungkinkan untuk melacak kebocoran memori dalam mode debug. Saya selalu berpikir ini keren.

jdizzle
sumber
1

Bersenang-senang dengan makro:

#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
     }
}
sanjoyd
sumber
0

Berikut adalah contoh bagaimana membuat kode C sama sekali tidak menyadari tentang apa yang sebenarnya digunakan HW untuk menjalankan aplikasi. Main.c melakukan pengaturan dan kemudian lapisan gratis dapat diimplementasikan pada kompiler / arch manapun. Menurut saya cukup rapi untuk mengabstraksi sedikit kode C, jadi tidak terlalu spesifik.

Menambahkan contoh kompilasi lengkap di sini.

/* 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;
}
eaanon01
sumber
4
Peduli untuk menguraikan, mungkin menjelaskan penggunaan praktis?
Leonardo Herrera
Sebagai contoh jika saya harus menulis program pengujian menggunakan beberapa antarmuka HW yang menghasilkan interupsi pada akhirnya. Kemudian modul ini dapat diatur untuk menjalankan fungsi di luar lingkup normal sebagai penangan sinyal / interupsi.
eaanon01
0
if(---------)  
printf("hello");  
else   
printf("hi");

Isi bagian yang kosong sehingga hello maupun hi tidak akan muncul di keluaran.
ans:fclose(stdout)

justgo
sumber
Anda dapat memformat kode dengan {}tombol toolbar (Saya telah melakukannya untuk Anda). Tombol "Kutip" tidak mempertahankan spasi atau menerapkan penyorotan sintaks.
Álvaro González