拾い物のコンパス

まともに書いたメモ

見たことあるCコーディングミス

知り合いが実際に書いて悩んでいたCコードを2種類書き残す。

その1

...
int i=0,
    count[i];
...(snip)...

一見してやばいのはわかるはず。要素数0の配列を宣言していた。その場ではただひどいコードだとだけ思ったが、あとから気になってちょっと実験してみた。

実験コード

/* zero_array.c */
#include <stdio.h>

int main(void)
{
   int  a = 0,
        b = 0,
        c[a];
        
   printf("a: %p\nb: %p\nc: %p\n", &a, &b, c);
}
/* E.O.F. */

実行結果

実行したところ、

a: 0xbf877850
b: 0xbf877854
c: 0xbf877840

となった。変数a, bは正しくスタックに積まれているようだ。cは確保されているのかどうかとても怪しいところ。領域は確保されていないがアクセスできるポインタみたいな印象であるが、詳細は不明。わかったら追記する。
当たり前ではあるが、上のソースでc[4]に任意の値を書くとaを書き換えることができる。ただのセキュリティホールにしか見えない。

2015/10/31追記

turn_upさんから指摘があった。要素数0の配列は構造体で

struct hoge {
   int i;
   int a[0];
}

とするとaiの次のアドレスを指すようになる。上手く使うと便利なようだが、マイナーな上にGCC拡張だから、依存性が強い。C++では配列は1個以上の要素を持たないといけないで要素数0の宣言はできない。

その2 (考察間違い。追記要参照)

これも知り合いが書いてしまったコード。詳しく覚えていないが、こんな雰囲気だった。

while (1) {

if (hoge = 32) break;
else ...(snip)...

if文の条件で代入を行うとTrue扱いになってすぐループを抜けてしまっていた。
気になったのでいじっていたら、代入が失敗する場合を1種類だけ見つけた。
それは以下の場合である。

int/char a;
if (a = 0.11) printf("True\n");
else          printf("False\n");

intcharの変数に小数を代入しようとすると失敗するようだ。

2015/10/31追記

turn_upさんから指摘があって、改めて考えてみた。
冷静に考えてみると、分岐命令の中身は
if (i = 0) -> if (i) -> if (0)
の流れでFalseになっているんじゃないかと思って、アセンブラを見てみたら案の定

mov DWORD PTR [esp+0x1c], 0x0
cmp DWORD PTR [esp+0x1c], 0x0
je 0x80......

となっていた。だから、変数に0を代入した場合はすべてFalseになる。これはint/char/float/doubleの時で確認できた。
細かいことではあるが、先ほどのコードのようなシンプルな分岐では、i0以外を代入したときは実行ファイルにcmpはなかった。自明すぎてコンパイラに消されてしまったようだ。-O0をつけても同じだった。

どこかで聞いたのは、分岐条件の記述は変数を後に書いたら良いとのことだった。
例えばif (NULL == ptr)if (32 == hoge)のようにである。これならば間違えてしまったときはコンパイルエラーが出てすぐに気付くことができる。

参考ページ

Using and Porting GNU CC - C 言語ファミリに対する拡張

"B"-con - Laboratory - Data & Algorithm - アクロバティックなコード (1)