Fitur tersembunyi dari C

141

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.

bernardn
sumber
Akan lebih bagus jika Anda / seseorang mengedit "pertanyaan" untuk menunjukkan pilihan fitur tersembunyi terbaik, seperti dalam versi C # dan Perl dari pertanyaan ini.
Donal Fellows

Jawaban:

62

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.

zvrba
sumber
29
Tetapi jika mereka menetapkan perilaku overflow, itu akan membuatnya sangat lambat pada arsitektur di mana itu bukan perilaku normal. Overhead runtime yang sangat rendah selalu menjadi tujuan desain C, dan itu berarti bahwa banyak hal seperti ini tidak terdefinisi.
Mark Baker
9
Saya sangat sadar mengapa melimpah adalah UB. Ini masih keliru, karena standar setidaknya harus menyediakan rutinitas perpustakaan yang dapat menguji aritmatika meluap (dari semua operasi dasar) tanpa menyebabkan UB.
zvrba
2
@zvrba, "rutinitas pustaka yang dapat menguji limpahan aritmatika (dari semua operasi dasar)" jika Anda telah menambahkan ini, maka Anda akan mendapatkan hit kinerja yang signifikan untuk operasi aritmatika bilangan bulat apa pun. ===== Studi kasus Matlab secara khusus MENAMBAH fitur mengendalikan perilaku integer overflow untuk membungkus atau menjenuhkan. Dan itu juga melempar pengecualian setiap kali terjadi overflow ==> Kinerja operasi integer Matlab: SANGAT PERLAHAN. Kesimpulan saya sendiri: Saya pikir Matlab adalah studi kasus yang meyakinkan yang menunjukkan mengapa Anda tidak ingin memeriksa integer overflow.
Trevor Boyd Smith
15
Saya mengatakan bahwa standar seharusnya menyediakan dukungan pustaka untuk memeriksa limpahan aritmatika. Sekarang, bagaimana bisa rutinitas perpustakaan menimbulkan kinerja jika Anda tidak pernah menggunakannya?
zvrba
5
Negatif besar adalah bahwa GCC tidak memiliki bendera untuk menangkap kelebihan integer yang ditandatangani dan melempar pengecualian runtime. Meskipun ada flag x86 untuk mendeteksi kasus-kasus seperti itu, GCC tidak menggunakannya. Memiliki flag semacam itu akan memungkinkan aplikasi non-kinerja-kritis (terutama warisan) manfaat keamanan dengan minimal atau tanpa ulasan kode dan refactoring.
Andrew Keeton
116

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();
     ...
}
tonylo
sumber
2
Trik ini keren ... :) Terutama dengan makro yang Anda tentukan. :)
sundar - Reinstate Monica
77
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!

Ben Collins
sumber
3
Saya pikir mereka C99 atau lebih. Saya belum menemukan cara portabel untuk memastikan ini akan ada.
akauppi
3
Mereka adalah bagian opsional C99, tapi saya tahu tidak ada vendor kompiler yang tidak mengimplementasikan ini.
Ben Collins
10
stdint.h bukan opsional di C99, tetapi mengikuti standar C99 tampaknya adalah untuk beberapa vendor ( batuk Microsoft).
Ben Combee
5
@Pete, jika Anda ingin anal: (1) utas ini tidak ada hubungannya dengan produk Microsoft. (2) Utas ini tidak pernah ada hubungannya dengan C ++ sama sekali. (3) Tidak ada yang namanya C ++ 97.
Ben Collins
5
Lihatlah azillionmonkeys.com/qed/pstdint.h - stdint.h dekat-portabel
gnud
73

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.

Ben Collins
sumber
7
Dalam 20 tahun CI belum pernah melihatnya!
Martin Beckett
11
Di C ++ Anda bahkan bisa membebani itu berlebihan.
Wouter Lievens
6
bisa! = harus, tentu saja. Bahaya dari overloading adalah bahwa built in berlaku untuk semua yang sudah ada, termasuk batal, jadi tidak akan pernah gagal untuk mengkompilasi karena kurangnya kelebihan yang tersedia. Yaitu, memberi banyak tali programmer.
Aaron
Int di dalam loop tidak akan bekerja dengan C: ini adalah perbaikan C ++. Apakah "," operasi yang sama dengan untuk (i = 0, j = 10; i <j; j--, i ++)?
Aif
63

