プログラミングにおいては、相当な理由がない限り、わざわざ処理系依存なコードを書くことはありません。仕事なら「このコーディング規約に従うこと」「この静的解析ツールで警告をゼロにしろ」「MISRAガイドラインに従う」だの、厳しく言われるかもしれません。でも趣味の世界ならなんでもあり、やりたい放題できます!
ということで、処理系依存なコードを沢山書いてみましょう。と、突然言われてもなかなか処理系に依存するコードは出てきません。そこで、C言語の規格を参照して、「処理系依存」とされているものを順に書いてみましょう。そして、コンパイラによって動作が本当に違う!って確認していきたいと思います。
処理系依存なコードとは
2015年現在では、C言語の規格としてはC99(ISO/IEC 9899:1999、JIS X 3010:2003)やC11(ISO/IEC 9899:2011、対応するJIS規格は未発行)があります。 C99であればJISから日本語で規格を読むことができます(ただしWindows環境に限る。LinuxやMacでの閲覧は出来ませんでした)。 現在最新のC11ではJIS規格はまだありません。ISO規格の閲覧は有料となるため、C11では最終ドラフトのN1570で代替することにします。
それでは規格の「Annex J (informative) Portability issues .」 (JIS規格の場合は「附属書J.可搬性」)を参照して行きましょう。何ページにも渡って、下記の分類で「べからず集」が記載されています。
- J.1 Unspecified behavior
- J.2 Undefined behavior
- J.3 Implementation-defined behavior
- J.4 Locale-specific behavior
- J.5 Common extensions
今回はこれから幾つかピックアップして実行してみます。使用するコンパイラはgccとclangです。バージョンはgcc 4.9.2とclang (Apple LLVM version 6.0 (clang-600.0.56) (based on LLVM 3.5svn))で実行しました。
J1: Unspecified behavior
— Whether two string literals result in distinct arrays (6.4.5). ...文字列リテラルが同じポインタになるかどうかは処理系依存
#include
int main()
{
const char* str1 = "hello, world";
const char* str2 = "hello, world";
printf("%s str1 = %p, str2 = %p\n",
(str1==str2 ? "[Match]" : "[Different]"),
str1, str2
);
return 0;
}
これを実行すると「Match」と表示されるか「Different」となるかは処理系次第のようです。GCCもClangも、デフォルトのままコンパイルすると「Match」となりました。文字列リテラルを最適化して配置するのがデフォルトなのですね。
— The order in which the function designator, arguments, and subexpressions within the arguments are evaluated in a function call (6.5.2.2). ... 入れ子になった関数の呼び出し順序は処理系依存です。
#include
int f(int x, int y)
{
printf("f(%d, %d)\n", x, y);
return x+y;
}
int g(int n)
{
printf("g(%d)\n", n);
return n;
}
int main()
{
int a = f(f(g(1),g(2)) , f(g(3),g(4)));
printf("a = %d\n", a);
return 0;
}
これはGCCとClangで実行順が異なりました。GCCでは一番右側から実行されていきます。
「
g(4)
g(3)
f(3, 4)
g(2)
g(1)
f(1, 2)
f(3, 7)
a = 10
」
しかしClangでは、一番左側から実行されていくようです。
「
g(1)
g(2)
f(1, 2)
g(3)
g(4)
f(3, 4)
f(3, 7)
a = 10
」
面白いですね。
— Whether or not a size expression is evaluated when it is part of the operand of a sizeof operator and changing the value of the size expression would not affect the result of the operator (6.7.6.2).
#include
int main()
{
int a = 1;
size_t b = sizeof(++a);
printf("a=%d. \n", a);
a = 1;
size_t c = sizeof(int [++a]);
printf("a=%d. (will be 2)\n", a);
a = 1;
size_t d = sizeof(int [++a % 1 + 10]);
printf("a=%d. (unspecified: 1 or 2)\n", a);
printf("b=%zd, c=%zd, d=%zd\n", b,c,d);
return 0;
}
sizeof
演算子に式を与えた場合の動作で、可変長配列について規定したものです。++a % 1 + 10
は++a
を実行してもしなくても結果は変わりません。ちなみにgccもclangもともに1,2,2という結果でした。
J2: Undefined behavior
J3: Implementation-defined behavior
J4: Locale-specific behavior
j5: Common extensions
続きはまた次回。
おわり
分量がおおいので、今回はここまででおしまいです。
(英語力の問題もあり、誤解をしていたらごめんなさい)
追記
追記:下記サイトにて未定義動作や処理系依存のコード例と、その回避方法が記載されていました。 https://www.securecoding.cert.org/confluence/display/seccode/DD.+Unspecified+Behavior;jsessionid=9AFACDECE35236C29B02BC507735CF34 https://www.securecoding.cert.org/confluence/display/seccode/CC.+Undefined+Behavior
0 件のコメント:
コメントを投稿