Cの隠し機能


141

私はすべてのCコンパイラの実装の背後に標準があることを知っているので、隠れた機能があってはなりません。それにもかかわらず、私はすべてのC開発者がいつも使用する隠し/秘密のトリックを持っていると確信しています。


あなたや誰かが「質問」を編集して、この質問のC#バージョンやPerlバージョンなど、最適な非表示機能の選択を示すとよいでしょう。
ドナルフェロー、

回答:


62

関数ポインタ。関数ポインターのテーブルを使用して、たとえば高速の間接スレッドコードインタープリター(FORTH)やバイトコードディスパッチャーを実装したり、OOのような仮想メソッドをシミュレートしたりできます。

次に、qsort()、bsearch()、strpbrk()、strcspn()などの標準ライブラリに隠された宝石があります[後者の2つはstrtok()の置換を実装するのに役立ちます]。

Cの欠点は、符号付き算術オーバーフローが未定義の動作(UB)であることです。そのため、x + yなどの式が両方ともintに署名されていると、オーバーフローしてUBが発生する可能性があります。


29
しかし、オーバーフロー時の動作を指定していた場合、それが通常の動作ではないアーキテクチャーでは非常に遅くなります。ランタイムオーバーヘッドが非常に低いことが常にCの設計目標であり、そのため、このような多くのことが未定義になっています。
Mark Ba​​ker、

9
オーバーフローがUB である理由はよく知っています。UBを引き起こさない(すべての基本的な演算の)算術オーバーフローをテストできるライブラリルーチンが少なくとも提供されている必要があるため、これはまだ機能の誤りです。
zvrba 2009年

2
@zvrba、「(すべての基本演算の)算術オーバーフローをテストできるライブラリルーチン」これを追加した場合、整数算術演算のパフォーマンスが大幅に低下します。=====ケーススタディMatlabは特に、整数オーバーフロー動作を制御してラッピングまたは飽和させる機能をADDSに追加します。また、オーバーフローが発生するたびに例外がスローされます==> Matlab整数演算のパフォーマンス:非常に遅い。私自身の結論:Matlabは、整数のオーバーフローチェックを望まない理由を示す説得力のあるケーススタディだと思います。
Trevor Boyd Smith、

15
標準は算術オーバーフローをチェックするためのライブラリサポートを提供する必要があると述べました。では、ライブラリルーチンを使用しない場合、どのようにしてパフォーマンスヒットが発生するのでしょうか。
zvrba

5
大きな欠点は、GCCに符号付き整数オーバーフローをキャッチしてランタイム例外をスローするフラグがないことです。そのようなケースを検出するためのx86フラグがありますが、GCCはそれらを利用しません。このようなフラグを設定すると、パフォーマンスが重要ではない(特にレガシー)アプリケーションで、コードのレビューやリファクタリングを最小限に抑えてセキュリティを強化できるようになります。
Andrew Keeton、

116

GCCコンパイラーのトリックの詳細ですが、コンパイラーに分岐指示のヒントを与えることができます(Linuxカーネルでは一般的)

#define likely(x)       __builtin_expect((x),1)
#define unlikely(x)     __builtin_expect((x),0)

参照:http : //kerneltrap.org/node/4705

これについて私が気に入っているのは、いくつかの機能に表現力が加わることです。

void foo(int arg)
{
     if (unlikely(arg == 0)) {
           do_this();
           return;
     }
     do_that();
     ...
}

2
このトリックはクールです... :)特にあなたが定義するマクロに関して。:)
sundar-モニカを2008年

77
int8_t
int16_t
int32_t
uint8_t
uint16_t
uint32_t

これらは標準のオプション項目ですが、人々は常にそれらを再定義しているため、非表示の機能である必要があります。私が取り組んだ1つのコードベース(今のところまだそうです)には、すべて異なる識別子を持つ複数の再定義があります。ほとんどの場合、プリプロセッサマクロを使用します。

#define INT16 short
#define INT32  long

等々。髪を抜いたくなる。 ひどい標準整数typedefを使用するだけです!


3
C99くらいかなと思います。これらを確実に実現するためのポータブルな方法は見つかりませんでした。
akauppi 2008