menginisialisasi struktur ke nol

struct mystruct a = {0};

ini akan nol semua elemen struktur.

mike511
sumber
2
Itu tidak nol padding, jika ada, namun.
Mikeage
2
@simonn, tidak, itu tidak melakukan perilaku yang tidak terdefinisi jika strukturnya mengandung tipe yang tidak terpisahkan. memset dengan 0 pada memori float / double akan tetap nol ketika Anda menginterpretasikan float / double (float / double dirancang seperti itu dengan sengaja).
Trevor Boyd Smith
6
@ Andrew: memset/ calloclakukan "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.
AnT
1
@ nvl: Anda mendapatkan nol fisik ketika Anda hanya dengan paksa mengatur semua memori yang ditempati oleh objek ke status semua-bit-nol. Inilah yang memsetdilakukannya (dengan 0argumen 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 memsetpada pointer, Anda mendapatkan 0x0000pointer. Tetapi ketika Anda menetapkan 0ke pointer, Anda mendapatkan nilai pointer nol , yang mungkin pada tingkat fisik 0xBAADF00Datau apa pun.
AnT
3
@ nvl: Ya, dalam praktiknya perbedaannya seringkali hanya konseptual. Namun secara teori, hampir semua jenis bisa memilikinya. Sebagai contoh 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 dtidak akan menjadi nol.
AnT
52

Konstanta multi-karakter:

int x = 'ABCD';

Ini diatur xke 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.

Ferruccio
sumber
Saya cukup yakin ini bukan konstruksi portabel. Hasil menciptakan konstanta multi-karakter ditentukan oleh implementasi.
Mark Bessey
8
Komentar "tidak portabel" sepenuhnya tidak tepat. Ini seperti mengkritik program untuk menggunakan INT_MAX hanya karena INT_MAX "tidak portabel" :) Fitur ini sama portabelnya dengan yang seharusnya. Konstanta multi-char adalah fitur yang sangat berguna yang menyediakan cara yang dapat dibaca untuk menghasilkan ID integer unik.
AnT
1
@ Chris Lutz - Saya cukup yakin koma tertinggal kembali ke K&R. Ini dijelaskan dalam edisi kedua (1988).
Ferruccio
1
@Ferruccio: Anda harus berpikir tentang koma tertinggal di daftar initailizer agregat. Adapun tanda koma dalam deklarasi enum - ini adalah tambahan baru-baru ini, C99.
AnT
3
Anda lupa 'HANG' atau 'BSOD' :-)
JBRWilkinson
44

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.

Motti
sumber
Kombinasi struct dan serikat pekerja bahkan lebih menarik - pada sistem tertanam atau kode driver tingkat rendah. Contohnya adalah ketika Anda ingin mem-parsing register kartu SD, Anda dapat membacanya menggunakan union (1) dan membacanya menggunakan union (2) yang merupakan struct dari bitfields.
ComSubVie
5
Bitfield tidak portabel - kompiler dapat memilih dengan bebas apakah, dalam contoh Anda, kaki akan dialokasikan 3 bit paling signifikan, atau 3 bit paling tidak signifikan.
zvrba
3
Bitfields adalah contoh di mana standar memberikan implementasi begitu banyak kebebasan dalam cara penerapannya, sehingga dalam praktiknya, mereka hampir tidak berguna. Jika Anda peduli berapa bit nilai yang digunakan, dan bagaimana itu disimpan, Anda lebih baik menggunakan bitmask.
Mark Bessey
26
Bitfield memang portabel selama Anda memperlakukannya sebagai elemen struktur, dan bukan "bilangan bulat." Ukuran, bukan lokasi, penting dalam sistem tertanam dengan memori terbatas, karena setiap bit sangat berharga ... tetapi sebagian besar coders saat ini terlalu muda untuk mengingatnya. :-)
Adam Liss
5
@ Adam: lokasi mungkin penting dalam sistem tertanam (atau di tempat lain), jika Anda bergantung pada posisi bitfield dalam byte-nya. Menggunakan topeng menghilangkan ambiguitas. Demikian pula untuk serikat pekerja.
Steve Melnikoff
37

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) .

