回答:
関数ポインタ。関数ポインターのテーブルを使用して、たとえば高速の間接スレッドコードインタープリター(FORTH)やバイトコードディスパッチャーを実装したり、OOのような仮想メソッドをシミュレートしたりできます。
次に、qsort()、bsearch()、strpbrk()、strcspn()などの標準ライブラリに隠された宝石があります[後者の2つはstrtok()の置換を実装するのに役立ちます]。
Cの欠点は、符号付き算術オーバーフローが未定義の動作(UB)であることです。そのため、x + yなどの式が両方ともintに署名されていると、オーバーフローしてUBが発生する可能性があります。
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();
...
}
int8_t
int16_t
int32_t
uint8_t
uint16_t
uint32_t
これらは標準のオプション項目ですが、人々は常にそれらを再定義しているため、非表示の機能である必要があります。私が取り組んだ1つのコードベース(今のところまだそうです)には、すべて異なる識別子を持つ複数の再定義があります。ほとんどの場合、プリプロセッサマクロを使用します。
#define INT16 short
#define INT32 long
等々。髪を抜いたくなる。 ひどい標準整数typedefを使用するだけです!
コンマ演算子は広く使用されていません。それは確かに悪用される可能性がありますが、非常に役立つこともあります。この使用法は最も一般的なものです。
for (int i=0; i<10; i++, doSomethingElse())
{
/* whatever */
}
ただし、この演算子はどこでも使用できます。観察する:
int j = (printf("Assigning variable j\n"), getValueFromSomewhere());
各ステートメントが評価されますが、式の値は最後に評価されたステートメントの値になります。
構造をゼロに初期化しています
struct mystruct a = {0};
これにより、すべての構造要素がゼロになります。
memset
/ calloc
実際にすべてのタイプに対して定義されているわけではない「すべてのバイトゼロ」(つまり、物理的なゼロ)を実行します。適切な論理ゼロ値ですべて{ 0 }
を包括することが保証されています。たとえば、特定のプラットフォームのnull値がnullの場合でも、ポインターは適切なnull値を取得することが保証されています。0xBAADFOOD
memset
(0
2番目の引数として)行うことです。ソースコード内のオブジェクトを初期化/割り当て(または)すると、論理値はゼロになります。これらの2種類のゼロは、必ずしも同じ結果を生成するとは限りません。ポインタの例のように。あなたが行うと、ポインタに、あなたが得るポインタを。ただし、ポインターに割り当てると、nullポインター値が取得されます。これは、物理レベルでは、その他の値になる可能性があります。0
{ 0 }
memset
0x0000
0
0xBAADF00D
double
。通常、IEEE-754標準に従って実装され、論理ゼロと物理ゼロは同じです。しかし、IEEE-754は言語によって要求されていません。したがって、double d = 0;
(論理ゼロ)を実行するd
と、メモリ内の物理的に占有されている一部のビットがゼロにならない場合があります。
複数文字定数:
int x = 'ABCD';
これは(またはアーキテクチャによっては)に設定さx
れます。0x41424344
0x44434241
編集:この方法は、特にintをシリアル化する場合は移植できません。ただし、自己文書化列挙型を作成すると非常に便利です。例えば
enum state {
stopped = 'STOP',
running = 'RUN!',
waiting = 'WAIT',
};
これにより、生のメモリダンプを見ていて、それを調べずに列挙型の値を決定する必要がある場合に、より簡単になります。
私はビットフィールドを使ったことはありませんが、超低レベルのものにはクールに聞こえます。
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)
。
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の例のように)が好きです。
ダフのデバイスのようなインターレース構造:
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);
}
}
私はC99で追加された(そしてgccで長い間サポートされている)指定された初期化子が大好きです:
#define FOO 16
#define BAR 3
myStructType_t myStuff[] = {
[FOO] = { foo1, foo2, foo3 },
[BAR] = { bar1, bar2, bar3 },
...
アレイの初期化は位置に依存しなくなりました。FOOまたはBARの値を変更すると、アレイの初期化は自動的にそれらの新しい値に対応します。
匿名の構造と配列は私のお気に入りです。(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});
リンクされたリストのインスタンス化にも使用できます...
私が最初に見たときに私に「衝撃を与えた」(隠された)機能は、printfに関するものです。この機能により、変数を使用してフォーマット指定子自体をフォーマットできます。コードを探してください。よくわかります。
#include <stdio.h>
int main() {
int a = 3;
float b = 6.412355;
printf("%.*f\n",a,b);
return 0;
}
*文字はこの効果を達成します。
ええと... C言語の強みの1つはその移植性と標準性だと思うので、現在使用している実装に「隠れたトリック」が見つかるときはいつでも、使用しないようにしています。可能な限り標準で移植可能なCコード。
すでにここで説明したように、コンパイル時のアサーション。
//--- 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);
定数文字列の連結
私が知っているすべてのコンパイラがそれをサポートしているので、答えにそれが表示されていないことにかなり驚きましたが、多くのプログラマはそれを無視しているようです。時にはそれは本当に便利で、マクロを書くときだけではありません。
現在のコードにある使用例:#define PATH "/some/path/"
構成ファイルに(実際にはmakefileによって設定されています)があります。次に、リソースを開くためのファイル名を含む完全なパスを作成します。それはちょうど行きます:
fd = open(PATH "/file", flags);
恐ろしいが、非常に一般的なのではなく:
char buffer[256];
snprintf(buffer, 256, "%s/file", PATH);
fd = open(buffer, flags);
一般的な恐ろしい解決策は次のとおりです。
配列または列挙型を初期化するとき、初期化子リストの最後の項目の後にコンマを置くことができます。例えば:
int x[] = { 1, 2, 3, };
enum foo { bar, baz, boom, };
これは、コードを自動的に生成する場合に、最後のカンマを削除することを心配する必要がないように行われました。
構造の割り当てはクールです。多くの人は、構造体も値であることを理解していないようで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で、非常にクリーンでオブジェクト指向のコードを書くことができます。
Cコンパイラは、いくつかの標準のうちの1つを実装します。ただし、標準があるからといって、言語のすべての側面が定義されているわけではありません。 たとえば、ダフのデバイスは非常に人気のある「隠された」機能であり、最近のコンパイラは特別な目的の認識コードを備えているため、最適化手法でこの頻繁に使用されるパターンの望ましい効果が損なわれないようにします。
一般に、コンパイラが使用するC標準のかみそりのエッジで実行しているため、隠し機能や言語トリックはお勧めしません。このようなトリックの多くはコンパイラ間で機能せず、多くの場合、これらの種類の機能は、特定の製造元のコンパイラスイートのあるバージョンから別のバージョンに失敗します。
Cコードが壊れたさまざまなトリックには、次のものがあります。
プログラマーがほとんどのC標準ですべて「コンパイラー依存」の動作として指定されている実行モデルについて仮定を行うたびに発生するその他の問題や問題。
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
INT(3)を使用してコードにブレークポイントを設定することは、私のお気に入りです
Cの私のお気に入りの「非表示」機能は、スタックに書き戻すためのprintfでの%nの使用です。通常、printfはフォーマット文字列に基づいてスタックからパラメータ値をポップしますが、%nはそれらを書き戻すことができます。
列挙型を使用したコンパイル時の仮定チェック:ばかげた例ですが、コンパイル時の構成可能な定数を備えたライブラリーには本当に役立ちます。
#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)には、ネストされた関数宣言や、a :: b形式の?:演算子など、aがfalseでない場合にaを返す、有効にできるいくつかの楽しい機能があります。
C99スタイルの可変引数マクロ、別名
#define ERR(name, fmt, ...) fprintf(stderr, "ERROR " #name ": " fmt "\n", \
__VAR_ARGS__)
のように使用されます
ERR(errCantOpen, "File %s cannot be opened", filename);
ここでは、文字列化演算子と文字列定数の連結など、私が本当に気に入っているその他の機能も使用しています。
可変サイズの自動変数も役立つ場合があります。これらはnC99で追加され、gccで長い間サポートされてきました。
void foo(uint32_t extraPadding) {
uint8_t commBuffer[sizeof(myProtocol_t) + extraPadding];
スタックには、固定サイズのプロトコルヘッダーと可変サイズのデータ用のスペースを持つバッファーができます。alloca()で同じ効果を得ることができますが、この構文はよりコンパクトです。
このルーチンを呼び出す前に、extraPaddingが適切な値であることを確認する必要があります。そうしないと、スタックを爆破してしまいます。mallocまたはその他のメモリ割り当て手法を呼び出す前に、引数の妥当性をチェックする必要があるため、これはそれほど珍しいことではありません。