すべてのCコンパイラ実装の背後には標準があるので、隠された機能はありません。それにもかかわらず、私はすべてのC開発者が常に使用する隠し/秘密のトリックを持っていると確信しています。
関数ポインター。関数ポインターのテーブルを使用して、たとえば、高速間接スレッドコードインタープリター(FORTH)またはバイトコードディスパッチャーを実装したり、OOのような仮想メソッドをシミュレートしたりできます。
次に、標準ライブラリにqsort()、bsearch()、strpbrk()、strcspn()などの隠されたgemがあります[後者2つはstrtok()の置換を実装するのに役立ちます]。
Cの特徴は、符号付き算術オーバーフローが未定義の動作(UB)であることです。したがって、x + yなどの式が符号付き整数である場合は、オーバーフローして潜在的に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
これらは標準のオプション項目ですが、人々は常にそれらを再定義しているため、隠された機能でなければなりません。私が取り組んでいる(そして今のところまだ)あるコードベースには、すべて異なる識別子を持つ複数の再定義があります。ほとんどの場合、プリプロセッサマクロを使用しています。
#define INT16 short
#define INT32 long
等々。それは私が私の髪を引き出したいです。 奇妙な標準整数typedefsを使用してください!
コンマ演算子は広く使用されていません。確かに悪用される可能性がありますが、非常に役立つこともあります。これは最も一般的な使用法です。
for (int i=0; i<10; i++, doSomethingElse())
{
/* whatever */
}
ただし、この演算子はどこでも使用できます。観察する:
int j = (printf("Assigning variable j\n"), getValueFromSomewhere());
各ステートメントが評価されますが、式の値は最後に評価されたステートメントの値になります。
構造体をゼロに初期化
struct mystruct a = {0};
これにより、すべての構造要素がゼロになります。
複数文字の定数:
int x = 'ABCD';
これにより、x
が0x41424344
(またはアーキテクチャに応じて0x44434241
)に設定されます。
EDIT:この手法は、特にintをシリアル化する場合、移植性がありません。ただし、自己文書化列挙型を作成すると非常に便利です。例えば.
enum state {
stopped = 'STOP',
running = 'RUN!',
waiting = 'WAIT',
};
これにより、生のメモリダンプを調べているときに、enumの値を調べる必要なく決定する必要がある場合に、はるかに簡単になります。
ビットフィールド を使用したことはありませんが、超低レベルのものにはクールに聞こえます。
_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)
と同じくらい小さいことを意味します。
Duff's Device :のようなインターレース構造
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);
}
}
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の例など)が大好きです。 。
私は、指定されたイニシャライザが非常に好きで、C99に追加されました(そしてgccで長い間サポートされていました)。
#define FOO 16
#define BAR 3
myStructType_t myStuff[] = {
[FOO] = { foo1, foo2, foo3 },
[BAR] = { bar1, bar2, bar3 },
...
配列の初期化は位置に依存しなくなりました。 FOOまたはBARの値を変更すると、配列の初期化は自動的に新しい値に対応します。
C99には、素晴らしい順不同の構造初期化があります。
struct foo{
int x;
int y;
char* name;
};
void main(){
struct foo f = { .y = 23, .name = "awesome", .x = -38 };
}
</ code>
匿名の構造と配列は私のお気に入りです。 (cf. 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;
}
*文字はこの効果を実現します。
gccには、私が楽しんでいるC言語の拡張機能がいくつかあります。これは here にあります。私のお気に入りのいくつかは 関数属性 です。非常に有用な例の1つは、format属性です。これは、printf形式の文字列を受け取るカスタム関数を定義する場合に使用できます。この関数属性を有効にすると、gccは引数をチェックしてフォーマット文字列と引数が一致することを確認し、必要に応じて警告またはエラーを生成します。
int my_printf (void *my_object, const char *my_format, ...)
__attribute__ ((format (printf, 2, 3)));
まあ... 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/"
構成ファイル内(実際には、メイクファイルによって設定されます)。ここで、リソースを開くためのファイル名を含むフルパスを作成します。それだけに行きます:
fd = open(PATH "/file", flags);
恐ろしいのではなく、非常に一般的です:
char buffer[256];
snprintf(buffer, 256, "%s/file", PATH);
fd = open(buffer, flags);
一般的な恐ろしい解決策は次のとおりです。
まあ、私はそれを使ったことがないし、誰にも勧めるかどうかはわかりませんが、サイモンタサムの co-ルーチントリック
配列または列挙型を初期化するとき、初期化子リストの最後の項目の後にコンマを置くことができます。例えば:
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;
}
(もちろん)戻り値が構造体の割り当てを使用して値ごとにコピーされる限り、これは安全です。
Point Origin;
Origin = point_new(0, 0);
このようにして、非常にクリーンでオブジェクト指向のコードを、すべて標準Cで記述できます。
奇妙なベクトルインデックス:
int v[100]; int index = 10;
/* v[index] it's the same thing as index[v] */
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
Cコンパイラは、いくつかの標準の1つを実装しています。ただし、標準があるからといって、言語のすべての側面が定義されているわけではありません。 Duffのデバイス は、たとえば、人気の高い「隠された」機能であり、最近のコンパイラーは、頻繁に使用されるこのパターンの望ましい効果を最適化手法が無効にしないようにする特別な目的の認識コードを備えています。
一般に、コンパイラが使用しているC標準のかみそりエッジで実行しているため、隠された機能や言語のトリックは推奨されません。このようなトリックの多くは、あるコンパイラから別のコンパイラへは機能しません。また、これらの種類の機能は、特定のメーカーのコンパイラスイートのあるバージョンから別のバージョンへ失敗することがよくあります。
Cコードを壊したさまざまなトリックには次のものがあります。
その他の問題と、プログラマーがほとんどのC標準で「コンパイラー依存」動作として指定されている実行モデルについて仮定するたびに生じる問題。
Gcc(c)には、ネストされた関数宣言や、?:演算子のa?:b形式など、有効にできるいくつかの楽しい機能があり、aがfalseでない場合にaを返します。
列挙型を使用したコンパイル時の前提条件チェック:愚かな例ですが、コンパイル時の構成可能な定数を持つライブラリには本当に役立ちます。
#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)
};
最近0ビットフィールドを発見しました。
struct {
int a:3;
int b:2;
int :0;
int c:4;
int d:3;
};
のレイアウトになります
000aaabb 0ccccddd
:0なしの代わりに;
0000aaab bccccddd
0幅のフィールドは、次のアトミックエンティティで次のビットフィールドを設定する必要があることを示します(char
)
私のお気に入りのCの「隠された」機能は、printfで%nを使用してスタックに書き戻すことです。通常、printfはフォーマット文字列に基づいてスタックからパラメーター値をポップしますが、%nはそれらを書き戻すことができます。
セクション3.4.2 こちら をご覧ください。多くの厄介な脆弱性につながる可能性があります。
iNT(3)を使用してコードにブレークポイントを設定することは、私の一番のお気に入りです
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またはその他のメモリ割り当て手法を呼び出す前に、引数を健全性チェックする必要があるため、これは実際には珍しいことではありません。
私はあなたが作ることができる可変サイズの構造が好きでした:
typedef struct {
unsigned int size;
char buffer[1];
} tSizedBuffer;
tSizedBuffer *buff = (tSizedBuffer*)(malloc(sizeof(tSizedBuffer) + 99));
// can now refer to buff->buffer[0..99].
また、offsetofマクロはANSI Cにありますが、初めて見たときは魔法のようなものでした。基本的に、構造変数として再キャストされるNULLポインターにアドレス演算子(&)を使用します。
好き __LINE__
および__FILE__
。こちらをご覧ください: http://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html
GCCのラムダ(匿名関数など):
#define lambda(return_type, function_body) \
({ return_type fn function_body fn })
これは次のように使用できます。
lambda (int, (int x, int y) { return x > y; })(1, 2)
展開されるもの:
({ int fn (int x, int y) { return x > y } fn; })(1, 2)
入力バッファをクリアするには、fflush(stdin)
を使用できません。正しい方法は次のとおりです。scanf("%*[^\n]%*c")
これにより、入力バッファーからすべてが破棄されます。
私はこれを15年以上のCプログラミングの後に初めて発見しました。
struct SomeStruct
{
unsigned a : 5;
unsigned b : 1;
unsigned c : 7;
};
ビットフィールド!コロンの後の数値は、メンバーが指定した型にパックされたメンバーが必要とするビット数です。したがって、符号なしが16ビットの場合、上記は次のようになります。
xxxc cccc ccba aaaa
スキズ
異常な型キャストを使用した型の変換。隠された機能ではありませんが、非常に注意が必要です。
例:
コンパイラがフロートを保存する方法を知る必要がある場合は、これを試してください:
uint32_t Int;
float flt = 10.5; // say
Int = *(uint32_t *)&flt;
printf ("Float 10.5 is stored internally as %8X\n", Int);
または
float flt = 10.5; // say
printf ("Float 10.5 is stored internally as %8X\n", *(uint32_t *)&flt);
型キャストの巧妙な使用に注意してください。変数のアドレス(ここ&flt)を目的のタイプ(ここ(uint32_t *))に変換し、その内容を抽出します(「*」を適用)。
これは式の反対側でも機能します:
*(float *)&Int = flt;
これは、ユニオンを使用しても実現できます。
typedef union
{
uint32_t Int;
float flt;
} FloatInt_type;
Gccの初期バージョンは、ソースコードで「#pragma」に遭遇したときにゲームを実行しようとしました。 here もご覧ください。
私は一度これを少しのコードで見せて、それが何をしたのか尋ねました:
hexDigit = "0123456789abcdef"[someNybble];
別のお気に入りは:
unsigned char bar[100];
unsigned char *foo = bar;
unsigned char blah = 42[foo];
変数をリテラルと比較する場合は、_==
_演算子のleftにリテラルを配置して、代入演算子を誤って使用したときにコンパイラーがエラーを出すようにすることをお勧めします代わりに。
_if (0 == count) {
...
}
_
一見奇妙に見えるかもしれませんが、頭痛の種を減らすことができます(たまたまif (count = 0)
と入力した場合など)。
Steve Webb が指摘した__LINE__
および__FILE__
マクロ。これは、以前の仕事でメモリ内ログを取得するためにハッキングしたことを思い出させます。
デバッグに使用されているPCにデバイスからログ情報を渡すために使用できるポートがないデバイスで作業していました。ブレークポイントを使用して停止し、デバッガーを使用してプログラムの状態を知ることもできますが、システムトレースに関する情報はありませんでした。
デバッグログへのすべての呼び出しは事実上単一のグローバルマクロであったため、ファイル名と行番号をグローバル配列にダンプするようにそのマクロを変更しました。この配列には、どのデバッグコールが呼び出されたかを示す一連のファイル名と行番号が含まれていたため、実行トレース(実際のログメッセージではありません)がかなりわかります。デバッガーによる実行を一時停止し、これらのバイトをローカルファイルにダンプし、スクリプトを使用してこの情報をコードベースにマップできます。これが可能になったのは、コーディングのガイドラインが厳格だったため、1つのファイルでロギングメカニズムを変更する必要があったためです。
タイプポインターの変数を宣言するためのintptr_t。 C99固有であり、stdint.hで宣言されています
本当に隠された機能ではありませんが、ブードゥー教のように見えました。初めてこのようなものを見たとき:
void callback(const char *msg, void *data)
{
// do something with msg, e.g.
printf("%s\n", msg);
return;
data = NULL;
}
この構成の理由は、-Wextraを使用して「data = NULL;」行を指定せずにコンパイルすると、gccが未使用のパラメーターに関する警告を吐き出すからです。しかし、この役に立たない行では、警告は表示されません。
編集:私はそれらの警告を防ぐために他の(より良い)方法があることを知っています。初めて見たとき、私には奇妙に見えました。
関数ポインタのサイズは標準ではありません。少なくともK&R本には含まれていません。他のタイプのポインターのサイズについて説明していますが、(と思う)関数ポインターのsizeof
は未定義の動作です。
また、sizeof
はコンパイル時の演算子です。多くの人がsizeof
が関数か、オンラインフォーラムの演算子かを尋ねています。
私が見た1つのエラーは次のとおりです(簡単な例):
int j;
int i;
j = sizeof(i++)
i
はコンパイル時に評価されるため、sizeof
の増分は実行されません。プログラマーは、1つのステートメントでi
の増分とsizeof
の計算の両方の操作をハッキングすることを意図していました。
Cの演算子の優先順位は、評価の順序ではなく関連の順序を管理します。たとえば、3つの関数f
、g
、h
があり、それぞれがint
を返し、それらが次のような式である場合:
f() + g() * h()
C標準は、これらの関数の評価の順序に関する規則を与えていません。 g
とh
の結果は、f
の結果を追加する前に乗算されます。関数が状態を共有し、計算がこれらの関数の評価の順序に依存する場合、これはエラーにつながる可能性があります。これは、移植性の問題につながる可能性があります。
抜粋:
このページには、興味深いCプログラミングの質問/パズルのリストがあります。リストされているこれらのプログラムは、友人から転送された電子メールとして受け取ったものです。 Cでのコーディング経験からのいくつか.
register
キーワードを使用していくつかの変数を宣言することで、速度が向上しました。これは、Cレジスタをローカルストレージとして使用するためのヒントをCコンパイラに与えます。現代のCコンパイラはこれを自動的に行うため、これはおそらく不要になります。
同じ型のメンバーを持つ構造体があるとします。
struct Point {
float x;
float y;
float z;
};
インスタンスをfloatポインターにキャストし、配列インデックスを使用できます。
Point a;
int sum = 0, i = 0;
for( ; i < 3; i++)
sum += ((float*)a)[i];
かなり初歩的ですが、簡潔なコードを書くときに役立ちます。
よく忘れられるprintf
フォーマット文字列の%n
指定子は、時には非常に実用的です。 %nは、printfが出力をフォーマットするときに使用される仮想カーソルの現在の位置を返します。
int pos1, pos2;
char *string_of_unknown_length = "we don't care about the length of this";
printf("Write text of unknown %n(%s)%n text\n", &pos1, string_of_unknown_length, &pos2);
printf("%*s\\%*s/\n", pos1, " ", pos2-pos1-2, " ");
printf("%*s", pos1+1, " ");
for(int i=pos1+1; i<pos2-1; i++)
putc('-', stdout);
putc('\n', stdout);
次の出力があります
Write text of unknown (we don't care about the length of this) text
\ /
--------------------------------------
少し不自然ですが、きれいなレポートを作成するときにいくつかの用途があります。
連鎖計算/エラーリターンにNaNを使用します。
//#include <stdint.h>
static uint64_t iNaN = 0xFFF8000000000000;
const double NaN = *(double *)&iNaN; //静かなNaN
内部関数はエラーフラグとしてNaNを返すことができます。任意の計算で安全に使用でき、結果は常にNaNになります。
注:NaN!= NaN ... isnan(x)を使用するか、独自にロールするため、NaNのテストは難しいです。
x!= xは、xがNaNであれば数学的には正しいが、一部のコンパイラーによって最適化される傾向がある
Typeof()演算子が好きです。コンパイル時に解決されるという点で、sizeof()と同様に機能します。バイト数を返す代わりに、タイプを返します。これは、変数が他の変数と同じ型になるように宣言する必要がある場合に便利です。
typeof(foo) copy_of_foo; //declare bar to be a variable of the same type as foo
copy_of_foo = foo; //now copy_of_foo has a backup of foo, for any type
これは単なるgcc拡張機能である可能性がありますが、わかりません。
私はこれを読むだけです 記事 。いくつかのC言語と他のいくつかの言語の「隠された機能」があります。
Breakのようなcontinueステートメントを使用できるように、スイッチ内でwhile(0)を使用してはどうですか。
void sw(int s)
{
switch (s) while (0) {
case 0:
printf("zero\n");
continue;
case 1:
printf("one\n");
continue;
default:
printf("something else\n");
continue;
}
}