Remo.D
sumber
18
C mendukung rantai, sehingga Anda dapat melakukan a ^ = b ^ = a ^ = b;
OJ.
4
Sebenarnya, contoh state adalah tanda centang pada preprocessor, dan bukan bahasa C - dimungkinkan untuk menggunakan yang pertama tanpa yang terakhir.
Greg Whitfield
15
OJ: sebenarnya yang Anda sarankan adalah perilaku yang tidak terdefinisi karena aturan titik urutan. Ini mungkin bekerja pada kebanyakan kompiler, tetapi tidak benar atau portabel.
Evan Teran
5
Xor swap sebenarnya bisa kurang efisien dalam hal register gratis. Pengoptimal yang layak akan membuat variabel temp menjadi register. Bergantung pada implementasi (dan kebutuhan untuk dukungan paralelisme) swap mungkin sebenarnya menggunakan memori nyata daripada register (yang akan sama).
Paul de Vrieze
27
tolong jangan pernah benar-benar melakukan ini: en.wikipedia.org/wiki/…
Christian Oudard
37

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);
    }
}
ComSubVie
sumber
29
@ComSubVie, siapa pun yang menggunakan Perangkat Duff adalah skrip kiddy yang melihat Perangkat Duff dan berpikir kode mereka akan terlihat 1337 jika mereka menggunakan Perangkat Duff. (1.) Perangkat Duff tidak menawarkan peningkatan kinerja pada prosesor modern karena prosesor modern memiliki zero-overhead-looping. Dengan kata lain itu adalah kode yang sudah usang (2.) Bahkan jika prosesor Anda tidak menawarkan zero-overhead-looping, mungkin akan ada sesuatu seperti pemrosesan SSE / altivec / vektor yang akan membuat Perangkat Duff Anda malu ketika Anda menggunakan memcpy (). (3.) Apakah saya menyebutkan bahwa melakukan memcpy () duff's tidak berguna?
Trevor Boyd Smith
2
@ComSubVie, tolong temui Fist-of-death saya ( en.wikipedia.org/wiki/… )
Trevor Boyd Smith
12
@ Trvor: jadi hanya skrip program kiddies 8051 dan PIC mikrokontroler, kan?
SF.
6
@Trevor Boyd Smith: Meskipun Perangkat Duff tampak ketinggalan zaman, itu masih merupakan keingintahuan historis, yang memvalidasi jawaban ComSubVie. Ngomong-ngomong, mengutip Wikipedia: "Ketika banyak contoh perangkat Duff dihapus dari Server XFree86 di versi 4.0, ada peningkatan yang signifikan dalam kinerja." ...
paercebal
2
Pada Symbian, kami pernah mengevaluasi berbagai loop untuk pengkodean piksel cepat; perangkat duff, di assembler, adalah yang tercepat. Jadi itu masih memiliki relevansi pada core ARM utama di smartphone Anda hari ini.
Will
33

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.

DGentry
sumber
Sintaks gcc telah mendukung untuk waktu yang lama tidak sama dengan sintaks C99 standar.
Mark Baker
28

C99 memiliki beberapa inisialisasi struktur pesanan mengagumkan.

struct foo{
  int x;
  int y;
  char* name;
};

void main(){
  struct foo f = { .y = 23, .name = "awesome", .x = -38 };
}

Jason
sumber
27

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 ...

PypeBros
sumber
3
Fitur ini biasanya disebut "majemuk literal". Struktur anonim (atau tidak disebutkan namanya) menunjuk struktur bersarang yang tidak memiliki nama anggota.
calandoa
menurut GCC saya, "ISO C90 melarang literal majemuk".
jmtd
"ISO C99 mendukung literal majemuk." "Sebagai ekstensi, GCC mendukung literal majemuk dalam mode C89 dan dalam C ++" (info gcc dixit). Plus, "Sebagai ekstensi GNU, GCC memungkinkan inisialisasi objek dengan durasi penyimpanan statis menurut literal majemuk (yang tidak mungkin dalam ISO C99, karena initializer tidak konstan)."
PypeBros
24

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)));
Russell Bryant
sumber
24

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.

kolistivra
sumber
24

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.