3
これらはC99のオプションの部分ですが、これを実装していないコンパイラベンダーはないことを知っています。
ベンコリンズ

10
stdint.hはC99ではオプションではありませんが、C99標準に従うことは明らかに一部のベンダー向けです(Microsoftの)。
ベンコンビー2008年

5
@Pete、アナルになりたい場合:(1)このスレッドは、Microsoft製品とは関係ありません。(2)このスレッドは、C ++とはまったく関係がありません。(3)C ++ 97のようなものはありません
ベン・コリンズ

5
見ていazillionmonkeys.com/qed/pstdint.hを -クローズ・ツー・ポータブルstdint.h
gnud

73

コンマ演算子は広く使用されていません。それは確かに悪用される可能性がありますが、非常に役立つこともあります。この使用法は最も一般的なものです。

for (int i=0; i<10; i++, doSomethingElse())
{
  /* whatever */
}

ただし、この演算子はどこでも使用できます。観察する:

int j = (printf("Assigning variable j\n"), getValueFromSomewhere());

各ステートメントが評価されますが、式の値は最後に評価されたステートメントの値になります。


7
CIの20年間で、それを見たことはありません!
Martin Beckett、

11
C ++では、オーバーロードすることもできます。
Wouter Lievens、

6
もちろん!=すべきです。オーバーロードの危険性は、組み込みがvoidを含めてすでにすべてに適用されるため、利用可能なオーバーロードがないためにコンパイルに失敗しないことです。つまり、プログラマに多くのロープを与えます。
アーロン

ループ内のintはCでは動作しません。これはC ++の改善です。「、」は(i = 0、j = 10; i <j; j--、i ++)と同じ演算ですか?
2010年

63

構造をゼロに初期化しています

struct mystruct a = {0};

これにより、すべての構造要素がゼロになります。


2
ただし、パディングがあってもゼロにはなりません。
Mikeage 2009年

2
@simonn、構造に非整数型が含まれている場合、未定義の動作は行いません。float / doubleのメモリが0であるmemsetは、float / doubleを解釈するときも0のままです(float / doubleは意図的にそのように設計されています)。
Trevor Boyd Smith、

6
@Andrew:memset/ calloc実際にすべてのタイプに対して定義されているわけではない「すべてのバイトゼロ」(つまり、物理的なゼロ)を実行します。適切な論理ゼロ値ですべて{ 0 } を包括することが保証されています。たとえば、特定のプラットフォームのnull値がnullの場合でも、ポインターは適切なnull値を取得することが保証されています。0xBAADFOOD
AnT 2009年

1
@nvl:オブジェクトが占有するすべてのメモリを強制的にすべてビット0の状態に設定すると、物理的に 0になります。これはmemset02番目の引数として)行うことです。ソースコード内のオブジェクトを初期化/割り当て(または)すると、論理値はゼロになります。これらの2種類のゼロは、必ずしも同じ結果を生成するとは限りません。ポインタの例のように。あなたが行うと、ポインタに、あなたが得るポインタを。ただし、ポインターに割り当てると、nullポインター値が取得されます。これは、物理レベルでは、その他のになる可能性があります。0{ 0 }memset0x000000xBAADF00D
AnT

3
@nvl:まあ、実際には、違いはしばしば概念的なものにすぎません。しかし理論的には、事実上すべてのタイプがそれを持つことができます。たとえば、double。通常、IEEE-754標準に従って実装され、論理ゼロと物理ゼロは同じです。しかし、IEEE-754は言語によって要求されていません。したがって、double d = 0;(論理ゼロ)を実行するdと、メモリ内の物理的に占有されている一部のビットがゼロにならない場合があります。
AnT

52

複数文字定数:

int x = 'ABCD';

これは(またはアーキテクチャによっては)に設定さxれます。0x414243440x44434241

編集:この方法は、特にintをシリアル化する場合は移植できません。ただし、自己文書化列挙型を作成すると非常に便利です。例えば

enum state {
    stopped = 'STOP',
    running = 'RUN!',
    waiting = 'WAIT',
};

これにより、生のメモリダンプを見ていて、それを調べずに列挙型の値を決定する必要がある場合に、より簡単になります。


