Saya tahu ada standar di balik semua implementasi kompiler C, jadi seharusnya tidak ada fitur tersembunyi. Meskipun demikian, saya yakin semua pengembang C memiliki trik tersembunyi / rahasia yang mereka gunakan setiap saat.
Saya tahu ada standar di balik semua implementasi kompiler C, jadi seharusnya tidak ada fitur tersembunyi. Meskipun demikian, saya yakin semua pengembang C memiliki trik tersembunyi / rahasia yang mereka gunakan setiap saat.
Jawaban:
Pointer fungsi. Anda dapat menggunakan tabel pointer fungsi untuk mengimplementasikan, misalnya, penerjemah kode tidak langsung cepat (FORTH) atau dispatcher kode byte, atau untuk mensimulasikan metode virtual mirip OO.
Kemudian ada permata tersembunyi di perpustakaan standar, seperti qsort (), bsearch (), strpbrk (), strcspn () [dua yang terakhir berguna untuk mengimplementasikan penggantian strtok ()].
Kesalahan C adalah bahwa aritmatika yang ditandatangani melimpah adalah perilaku tidak terdefinisi (UB). Jadi, setiap kali Anda melihat ekspresi seperti x + y, keduanya sedang ditandatangani, mungkin berpotensi meluap dan menyebabkan UB.
Lebih banyak trik dari kompiler GCC, tetapi Anda dapat memberikan petunjuk indikasi cabang ke kompiler (umum di kernel Linux)
#define likely(x) __builtin_expect((x),1)
#define unlikely(x) __builtin_expect((x),0)
lihat: http://kerneltrap.org/node/4705
Yang saya sukai tentang ini adalah ia juga menambahkan ekspresi pada beberapa fungsi.
void foo(int arg)
{
if (unlikely(arg == 0)) {
do_this();
return;
}
do_that();
...
}
int8_t
int16_t
int32_t
uint8_t
uint16_t
uint32_t
Ini adalah item opsional dalam standar, tetapi harus merupakan fitur tersembunyi, karena orang-orang secara konstan mendefinisikannya. Satu basis kode yang telah saya kerjakan (dan masih dilakukan, untuk saat ini) memiliki beberapa definisi ulang, semua dengan pengidentifikasi yang berbeda. Sebagian besar waktu dengan makro preprosesor:
#define INT16 short
#define INT32 long
Dan seterusnya. Itu membuat saya ingin mencabut rambut saya. Cukup gunakan typedefs integer standar freaking!
Operator koma tidak banyak digunakan. Ini tentu saja dapat disalahgunakan, tetapi juga bisa sangat berguna. Penggunaan ini adalah yang paling umum:
for (int i=0; i<10; i++, doSomethingElse())
{
/* whatever */
}
Tetapi Anda dapat menggunakan operator ini di mana saja. Mengamati:
int j = (printf("Assigning variable j\n"), getValueFromSomewhere());
Setiap pernyataan dievaluasi, tetapi nilai ekspresi akan menjadi pernyataan terakhir yang dievaluasi.
menginisialisasi struktur ke nol
struct mystruct a = {0};
ini akan nol semua elemen struktur.
memset
/ calloc
lakukan "semua byte nol" (yaitu nol fisik), yang memang tidak didefinisikan untuk semua jenis. { 0 }
dijamin untuk mengintensifkan segalanya dengan nilai nol logis yang tepat . Pointer, misalnya, dijamin untuk mendapatkan nilai nol yang tepat, bahkan jika nilai nol pada platform yang diberikan adalah 0xBAADFOOD
.
memset
dilakukannya (dengan 0
argumen kedua). Anda mendapatkan nol logis ketika Anda menginisialisasi / menetapkan 0
(atau { 0 }
) ke objek dalam kode sumber. Kedua jenis nol ini tidak serta merta menghasilkan hasil yang sama. Seperti pada contoh dengan pointer. Ketika Anda melakukannya memset
pada pointer, Anda mendapatkan 0x0000
pointer. Tetapi ketika Anda menetapkan 0
ke pointer, Anda mendapatkan nilai pointer nol , yang mungkin pada tingkat fisik 0xBAADF00D
atau apa pun.
double
,. Biasanya ini diterapkan sesuai dengan standar IEEE-754, di mana nol logis dan nol fisik adalah sama. Tetapi IEEE-754 tidak diperlukan oleh bahasa. Jadi mungkin saja terjadi bahwa ketika Anda melakukannya double d = 0;
(nol logis), secara fisik beberapa bit dalam memori yang ditempati d
tidak akan menjadi nol.
Konstanta multi-karakter:
int x = 'ABCD';
Ini diatur x
ke 0x41424344
(atau 0x44434241
, tergantung pada arsitektur).
EDIT: Teknik ini tidak portabel, terutama jika Anda membuat serial int. Namun, dapat sangat berguna untuk membuat enum yang mendokumentasikan sendiri. misalnya
enum state {
stopped = 'STOP',
running = 'RUN!',
waiting = 'WAIT',
};
Ini membuatnya lebih sederhana jika Anda melihat dump memori mentah dan perlu menentukan nilai enum tanpa harus mencarinya.
Saya tidak pernah menggunakan bidang bit tetapi kedengarannya keren untuk hal-hal tingkat rendah.
struct cat {
unsigned int legs:3; // 3 bits for legs (0-4 fit in 3 bits)
unsigned int lives:4; // 4 bits for lives (0-9 fit in 4 bits)
// ...
};
cat make_cat()
{
cat kitty;
kitty.legs = 4;
kitty.lives = 9;
return kitty;
}
Ini berarti bahwa sizeof(cat)
bisa sekecil sizeof(char)
.
Komentar yang dimasukkan oleh Aaron dan leppie , terima kasih kawan.
C memiliki standar tetapi tidak semua kompiler C sepenuhnya memenuhi syarat (Saya belum melihat kompiler C99 yang sepenuhnya patuh!).
Yang mengatakan, trik yang saya sukai adalah mereka yang tidak jelas dan portabel di seluruh platform karena mereka bergantung pada semantik C. Mereka biasanya tentang makro atau bit aritmatika.
Misalnya: bertukar dua bilangan bulat tanpa tanda tangan tanpa menggunakan variabel sementara:
...
a ^= b ; b ^= a; a ^=b;
...
atau "extending C" untuk mewakili mesin negara hingga seperti:
FSM {
STATE(x) {
...
NEXTSTATE(y);
}
STATE(y) {
...
if (x == 0)
NEXTSTATE(y);
else
NEXTSTATE(x);
}
}
yang bisa dicapai dengan makro berikut:
#define FSM
#define STATE(x) s_##x :
#define NEXTSTATE(x) goto s_##x
Secara umum, saya tidak suka trik yang cerdik tetapi membuat kode tidak perlu rumit untuk dibaca (sebagai contoh swap) dan saya suka yang membuat kode lebih jelas dan langsung menyampaikan maksud (seperti contoh FSM) .
Struktur yang saling terkait seperti Perangkat Duff :
strncpy(to, from, count)
char *to, *from;
int count;
{
int n = (count + 7) / 8;
switch (count % 8) {
case 0: do { *to = *from++;
case 7: *to = *from++;
case 6: *to = *from++;
case 5: *to = *from++;
case 4: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
} while (--n > 0);
}
}
Saya sangat menyukai inisialisasi yang ditunjuk, ditambahkan dalam C99 (dan didukung dalam gcc untuk waktu yang lama):
#define FOO 16
#define BAR 3
myStructType_t myStuff[] = {
[FOO] = { foo1, foo2, foo3 },
[BAR] = { bar1, bar2, bar3 },
...
Inisialisasi array tidak lagi tergantung posisi. Jika Anda mengubah nilai FOO atau BAR, inisialisasi array akan secara otomatis sesuai dengan nilai baru mereka.
struktur dan susunan anonim adalah yang favorit saya. (cf. http://www.run.montefiore.ulg.ac.be/~martin/resources/kung-f00.html )
setsockopt(yourSocket, SOL_SOCKET, SO_REUSEADDR, (int[]){1}, sizeof(int));
atau
void myFunction(type* values) {
while(*values) x=*values++;
}
myFunction((type[]){val1,val2,val3,val4,0});
bahkan dapat digunakan untuk memulai daftar yang ditautkan ...
gcc memiliki sejumlah ekstensi ke bahasa C yang saya nikmati, yang dapat ditemukan di sini . Beberapa favorit saya adalah atribut fungsi . Salah satu contoh yang sangat berguna adalah atribut format. Ini dapat digunakan jika Anda mendefinisikan fungsi kustom yang mengambil string format printf. Jika Anda mengaktifkan atribut fungsi ini, gcc akan melakukan pemeriksaan pada argumen Anda untuk memastikan bahwa string format dan argumen Anda cocok dan akan menghasilkan peringatan atau kesalahan yang sesuai.
int my_printf (void *my_object, const char *my_format, ...)
__attribute__ ((format (printf, 2, 3)));
fitur (tersembunyi) yang "mengejutkan" saya ketika saya pertama kali melihat adalah tentang printf. fitur ini memungkinkan Anda untuk menggunakan variabel untuk memformat penentu format sendiri. mencari kode, Anda akan melihat lebih baik:
#include <stdio.h>
int main() {
int a = 3;
float b = 6.412355;
printf("%.*f\n",a,b);
return 0;
}
karakter * mencapai efek ini.
Yah ... Saya pikir salah satu poin kuat dari bahasa C adalah portabilitas dan standarnya, jadi setiap kali saya menemukan beberapa "trik tersembunyi" dalam implementasi yang saya gunakan saat ini, saya mencoba untuk tidak menggunakannya karena saya mencoba untuk menjaga saya Kode C sebagai standar dan portabel mungkin.
Menyusun pernyataan waktu, seperti yang sudah dibahas di sini .
//--- size of static_assertion array is negative if condition is not met
#define STATIC_ASSERT(condition) \
typedef struct { \
char static_assertion[condition ? 1 : -1]; \
} static_assertion_t
//--- ensure structure fits in
STATIC_ASSERT(sizeof(mystruct_t) <= 4096);
Rangkaian string yang konstan
Saya cukup terkejut tidak melihat semuanya sudah ada di jawaban, karena semua kompiler saya tahu mendukungnya, tetapi banyak programmer tampaknya mengabaikannya. Terkadang ini sangat berguna dan tidak hanya saat menulis makro.
Gunakan case yang saya miliki di kode saya saat ini: Saya punya #define PATH "/some/path/"
di file konfigurasi (benar-benar diselesaikan oleh makefile). Sekarang saya ingin membangun path lengkap termasuk nama file untuk membuka sumber daya. Ini hanya berlaku untuk:
fd = open(PATH "/file", flags);
Alih-alih yang mengerikan, tetapi sangat umum:
char buffer[256];
snprintf(buffer, 256, "%s/file", PATH);
fd = open(buffer, flags);
Perhatikan bahwa solusi mengerikan yang umum adalah:
Yah, saya tidak pernah menggunakannya, dan saya tidak yakin apakah saya akan merekomendasikan hal ini kepada siapa pun, tetapi saya merasa pertanyaan ini tidak akan lengkap tanpa menyebutkan trik rutinitas Simon Tatham .
Saat menginisialisasi array atau enum, Anda dapat menempatkan koma setelah item terakhir dalam daftar penginisialisasi. misalnya:
int x[] = { 1, 2, 3, };
enum foo { bar, baz, boom, };
Ini dilakukan agar jika Anda membuat kode secara otomatis, Anda tidak perlu khawatir menghilangkan koma terakhir.
Tugas struktur keren. Banyak orang tampaknya tidak menyadari bahwa struct adalah nilai juga, dan dapat ditetapkan, tidak perlu digunakan memcpy()
, ketika tugas sederhana berhasil.
Misalnya, pertimbangkan beberapa perpustakaan grafis 2D imajiner, mungkin menentukan jenis untuk mewakili koordinat layar (integer):
typedef struct {
int x;
int y;
} Point;
Sekarang, Anda melakukan hal-hal yang mungkin terlihat "salah", seperti menulis fungsi yang menciptakan titik yang diinisialisasi dari argumen fungsi, dan mengembalikannya, seperti:
Point point_new(int x, int y)
{
Point p;
p.x = x;
p.y = y;
return p;
}
Ini aman, selama (tentu saja) karena nilai kembali disalin oleh nilai menggunakan penugasan struct:
Point origin;
origin = point_new(0, 0);
Dengan cara ini Anda dapat menulis kode yang cukup bersih dan berorientasi objek-ish, semuanya dalam standar sederhana C.
Pengindeksan vektor aneh:
int v[100]; int index = 10;
/* v[index] it's the same thing as index[v] */
Kompiler C mengimplementasikan salah satu dari beberapa standar. Namun, memiliki standar tidak berarti bahwa semua aspek bahasa didefinisikan. Perangkat Duff , misalnya, adalah fitur 'tersembunyi' favorit yang telah menjadi sangat populer sehingga kompiler modern memiliki kode pengenalan tujuan khusus untuk memastikan bahwa teknik optimasi tidak mengalahkan efek yang diinginkan dari pola yang sering digunakan ini.
Secara umum, fitur tersembunyi atau trik bahasa tidak disarankan saat Anda menggunakan pisau cukur yang menggunakan standar C apa pun yang digunakan kompiler Anda. Banyak trik semacam itu tidak bekerja dari satu kompiler ke yang lain, dan seringkali fitur-fitur semacam ini akan gagal dari satu versi suite kompiler oleh pabrikan yang diberikan ke versi lain.
Berbagai trik yang melanggar kode C meliputi:
Masalah dan masalah lain yang muncul setiap kali programmer membuat asumsi tentang model eksekusi yang semuanya ditentukan dalam sebagian besar standar C sebagai perilaku 'tergantung kompiler'.
Saat menggunakan sscanf Anda dapat menggunakan% n untuk mencari tahu di mana Anda harus terus membaca:
sscanf ( string, "%d%n", &number, &length );
string += length;
Tampaknya, Anda tidak dapat menambahkan jawaban lain, jadi saya akan memasukkan jawaban kedua di sini, Anda dapat menggunakan "&&" dan "||" sebagai persyaratan:
#include <stdio.h>
#include <stdlib.h>
int main()
{
1 || puts("Hello\n");
0 || puts("Hi\n");
1 && puts("ROFL\n");
0 && puts("LOL\n");
exit( 0 );
}
Kode ini akan menampilkan:
Hai ROFL
menggunakan INT (3) untuk mengatur break point pada kode adalah favorit saya sepanjang masa
Fitur "tersembunyi" favorit saya dari C, adalah penggunaan% n di printf untuk menulis kembali ke stack. Biasanya printf memunculkan nilai parameter dari stack berdasarkan string format, tetapi% n dapat menuliskannya kembali.
Lihat bagian 3.4.2 di sini . Dapat menyebabkan banyak kerentanan buruk.
Pengecekan asumsi waktu-kompilasi menggunakan enum: Contoh bodoh, tetapi bisa sangat berguna untuk perpustakaan dengan konstanta yang dapat dikonfigurasikan waktu kompilasi.
#define D 1
#define DD 2
enum CompileTimeCheck
{
MAKE_SURE_DD_IS_TWICE_D = 1/(2*(D) == (DD)),
MAKE_SURE_DD_IS_POW2 = 1/((((DD) - 1) & (DD)) == 0)
};
#define CompilerAssert(exp) extern char _CompilerAssert[(exp)?1:-1]
)
Gcc (c) memiliki beberapa fitur menyenangkan yang dapat Anda aktifkan, seperti deklarasi fungsi bersarang, dan bentuk a?: B dari operator?:, Yang mengembalikan a jika a tidak salah.
Saya menemukan 0 bitfields baru-baru ini.
struct {
int a:3;
int b:2;
int :0;
int c:4;
int d:3;
};
yang akan memberikan tata letak
000aaabb 0ccccddd
bukannya tanpa: 0;
0000aaab bccccddd
Bidang 0 width memberitahu bahwa bitfield berikut harus ditetapkan pada entitas atom berikutnya ( char
)
Makro argumen variabel gaya C99, alias
#define ERR(name, fmt, ...) fprintf(stderr, "ERROR " #name ": " fmt "\n", \
__VAR_ARGS__)
yang akan digunakan seperti
ERR(errCantOpen, "File %s cannot be opened", filename);
Di sini saya juga menggunakan operator stringisasi dan string concatentation konstan, fitur lain yang sangat saya sukai.
Variabel ukuran variabel otomatis juga berguna dalam beberapa kasus. Ini ditambahkan i nC99 dan telah didukung dalam gcc untuk waktu yang lama.
void foo(uint32_t extraPadding) {
uint8_t commBuffer[sizeof(myProtocol_t) + extraPadding];
Anda berakhir dengan buffer pada stack dengan ruang untuk header protokol ukuran tetap plus data ukuran variabel. Anda bisa mendapatkan efek yang sama dengan Alokasi (), tetapi sintaks ini lebih kompak.
Anda harus memastikan extraPadding adalah nilai yang masuk akal sebelum memanggil rutin ini, atau Anda akhirnya menumpuknya. Anda harus memeriksa argumen sebelum menelepon malloc atau teknik alokasi memori lainnya, jadi ini tidak biasa.