私は本でこの行を読みました:
C ++関数が特定の変数の値を変更するかどうかを実際に決定できるコンパイラを構築することはおそらく不可能です。
このパラグラフは、コンパイラがcons-nessをチェックするときに保守的である理由について話していました。
なぜそのようなコンパイラを構築することが不可能なのですか?
コンパイラーは常に、変数が再割り当てされているか、非const関数が呼び出されているか、または非constパラメーターとして渡されているかどうかを常にチェックできます...
私は本でこの行を読みました:
C ++関数が特定の変数の値を変更するかどうかを実際に決定できるコンパイラを構築することはおそらく不可能です。
このパラグラフは、コンパイラがcons-nessをチェックするときに保守的である理由について話していました。
なぜそのようなコンパイラを構築することが不可能なのですか?
コンパイラーは常に、変数が再割り当てされているか、非const関数が呼び出されているか、または非constパラメーターとして渡されているかどうかを常にチェックできます...
回答:
なぜそのようなコンパイラを構築することが不可能なのですか?
同じ理由で、特定のプログラムが終了するかどうかを決定するプログラムを作成することはできません。これは停止問題と呼ばれ、計算不可能な問題の1つです。
明確にするために、場合によっては関数が変数を変更すると判断できるコンパイラを作成できますが、関数が変数を変更する(または停止する)かどうかを確実に通知するコンパイラを作成することはできません。すべての可能な機能。
ここに簡単な例があります:
void foo() {
if (bar() == 0) this->a = 1;
}
コンパイラは、そのコードを見ただけで、foo
今後変更されるa
かどうかをどのように判断できますか?機能するかどうかは、関数の外部条件、つまりの実装に依存しますbar
。停止の問題が計算できないことの証明にはそれ以上のものがありますが、リンクされているWikipediaの記事(およびすべての計算理論の教科書)ですでにうまく説明されているため、ここでは正しく説明しません。
そのようなコンパイラが存在すると想像してください。また、便宜上、渡された関数が指定された変数を変更した場合は1を返し、変更されなかった場合は0を返すライブラリ関数を提供するとします。次に、このプログラムは何を印刷する必要がありますか?
int variable = 0;
void f() {
if (modifies_variable(f, variable)) {
/* do nothing */
} else {
/* modify variable */
variable = 1;
}
}
int main(int argc, char **argv) {
if (modifies_variable(f, variable)) {
printf("Modifies variable\n");
} else {
printf("Does not modify variable\n");
}
return 0;
}
f
変数を変更できるかどうかであり、変数を変更できるかどうかではありません。この答えは正しいです。
modifies_variable
コンパイラのソースからのコードをコピー/貼り付けることを妨げるものは何もありません。引数を完全に無効にします。(オープンソースを想定していますが、要点は明確である必要があります)
「これらの入力を与えられた変数を変更するかしないか」と「変数を変更する実行パスがあるか」を混同しないでください。
前者は不透明な述語決定と呼ばれ、決定するのは簡単ではありません-停止問題からの削減は別として、入力が不明なソース(ユーザーなど)からのものである可能性があることを指摘するだけで済みます。これは、C ++だけでなく、すべての言語に当てはまります。
ただし、後者のステートメントは、すべての最適化コンパイラーが行う構文解析ツリーを見れば判断できます。それらが行う理由は、純粋な関数 (および参照透過の一部の定義では、参照透過関数)は、簡単にインライン化できる、またはコンパイル時に値が決定されるなど、適用できるあらゆる種類の優れた最適化を備えているためです。しかし、関数が純粋であるかどうかを知るには、変数を変更できるかどうかを知る必要があります。
したがって、C ++に関する意外なステートメントのように見えるのは、実際にはすべての言語についてのささいなステートメントです。
「C ++関数が特定の変数の値を変更するかどうか」のキーワードは「意志」だと思います。C ++関数が特定の変数の値を変更できるかどうかをチェックするコンパイラーを構築することは確かに可能です。変更が発生することを確実に言うことはできません。
void maybe(int& val) {
cout << "Should I change value? [Y/N] >";
string reply;
cin >> reply;
if (reply == "Y") {
val = 42;
}
}
const
ネスチェックについて話すときに心に留めていたことだと信じています。
これは実行可能であり、コンパイラは一部の関数に対して常にそれを実行しています。これは、たとえば、単純なインラインアクセサまたは多くの純粋な関数の簡単な最適化です。
一般的なケースではそれを知ることは不可能です。
別のモジュールからのシステムコールや関数呼び出し、またはオーバーライドされる可能性のあるメソッドへの呼び出しがある場合は常に、ハッカーがスタックオーバーフローを使用して関係のない変数を変更することによる敵対的テイクオーバーが起こりました。
ただし、constを使用し、グローバルを回避し、ポインターへの参照を優先し、無関係なタスクに変数を再利用しないようにする必要があります。これにより、積極的な最適化を実行するときにコンパイラーのライフが容易になります。
これを説明する方法は複数ありますが、その1つが停止問題です。
計算可能性理論では、停止の問題は次のように述べることができます:「任意のコンピュータープログラムの説明を与えられて、プログラムが実行を終了するか、永久に実行し続けるかを決定します」。これは、プログラムと入力が与えられた場合、プログラムがその入力で実行されると最終的に停止するか、それとも永久に実行されるかを決定する問題に相当します。
アランチューリングは、1936年に、すべての可能なプログラムと入力のペアの停止問題を解決する一般的なアルゴリズムは存在できないことを証明しました。
次のようなプログラムを作成すると、
do tons of complex stuff
if (condition on result of complex stuff)
{
change value of x
}
else
{
do not change value of x
}
価値はx
変わりますか?これを決定するには、最初に、do tons of complex stuff
パーツが条件を発生させるかどうか、またはさらに基本的に、それが停止するかどうかを判断する必要があります。それはコンパイラーができないことです。
停止問題を直接使用する答えがないことに本当に驚いています!この問題から停止の問題への非常に簡単な削減があります。
コンパイラが関数が変数の値を変更したかどうかを判断できると想像してください。その後、プログラムの残りのすべての呼び出しでxの値を追跡できると仮定すると、次の関数がyの値を変更するかどうかを確実に判別できます。
foo(int x){
if(x)
y=1;
}
さて、私たちが好きなプログラムのために、それを次のように書き換えましょう:
int y;
main(){
int x;
...
run the program normally
...
foo(x);
}
プログラムがyの値を変更した場合にのみ、それが終了することに注意してください。foo()は終了する前に行う最後の処理です。これは、停止の問題を解決したことを意味します。
上記の削減が示すのは、変数の値が変化するかどうかを判断する問題が、少なくとも停止の問題と同じくらい難しいということです。停止の問題は計算不可能であることが知られているので、これもまたそうでなければなりません。
y
。foo()
すぐに戻ってmain()
終了するように見えます。(また、あなたはfoo()
議論なしで呼んでいます...それは私の混乱の一部です。)
関数が、コンパイラがソースを「認識しない」別の関数を呼び出すとすぐに、変数が変更されたと想定する必要があります。そうでない場合、以下でさらに問題が発生する可能性があります。たとえば、「foo.cpp」に次のように記述したとします。
void foo(int& x)
{
ifstream f("f.dat", ifstream::binary);
f.read((char *)&x, sizeof(x));
}
これは "bar.cpp"にあります:
void bar(int& x)
{
foo(x);
}
コンパイラx
は、変更されていない(または変更されている、より適切に)ことをどのように「知る」ことができbar
ますか?
これが十分に複雑でなければ、もっと複雑なものを思いつくことができると私は確信しています。
const_cast
fooにaを追加した場合でも、x
変更は行われます- const
変数を変更しないことを示す契約に違反しますが、何でも「より多くのconst」に変換でき、const_cast
存在します。言語の設計者は確かに、const
値を変更する必要があるかもしれないと信じる正当な理由が時々あるという考えを心に留めていました。
指摘されているように、変数が変更されるかどうかをコンパイラが判断することは、一般的に不可能です。
定数をチェックするとき、問題は変数が関数によって変更できるかどうかであるようです。ポインタをサポートする言語では、これも難しいです。他のコードがポインターで行うことを制御することはできません。外部ソースから読み取ることもできます(可能性は低いですが)。メモリへのアクセスを制限する言語では、これらのタイプの保証が可能で、C ++よりも強力な最適化が可能です。
質問をより具体的にするために、私は次の一連の制約が本の著者が考えていたかもしれないものであったかもしれないことを示唆します:
コンパイラー設計のコンテキストでは、コードジェネレーションの正確性やコード最適化のコンテキストでのコンパイラーライターの観点から、仮定1、3、4は完全に理にかなっていると思います。前提条件2は、volatileキーワードがない場合に意味があります。そして、これらの仮定は、提案された回答の判断をより明確にするのに十分な質問にも焦点を当てています:-)
これらの仮定を前提として、定数が仮定できない主な理由は、変数のエイリアシングによるものです。コンパイラーは、別の変数がconst変数を指しているかどうかを知ることができません。エイリアシングは、同じコンパイル単位内の別の関数が原因である可能性があります。その場合、コンパイラは関数全体を調べ、呼び出しツリーを使用して、エイリアシングが発生する可能性があることを静的に判断できます。ただし、エイリアスがライブラリまたは他の外部コードによるものである場合、コンパイラは、関数のエントリ時に変数がエイリアスされているかどうかを知る方法がありません。
変数/引数がconstとマークされている場合、エイリアスによって変更されることはないはずですが、コンパイラライターにとってはかなり危険です。人間のプログラマーが変数constを、システム全体、OS、またはライブラリーの動作を知らない大規模なプロジェクトの一部として宣言することは、変数が実際にそうなることを本当に知っているため、危険な場合さえあるt変更します。
変数が宣言されconst
ていても、不適切に記述されたコードによって上書きされる可能性があるわけではありません。
// g++ -o foo foo.cc
#include <iostream>
void const_func(const int&a, int* b)
{
b[0] = 2;
b[1] = 2;
}
int main() {
int a = 1;
int b = 3;
std::cout << a << std::endl;
const_func(a,&b);
std::cout << a << std::endl;
}
出力:
1
2
a
とb
、スタック変数であり、b[1]
同じように同じメモリ位置であることを起こりますa
。
const
、すべてがラベル付けされている場合にコンパイラが何かが本当にそうであるかをコンパイラが理解できない理由についてのOPの元の質問の例にすぎませんconst
。これは、未定義の動作がC / C ++の一部であるためです。私は彼の質問に答える別の方法を見つけようとしていたのではなく、停止の問題や外部からの人間の入力に言及するのではありませんでした。
私のコメントを拡大すると、その本のテキストは問題を難読化するのが不明確です。
私がコメントしたように、その本はこう書こうとしている、「無限の数のサルに、これまでに記述される可能性のある考えられるすべてのC ++関数を記述させましょう。変数(サルが記述した特定の関数)を選択する場合があります。使用すると、関数がその変数を変更するかどうかを判断できません。」
もちろん、特定のアプリケーションの一部(多くでも)の関数の場合、これはコンパイラーによって簡単に決定できます。しかし、すべてではない(または必ずしもほとんどではない)。
この機能は簡単に分析できます:
static int global;
void foo()
{
}
「foo」は明らかに「グローバル」を変更しません。これは何も変更せず、コンパイラーはこれを非常に簡単に処理できます。
この関数はそれほど分析できません:
static int global;
int foo()
{
if ((rand() % 100) > 50)
{
global = 1;
}
return 1;
「foo」のアクションは、実行時に変更される可能性がある値に依存するため、「グローバル」を変更するかどうかはコンパイル時に決定することはできません。
この全体の概念は、コンピューター科学者が理解するよりも理解するのがはるかに簡単です。実行時に変更される可能性のある事柄に基づいて、関数が何か異なることを実行できる場合、関数が実行されるまで何を実行するかを計算することはできません。証明が不可能であろうとなかろうと、それは明らかに不可能です。