Giacomo Degli Esposti
sumber
Namun pada kenyataannya, seberapa sering Anda harus mengkompilasi kode Anda dengan kompiler lain?
Joe D
3
@ Jo D jika ini adalah proyek lintas platform seperti Windows / OSX / Linux, mungkin sedikit, dan juga ada lengkungan berbeda seperti x86 vs x86_64 dan lain-lain ...
Pharaun
@ Joey Kecuali jika Anda berada dalam proyek yang sangat sempit yang senang menikah dengan satu vendor kompiler, sangat. Anda mungkin ingin menghindari benar-benar harus mengganti kompiler, tetapi Anda ingin tetap membuka opsi itu. Dengan sistem tertanam, Anda tidak selalu mendapatkan pilihan. AHS, ASS.
XTL
19

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);
filant
sumber
16

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:

  • tiga kali lebih lama
  • jauh lebih mudah dibaca
  • jauh lebih lambat
  • kurang kuat dalam menetapkan batas ukuran buffer sewenang-wenang (tetapi Anda harus menggunakan kode lebih lama untuk menghindari itu tanpa rangkaian string konstan).
  • gunakan lebih banyak ruang tumpukan
Kriss
sumber
1
Juga berguna untuk membagi konstanta string pada beberapa baris sumber tanpa menggunakan `` `yang kotor.
dolmen
15

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 .

Mark Baker
sumber
12

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.

Ferruccio
sumber
Ini juga penting dalam lingkungan multi-pengembang di mana, misalnya, Eric menambahkan "baz," dan kemudian George menambahkan "boom,". Jika Eric memutuskan untuk mengeluarkan kodenya untuk membangun proyek berikutnya, itu masih dikompilasi dengan perubahan George. Sangat penting untuk kontrol kode sumber multi-cabang dan jadwal pengembangan yang tumpang tindih.
Harold Bamford
Enum mungkin C99. Inisialisasi array & koma yang tertinggal adalah K&R.
Ferruccio
Enum polos berada di c89, AFAIK. Setidaknya mereka sudah ada sejak lama.
XTL
12

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.

bersantai
sumber
4
Tentu saja, ada implikasi kinerja untuk melewati struct besar dengan cara ini; seringkali bermanfaat (dan memang sesuatu yang banyak orang tidak sadari dapat Anda lakukan) tetapi Anda perlu mempertimbangkan apakah memberikan petunjuk lebih baik.
Mark Baker
1
Tentu saja, mungkin ada. Itu juga sangat mungkin bagi kompiler untuk mendeteksi penggunaan dan mengoptimalkannya.
bersantai
Berhati-hatilah jika salah satu elemennya adalah pointer, karena Anda akan menyalin pointer itu sendiri, bukan isinya. Tentu saja, hal yang sama berlaku jika Anda menggunakan memcpy ().
Adam Liss
Compiler tidak dapat mengoptimalkan konversi by-value ini dengan by-referenece, kecuali ia dapat melakukan optimasi global.
Blaisorblade
Mungkin perlu dicatat bahwa dalam C ++ standar secara khusus memungkinkan mengoptimalkan salinan (standar harus mengizinkannya untuk kompiler untuk mengimplementasikannya karena itu berarti copy constructor yang mungkin memiliki efek samping mungkin tidak dipanggil), dan karena sebagian besar kompiler C ++ juga kompiler C, ada kemungkinan kompiler Anda melakukan optimasi ini.
Joseph Garvin
10

Pengindeksan vektor aneh:

int v[100]; int index = 10; 
/* v[index] it's the same thing as index[v] */
INS
sumber
4
Itu lebih baik ... char c = 2 ["Hello"]; (c == 'l' setelah ini).
yrp
5
Tidak begitu aneh ketika Anda mempertimbangkan bahwa v [indeks] == * (v + indeks) dan indeks [v] == * (indeks + v)
Ferruccio
17
Tolong beritahu saya Anda tidak benar-benar menggunakan ini "sepanjang waktu", seperti pertanyaan yang diajukan!
Tryke
9

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:

  1. Mengandalkan bagaimana kompiler menjabarkan struct dalam memori.
  2. Asumsi tentang endianness dari bilangan bulat / mengapung.
  3. Asumsi tentang fungsi ABI.
  4. Asumsi tentang arah pertumbuhan frame stack.
  5. Asumsi tentang urutan eksekusi dalam pernyataan.
  6. Asumsi tentang urutan eksekusi pernyataan dalam argumen fungsi.
  7. Asumsi pada ukuran bit atau presisi tipe pendek, int, panjang, float dan ganda.

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'.

Kevin S.
sumber
Untuk mengatasi sebagian besar dari itu, buatlah asumsi-asumsi itu bergantung pada karakteristik platform Anda, dan jelaskan setiap platform dalam header-nya sendiri. Eksekusi pesanan adalah pengecualian - jangan pernah mengandalkan itu; pada gagasan lain, setiap platform perlu memiliki keputusan yang andal.
Blaisorblade
2
@ Blaisorblade, Bahkan lebih baik, gunakan pernyataan waktu kompilasi untuk mendokumentasikan asumsi Anda dengan cara yang akan membuat kompilasi gagal pada platform di mana mereka dilanggar.
RBerteig
Saya pikir kita harus menggabungkan keduanya, sehingga kode Anda bekerja pada banyak platform (itu adalah niat asli), dan jika makro fitur diatur dengan cara yang salah, pernyataan waktu kompilasi akan menangkapnya. Saya tidak yakin apakah, katakanlah, asumsi pada fungsi ABI dapat diperiksa sebagai pernyataan waktu kompilasi, tetapi itu harus dimungkinkan untuk sebagian besar yang lain (valid) (kecuali urutan eksekusi ;-)).
Blaisorblade
Fungsi pemeriksaan ABI harus ditangani oleh test suite.
dolmen
9

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
onemasse
sumber
8

menggunakan INT (3) untuk mengatur break point pada kode adalah favorit saya sepanjang masa

Dror Helper
sumber
3
Saya tidak berpikir itu portabel. Ini akan bekerja pada x86, tetapi bagaimana dengan platform lain?
Cristian Ciupitu
1
Saya tidak tahu - Anda harus memposting pertanyaan tentang hal itu
Dror Helper
2
Ini adalah teknik yang baik dan spesifik X86 (meskipun mungkin ada teknik serupa di platform lain). Namun, ini bukan fitur C. Ini tergantung pada ekstensi C atau panggilan pustaka yang tidak standar.
Ferruccio
1
Di GCC ada __builtin_trap dan untuk __debugbreak MSVC yang akan berfungsi pada semua arsitektur yang didukung.
Axel Gneiting
8

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.

Sridhar Iyer
sumber
Tautan tidak berfungsi lagi, bahkan situs itu sendiri tampaknya tidak berfungsi. Bisakah Anda memberikan tautan lain?
thequark
@thequark: Setiap artikel tentang "format string vulnerabilities" akan memiliki beberapa info di dalamnya .. (mis. crypto.stanford.edu/cs155/papers/formatstring-1.2.pdf ) .. Namun karena sifat lapangan, keamanan situs web sendiri agak serpihan dan artikel akademis sungguhan sulit didapat (dengan implementasi).
Sridhar Iyer
8

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)
};
SC Madsen
sumber
2
+1 Rapi. Saya dulu menggunakan makro CompilerAssert dari Microsoft, tetapi Anda tidak buruk juga. ( #define CompilerAssert(exp) extern char _CompilerAssert[(exp)?1:-1])
Patrick Schlüter
1
Saya suka metode enumerasi. Pendekatan yang saya gunakan sebelumnya mengambil keuntungan dari penghapusan kode mati: "if (something_bad) {void BLORG_IS_WOOZLED (void); BLORG_IS_WOOZLED ();}" yang tidak kesalahan hingga waktu tautan, meskipun itu menawarkan keuntungan dengan membiarkan Programmer tahu melalui pesan kesalahan bahwa blorg itu woozled.
supercat
8

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.

Alex Brown
sumber
8

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)

tristopia
sumber
7

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.

Ben Combee
sumber
Anda memiliki 'R' tambahan di VA_ARGS .
Blaisorblade
6

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.

DGentry
sumber
Apakah ini juga berfungsi dengan benar jika byte / char tidak selebar 8 bit pada platform target? Aku tahu, kasus-kasus yang jarang terjadi, tapi masih ... :)
Stephan202