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.


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.


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();
     ...
}

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!


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.


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.


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.


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.


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


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

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.


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


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


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

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.


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.


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

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

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 .


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.


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.


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] */

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


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

8

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


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.


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

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.


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)


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.


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.


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
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.