これは移植性のある構成ではないと確信しています。複数文字定数を作成した結果は、実装によって定義されます。
マークベッシー

8
「移植不可」のコメントは、要点を完全に逃しています。INT_MAXが「移植不可能」であるという理由だけで、INT_MAXを使用するためのプログラムを批判するようなものです:)この機能は、必要なだけ移植可能です。マルチ文字定数は、一意の整数IDを生成するための読みやすい方法を提供する非常に便利な機能です。
AnT

1
@クリス・ルッツ-末尾のコンマがK&Rに戻ると確信しています。第2版​​(1988)で説明されています。
Ferruccio

1
@Ferruccio:集計initailizerリストの末尾のコンマについて考える必要があります。列挙型宣言の末尾のコンマについては、最近追加されたC99です。
AnT

3
「HANG」または「BSOD」を忘れた:-)
JBRウィルキンソン、2009年

44

私はビットフィールドを使ったことはありませんが、超低レベルのものにはクールに聞こえます。

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

つまり、sizeof(cat)はと同じくらい小さくすることができますsizeof(char)


アーロンレッピーのコメントを組み込んだ。


構造体と共用体の組み合わせは、組み込みシステムや低レベルのドライバーコードなど、さらに興味深いものです。例として、SDカードのレジスタを解析する場合、ビットフィールドの構造体であるunion(1)を使用して読み取り、union(2)を使用して読み取ることができます。
ComSubVie 2008

5
ビットフィールドは移植可能ではありません-例では、レッグに最上位3ビットを割り当てるか、最下位3ビットを割り当てるかをコンパイラーが自由に選択できます。
zvrba 2008

3
ビットフィールドは、標準によって実装方法が非常に自由になるため、実際にはほとんど役に立たない例です。値が占めるビット数とその格納方法を気にする場合は、ビットマスクを使用する方がよいでしょう。
マークベッシー

26
ビットフィールドは、「整数の断片」ではなく、構造要素として扱う限り、確かに移植可能です。メモリが限られている組み込みシステムでは、各ビットが貴重であるため、場所ではなくサイズが重要です...しかし、今日のほとんどのコーダーは若すぎて覚えられません。:-)
Adam Liss、

5
@Adam:バイト内のビットフィールドの位置に依存している場合、埋め込みシステム(または他の場所)では場所が問題になることがあります。マスクを使用すると、あいまいさがなくなります。組合についても同様です。
スティーブメルニコフ2009年

37

Cには標準がありますが、すべてのCコンパイラが完全に準拠しているわけではありません(完全に準拠したC99コンパイラはまだ見たことがありません!)。

とはいえ、私が好むトリックは、Cセマンティックスに依存しているため、プラットフォーム間で非自明で移植可能なトリックです。それらは通常、マクロまたはビット演算に関するものです。

例:一時変数を使用せずに2つの符号なし整数を交換する:

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

または「拡張C」は、次のような有限状態機械を表します。

FSM {
  STATE(x) {
    ...
    NEXTSTATE(y);
  }

  STATE(y) {
    ...
    if (x == 0) 
      NEXTSTATE(y);
    else 
      NEXTSTATE(x);
  }
}

これは、次のマクロで実現できます。

#define FSM
#define STATE(x)      s_##x :
#define NEXTSTATE(x)  goto s_##x

一般的に、しかし、私は巧妙なトリックは好きではありませんが、(スワップの例として)コードを不必要に複雑にし、コードをより明確にして意図を直接伝えるもの(FSMの例のように)が好きです。


18
Cは連鎖をサポートしているため、^ = b ^ = a ^ = bを実行できます。
OJ。

4
厳密に言うと、状態の例はプリプロセッサの目盛りであり、C言語ではありません。前者は後者なしで使用できます。
グレッグホイットフィールド、

15
OJ:実際に提案するのは、シーケンスポイントルールのため、未定義の動作です。ほとんどのコンパイラで動作する可能性がありますが、正しくないか移植性がありません。
エヴァン・テラン

5
Xor swapは、空きレジスタの場合、実際には効率が低下する可能性があります。まともなオプティマイザは、一時変数をレジスタにします。実装(および並列処理サポートの必要性)に応じて、スワップは実際にはレジスター(同じ)の代わりに実メモリーを使用する場合があります。
ポールドヴリーズ08年

