208)LinuxカーネルがC89からC11に
LinuxカーネルがC89からC99あるいはC11と、新しい規格に移行する可能性があるらしい(「トーバルズ氏、Linuxカーネルを「C89」から「C11」コードに移行する準備」)。なんでも投機的実行の脆弱性を解決するには、変数の宣言をブロックの先頭以外でも可能な新しい規格への移行が必要とのこと。
投機的実行とは、条件分岐を事前に処理しておいて、使わなかったケースを放棄することで、結果的に実行速度を速くするものだ。しかし、脆弱性もあって、本来参照できないアドレスにアクセスされてしまう可能性がある。一時期話題になったSpectre(スペクター)がそれだ。
さて、C89では、変数をブロック({}
)の先頭で宣言しなければならない。したがって、ループ文で使うカウンタ変数もループの外側で宣言する。つまり、カウンタ変数をループの外側からアクセスすることが可能である。
次に示したコードと実行結果を見ると、出力された変数i
の値がすべて違うことが分かる。普通に使っていれば特に問題はないのだが、投機的実行に係ると致命的な問題なのかもしれない。なお、オプションの-pedantic-errors -std=c89
は、gccの拡張機能の停止とC89の規格でコンパイルするものだ。
#include <stdio.h>
int main(void)
{
const int Max = 10;
int i;
printf("%2d | i = %d\n", __LINE__, i);
#line 14
printf("%d | i = ", __LINE__);
for (i = 0; i < Max; i++) {
printf("%d ", i);
}
putc('\n', stdout);
#line 19
{
int i = 99;
printf("%d | i = %d\n", __LINE__, i);
}
printf("%d | i = %d\n", __LINE__, i);
return 0;
}
$ gcc -pedantic-errors -std=c89 !$:r
$ ./a.out
8 | i = 1
14 | i = 0 1 2 3 4 5 6 7 8 9
21 | i = 99
24 | i = 10
C99の書き方だと、ループの初期化にてカウンタ変数が宣言できる。この場合は、ループの外側からカウンタ変数にアクセスすることはできず(消滅している)、i
が宣言されていないとしてコンパイルエラーになる。
#include <stdio.h>
int main(void)
{
const int Max = 10;
for (int i = 0; i < Max; i++) {
printf("%d \n", i);
}
printf("i = %d\n", i);
return 0;
}
$ gcc !$
test.c: In function 'main':
test.c:11:22: error: 'i' undeclared (first use in this function)
11 | printf("%d | i = %d\n", i);
| ^
test.c:11:22: note: each undeclared identifier is reported only once for each function it appears in
なお、C89の変数宣言はブロックの先頭なので、次のようにカウンタ変数とループ文をブロック化することも可能である。しかし、投機的実行に係る問題はこれでは解決できないのだろう。
#include <stdio.h>
int main(void)
{
const int Max = 10;
{
int i;
for (i = 0; i < Max; i++) {
printf("%d ", i);
}
}
putc('\n', stdout);
return 0;
}
個人的にC89からの移行はかなり嬉しい(律儀にC89で書く必要もなかったんだけど)。そんな訳で、新しい規格のCを勉強しようと思う。『Cクイックリファレンス』(第2版、オライリー、2016年)が、C11に準拠している。
2022-03-06