27
:今までに実際にこれを行うしないでくださいen.wikipedia.org/wiki/...
クリスチャンOudard

37

ダフのデバイスのようなインターレース構造:

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、Duff's Deviceを使用する人はだれでも、Duff's Deviceを見て、Duff's Deviceを使用するとコードが1337に見えると思ったスクリプトキディです。(1.)最新のプロセッサにはオーバーヘッドループがゼロであるため、Duff's Deviceは最新のプロセッサのパフォーマンスを向上させません。言い換えれば、これは時代遅れのコードです。(2.)プロセッサがゼロオーバーヘッドループを提供しない場合でも、memcpy()を使用するとDuffのデバイスが恥をかかえるSSE / altivec / vector-processingのようなものになる可能性があります。(3.)memcpy()duffを実行することは他の方法では役に立たないということを私は言及しましたか?
Trevor Boyd Smith、

2
@ComSubVie、私のフィストオブデス(en.wikipedia.org/wiki/…)に会ってください
Trevor Boyd Smith

12
@Trevor:スクリプトキディプログラム8051とPICマイクロコントローラーだけですよね?
SF。

6
@Trevor Boyd Smith:Duff's Deviceは古くなっているように見えますが、それでもComSubVieの回答を検証する歴史的な好奇心です。とにかく、Wikipediaを引用すると、「バージョン4.0でXFree86サーバーからDuffのデバイスの多数のインスタンスが削除されたとき、パフォーマンスが著しく向上しました。」...
paercebal

2
Symbianでは、高速ピクセルコーディングのためにさまざまなループを評価しました。アフターでのダフのデバイスが最速でした。そのため、現在でもスマートフォンの主流のARMコアとの関連性がありました。
2010年

33

私はC99で追加された(そしてgccで長い間サポートされている)指定された初期化子が大好きです:

#define FOO 16
#define BAR 3

myStructType_t myStuff[] = {
    [FOO] = { foo1, foo2, foo3 },
    [BAR] = { bar1, bar2, bar3 },
    ...

アレイの初期化は位置に依存しなくなりました。FOOまたはBARの値を変更すると、アレイの初期化は自動的にそれらの新しい値に対応します。


gccが長い間サポートしてきた構文は、標準のC99構文と同じではありません。
Mark Ba​​ker、

28

C99には、すばらしい任意の順序の構造の初期化があります。

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

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


27

匿名の構造と配列は私のお気に入りです。(http://www.run.montefiore.ulg.ac.be/~martin/resources/kung-f00.htmlを参照

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

または

void myFunction(type* values) {
    while(*values) x=*values++;
}
myFunction((type[]){val1,val2,val3,val4,0});

リンクされたリストのインスタンス化にも使用できます...


3
この機能は通常、「複合リテラル」と呼ばれます。匿名(または名前のない)構造は、メンバー名のないネストされた構造を示します。
カランドア2009年

私のGCCによれば、「ISO C90は複合リテラルを禁じています」。
jmtd 2009年

「ISO C99は複合リテラルをサポートしています。」「拡張機能として、GCCはC89モードとC ++で複合リテラルをサポートします」(dixit info gcc)。さらに、「GNU拡張機能として、GCCは静的リテラル期間を持つオブジェクトの初期化を複合リテラルで許可します(これは、初期化子が定数ではないため、ISO C99では不可能です)。」
PypeBros、2011

24

gccには、私が楽しんでいるC言語の拡張機能がいくつかあります。私のお気に入りのいくつかは関数属性です。非常に便利な例の1つはformat属性です。これは、printf形式の文字列を受け取るカスタム関数を定義する場合に使用できます。この関数属性を有効にすると、gccは引数をチェックして、フォーマット文字列と引数が一致することを確認し、必要に応じて警告またはエラーを生成します。

int my_printf (void *my_object, const char *my_format, ...)
            __attribute__ ((format (printf, 2, 3)));

24

私が最初に見たときに私に「衝撃を与えた」(隠された)機能は、printfに関するものです。この機能により、変数を使用してフォーマット指定子自体をフォーマットできます。コードを探してください。よくわかります。

#include <stdio.h>

int main() {
    int a = 3;
    float b = 6.412355;
    printf("%.*f\n",a,b);
    return 0;
}

*文字はこの効果を達成します。


24

ええと... C言語の強みの1つはその移植性と標準性だと思うので、現在使用している実装に「隠れたトリック」が見つかるときはいつでも、使用しないようにしています。可能な限り標準で移植可能なCコード。


しかし実際には、別のコンパイラでコードをコンパイルする必要がある頻度はどれくらいですか?
Joe D

3
@Joe Dの場合、おそらくWindows / OSX / Linuxのようなクロスプラットフォームプロジェクトであり、おそらく少しだけでなく、x86とx86_64のような別のアーチもある...
Pharaun

@JoeD 1つのコンパイラベンダーとの結婚を喜んで受け入れる、非常に狭い分野のプロジェクトでない限り。実際にコンパイラを切り替える必要はないかもしれませんが、そのオプションは開いたままにしておきたいです。ただし、組み込みシステムでは、常に選択できるとは限りません。AHS、ASS。
XTL

19

すでにここで説明したように、コンパイル時のアサーション。

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

定数文字列の連結

私が知っているすべてのコンパイラがそれをサポートしているので、答えにそれが表示されていないことにかなり驚きましたが、多くのプログラマはそれを無視しているようです。時にはそれは本当に便利で、マクロを書くときだけではありません。

現在のコードにある使用例:#define PATH "/some/path/"構成ファイルに(実際にはmakefileによって設定されています)があります。次に、リソースを開くためのファイル名を含む完全なパスを作成します。それはちょうど行きます:

fd = open(PATH "/file", flags);

恐ろしいが、非常に一般的なのではなく:

char buffer[256];
snprintf(buffer, 256, "%s/file", PATH);
fd = open(buffer, flags);

一般的な恐ろしい解決策は次のとおりです。

  • 3倍の長さ
  • 読みにくい
  • ずっと遅い
  • 任意のバッファーサイズ制限に設定されている場合は、それほど強力ではありません(ただし、文字列が一定に汚染されないようにするには、さらに長いコードを使用する必要があります)。
  • より多くのスタックスペースを使用する

1
また、ダーティ `\`を使用せずに文字列定数を複数のソース行で分割することも役立ちます。
ドルメン


12

配列または列挙型を初期化するとき、初期化子リストの最後の項目の後にコンマを置くことができます。例えば:

int x[] = { 1, 2, 3, };

enum foo { bar, baz, boom, };

これは、コードを自動的に生成する場合に、最後のカンマを削除することを心配する必要がないように行われました。


これは、たとえば、Ericが「baz」を追加し、次にGeorgeが「boom」を追加するマルチ開発環境でも重要です。エリックが次のプロジェクトビルドのために自分のコードを引き出すことに決めた場合でも、ジョージの変更でコンパイルされます。マルチブランチのソースコード管理と重複する開発スケジュールにとって非常に重要です。
ハロルドバンフォード

列挙型はC99の場合があります。配列初期化子と末尾のコンマはK&Rです。
フェルッチョ

明白な列挙型はc89にあった、AFAIK。少なくとも彼らは長い間存在してきた。
XTL、2012

12

構造の割り当てはクールです。多くの人は、構造体も値であることを理解していないようでmemcpy()、簡単に割り当てるとうまくいく場合は、を使用する必要はありません。

たとえば、架空の2Dグラフィックライブラリについて考えてみましょう。これは、(整数の)画面座標を表すタイプを定義する場合があります。

typedef struct {
   int x;
   int y;
} Point;

ここで、関数の引数から初期化されたポイントを作成してそれを返す関数を書くなど、「間違っている」ように見えることを行います。

Point point_new(int x, int y)
{
  Point p;
  p.x = x;
  p.y = y;
  return p;
}

これは、(もちろん)戻り値がstruct割り当てを使用して値ごとにコピーされる限り安全です。

Point origin;
origin = point_new(0, 0);

このようにして、すべてがプレーンな標準Cで、非常にクリーンでオブジェクト指向のコードを書くことができます。


4
もちろん、この方法で大きな構造体を渡すと、パフォーマンスに影響があります。これはしばしば便利です(実際、多くの人があなたにできることを知らないものです)が、ポインタを渡す方が良いかどうかを検討する必要があります。
Mark Ba​​ker、

1
もちろんあるかもしれません。また、コンパイラが使用状況を検出して最適化することも不可能です。
アンワインド

内容がポインタではなくポインタ自体をコピーするため、要素がポインタである場合は注意してください。もちろん、memcpy()を使用する場合も同様です。
Adam Liss

コンパイラーは、グローバルな最適化を実行できない限り、参照渡しで値渡しのこの変換を最適化できません。
Blaisorblade 2009年

C ++では、標準でコピーの最適化を明確に許可していることに注意することをお勧めします(標準では、副作用のあるコピーコンストラクターが呼び出されない可能性があるため、コンパイラーがコピーを実装できるようにする必要があります)。 Cコンパイラでもあるため、コンパイラがこの最適化を行う可能性は十分にあります。
ジョセフガービン

10

奇妙なベクトルインデックス:

int v[100]; int index = 10; 
/* v[index] it's the same thing as index[v] */

4
それはさらに良いです... char c = 2 ["Hello"]; (この後c == 'l')。
yrp 2008

5
v [index] == *(v + index)およびindex [v] == *(index + v)と考えるとそれほど奇妙ではありません
Ferruccio

17
質問のように、これを「常に」実際に使用していないことを教えてください。
トライク2008

9

Cコンパイラは、いくつかの標準のうちの1つを実装します。ただし、標準があるからといって、言語のすべての側面が定義されているわけではありません。 たとえば、ダフのデバイスは非常に人気のある「隠された」機能であり、最近のコンパイラは特別な目的の認識コードを備えているため、最適化手法でこの頻繁に使用されるパターンの望ましい効果が損なわれないようにします。

一般に、コンパイラが使用するC標準のかみそりのエッジで実行しているため、隠し機能や言語トリックはお勧めしません。このようなトリックの多くはコンパイラ間で機能せず、多くの場合、これらの種類の機能は、特定の製造元のコンパイラスイートのあるバージョンから別のバージョンに失敗します。

Cコードが壊れたさまざまなトリックには、次のものがあります。

  1. コンパイラーが構造体をメモリに配置する方法に依存します。
  2. 整数/浮動小数点数のエンディアンの仮定。
  3. 関数ABIの前提条件。
  4. スタックフレームが成長する方向の仮定。
  5. ステートメント内の実行順序に関する仮定。
  6. 関数の引数でのステートメントの実行順序に関する仮定。
  7. short、int、long、float、およびdouble型のビットサイズまたは精度に関する仮定。

プログラマーがほとんどのC標準ですべて「コンパイラー依存」の動作として指定されている実行モデルについて仮定を行うたびに発生するその他の問題や問題。


それらのほとんどを解決するには、それらの仮定をプラットフォームの特性に依存させ、各プラットフォームを独自のヘッダーに記述します。注文の実行は例外です-決してそれに依存しないでください。他のアイデアでは、各プラットフォームには信頼できる決定が必要です。
Blaisorblade、2009年

2
@Blaisorblade、さらに良いことに、コンパイル時のアサーションを使用して、違反があったプラットフォームでコンパイルが失敗するように仮定を文書化します。
RBerteig 2010

両方を組み合わせて、コードが複数のプラットフォームで動作するようにする必要があります(元々の意図)、機能マクロが間違った方法で設定されている場合、コンパイル時のアサーションがそれをキャッチします。たとえば、関数ABIの仮定がコンパイル時のアサーションとしてチェック可能かどうかはわかりませんが、他のほとんどの(有効な)アサーション(実行順序を除く;-)で可能であるはずです。
Blaisorblade、2010

関数ABIチェックは、テストスイートで処理する必要があります。
ドルメン

9

sscanfを使用する場合、%nを使用して、どこを読み続ける必要があるかを見つけることができます。

sscanf ( string, "%d%n", &number, &length );
string += length;

どうやら、別の回答を追加することはできないので、ここに2つ目の回答を追加します。「&&」と「||」を使用できます 条件付き:

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

このコードは出力します:

こんにちは
ROFL

8

INT(3)を使用してコードにブレークポイントを設定することは、私のお気に入りです


3
持ち運びできるとは思いません。x86でも動作しますが、他のプラットフォームはどうですか?
クリスティアンCiupitu 2008

1
わから

2
これは優れた手法であり、X86固有です(ただし、他のプラットフォームでもおそらく同様の手法が存在します)。ただし、これはCの機能ではありません。非標準のC拡張またはライブラリ呼び出しに依存します。
Ferruccio

1
GCCには__builtin_trapがあり、サポートされているアーキテクチャで動作するMSVCには__debugbreakがあります。
Axel Gneiting 2010年

8

Cの私のお気に入りの「非表示」機能は、スタックに書き戻すためのprintfでの%nの使用です。通常、printfはフォーマット文字列に基づいてスタックからパラメータ値をポップしますが、%nはそれらを書き戻すことができます。

ここ 3.4.2をご覧ください。多くの厄介な脆弱性につながる可能性があります。


リンクはもう機能していません。実際、サイト自体は機能していないようです。別のリンクを提供できますか?
クォーク、2011年

@thequark:「フォーマット文字列の脆弱性」に関する記事には情報が含まれています。(たとえば、crypto.stanford.edu / cs155 / papers / formatstring-1.2.pdf)..ただし、フィールドの性質により、セキュリティウェブサイト自体は少し不安定で、実際の学術記事は(実装とともに)入手するのが困難です。
Sridhar Iyer、2011年

8

列挙型を使用したコンパイル時の仮定チェック:ばかげた例ですが、コンパイル時の構成可能な定数を備えたライブラリーには本当に役立ちます。

#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。以前はMicrosoftのCompilerAssertマクロを使用していましたが、あなたのマクロも悪くありません。(#define CompilerAssert(exp) extern char _CompilerAssert[(exp)?1:-1]
パトリックSchlüter

1
列挙法が好きです。以前使用したアプローチでは、デッドコードの排除を利用していました。プログラマーは、エラーメッセージを介して、blorgが失敗したことを知っています。
スーパーキャット'25

8

Gcc(c)には、ネストされた関数宣言や、a :: b形式の?:演算子など、aがfalseでない場合にaを返す、有効にできるいくつかの楽しい機能があります。


8

最近0ビットフィールドを発見しました。

struct {
  int    a:3;
  int    b:2;
  int     :0;
  int    c:4;
  int    d:3;
};

のレイアウトを与える

000aaabb 0ccccddd

:0なしの代わりに;

0000aaab bccccddd

幅0のフィールドは、次のビットフィールドを次のアトミックエンティティ(char)に設定する必要があることを示します


7

C99スタイルの可変引数マクロ、別名

#define ERR(name, fmt, ...)   fprintf(stderr, "ERROR " #name ": " fmt "\n", \
                                  __VAR_ARGS__)

のように使用されます

ERR(errCantOpen, "File %s cannot be opened", filename);

ここでは、文字列化演算子と文字列定数の連結など、私が本当に気に入っているその他の機能も使用しています。


VA_ARGSに余分な「R」があります。
Blaisorblade、2009年

6

可変サイズの自動変数も役立つ場合があります。これらはnC99で追加され、gccで長い間サポートされてきました。

void foo(uint32_t extraPadding) {
    uint8_t commBuffer[sizeof(myProtocol_t) + extraPadding];

スタックには、固定サイズのプロトコルヘッダーと可変サイズのデータ​​用のスペースを持つバッファーができます。alloca()で同じ効果を得ることができますが、この構文はよりコンパクトです。

このルーチンを呼び出す前に、extraPaddingが適切な値であることを確認する必要があります。そうしないと、スタックを爆破してしまいます。mallocまたはその他のメモリ割り当て手法を呼び出す前に、引数の妥当性をチェックする必要があるため、これはそれほど珍しいことではありません。


ターゲットプラットフォームでバイト/文字が正確に8ビット幅でない場合も、これは正しく機能しますか?私は知っています、これらのケースはまれですが、それでも... :)
Stephan202 '26
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.